Improve new row top component and update new row bottom component

This commit is contained in:
Andrew Kingston 2023-04-22 15:09:49 +01:00
parent 9361c91ad4
commit 61f05492ad
5 changed files with 285 additions and 24 deletions

View File

@ -29,6 +29,7 @@
GutterWidth,
DefaultRowHeight,
} from "../lib/constants"
import NewRowBottom from "./NewRowBottom.svelte"
export let API = null
export let tableId = null

View File

@ -0,0 +1,228 @@
<script>
import GridCell from "../cells/GridCell.svelte"
import { getContext, onMount, tick } from "svelte"
import { Icon, Button } from "@budibase/bbui"
import GridScrollWrapper from "./GridScrollWrapper.svelte"
import DataCell from "../cells/DataCell.svelte"
import { fade } from "svelte/transition"
import { GutterWidth } from "../lib/constants"
export let animate = false
export let rowIdx = 0
const {
hoveredRowId,
focusedCellId,
stickyColumn,
config,
dispatch,
rows,
focusedCellAPI,
tableId,
subscribe,
renderedColumns,
focusedRow,
reorder,
} = getContext("grid")
let isAdding = false
let newRow = { _id: `new${rowIdx}` }
let touched = false
$: rowId = `new${rowIdx}`
$: firstColumn = $stickyColumn || $renderedColumns[0]
$: width = GutterWidth + ($stickyColumn?.width || 0)
$: $tableId, (isAdding = false)
$: rowHovered = $hoveredRowId === rowId
$: rowFocused = $focusedRow?._id === rowId
$: reorderSource = $reorder.sourceColumn
const addRow = async () => {
// Create row
const savedRow = await rows.actions.addRow(newRow, rowIdx)
if (savedRow) {
// Select the first cell if possible
if (firstColumn) {
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
}
// Reset state
isAdding = false
newRow = {}
}
}
const cancel = () => {
newRow = { _id: rowId }
isAdding = false
$focusedCellId = null
$hoveredRowId = null
}
const startAdding = async () => {
isAdding = true
$hoveredRowId = rowId
if (firstColumn) {
$focusedCellId = `${rowId}-${firstColumn.name}`
// Also focus the cell if it is a text-like cell
if (["string", "number"].includes(firstColumn.schema.type)) {
await tick()
$focusedCellAPI?.focus()
}
}
}
const updateValue = (rowId, columnName, val) => {
touched = true
newRow[columnName] = val
}
const addViaModal = () => {
isAdding = false
dispatch("add-row")
}
const handleKeyPress = e => {
if (!isAdding) {
return
}
if (e.key === "Escape") {
cancel()
}
}
onMount(() => subscribe("add-row-inline", startAdding))
onMount(() => {
document.addEventListener("keydown", handleKeyPress)
return () => {
document.removeEventListener("keydown", handleKeyPress)
}
})
</script>
<!-- Only show new row functionality if we have any columns -->
<div
class="container"
transition:fade={{ duration: 130 }}
on:focus
on:mouseenter={() => ($hoveredRowId = rowId)}
on:mouseleave={() => ($hoveredRowId = null)}
>
<div class="sticky-column" style="flex: 0 0 {width}px">
<GridCell width={GutterWidth} highlighted={rowHovered || rowFocused}>
<div class="gutter">
<div class="number">
<Icon name="Add" />
</div>
{#if $config.allowExpandRows}
<div class="expand" class:visible={rowFocused || rowHovered}>
<Icon name="Maximize" size="S" hoverable on:click={addViaModal} />
</div>
{/if}
</div>
</GridCell>
{#if $stickyColumn}
{@const cellId = `${rowId}-${$stickyColumn.name}`}
<DataCell
{cellId}
{rowFocused}
highlighted={rowHovered || rowFocused}
column={$stickyColumn}
row={newRow}
focused={$focusedCellId === cellId}
width={$stickyColumn.width}
{updateValue}
rowIdx={0}
/>
{/if}
</div>
<GridScrollWrapper scrollHorizontally wheelInteractive>
<div class="row">
{#each $renderedColumns as column}
{@const cellId = `${rowId}-${column.name}`}
{#key cellId}
<DataCell
{cellId}
{column}
{updateValue}
{rowFocused}
highlighted={rowHovered ||
rowFocused ||
reorderSource === column.name}
row={newRow}
focused={$focusedCellId === cellId}
width={column.width}
rowIdx={0}
/>
{/key}
{/each}
</div>
</GridScrollWrapper>
{#if Object.keys(newRow || {}).length > 1}
<div class="buttons" in:fade={{ duration: 130 }}>
<Button size="M" cta on:click={addRow}>Save</Button>
<Button size="M" secondary newStyles on:click={cancel}>Cancel</Button>
</div>
{/if}
</div>
<style>
.container {
display: flex;
flex-direction: row;
align-items: stretch;
}
/* Floating buttons which sit on top of the underlay but below the sticky column */
.buttons {
display: flex;
flex-direction: row;
gap: 8px;
pointer-events: all;
z-index: 3;
position: absolute;
top: calc(var(--row-height) + 24px);
left: var(--gutter-width);
}
/* Sticky column styles */
.sticky-column {
display: flex;
z-index: 4;
position: relative;
align-self: flex-start;
}
.sticky-column :global(.cell:not(:last-child)) {
border-right: none;
}
.gutter {
flex: 1 1 auto;
display: grid;
align-items: center;
padding: var(--cell-padding);
grid-template-columns: 1fr auto;
gap: var(--cell-spacing);
}
.number {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
color: var(--spectrum-global-color-gray-500);
}
.expand {
opacity: 0;
pointer-events: none;
}
.expand.visible {
opacity: 1;
pointer-events: all;
}
/* Normal column styles */
.row {
width: 0;
display: flex;
}
</style>

View File

@ -0,0 +1,25 @@
<script>
import NewRow from "./NewRow.svelte"
import { getContext } from "svelte"
import { DefaultRowHeight } from "../lib/constants"
const { rows, renderedRows, scrollTop, rowHeight } = getContext("grid")
$: top =
$renderedRows.length * $rowHeight -
($scrollTop % $rowHeight) +
DefaultRowHeight
</script>
<div class="new-row-bottom" style="top:{top}px;">
<NewRow rowIdx={$rows.length} />
</div>
<style>
.new-row-bottom {
position: absolute;
left: 0;
width: 100%;
height: 100px;
}
</style>

View File

@ -1,6 +1,6 @@
<script>
import GridCell from "../cells/GridCell.svelte"
import { getContext, onMount, tick } from "svelte"
import { getContext, onDestroy, onMount, tick } from "svelte"
import { Icon, Button } from "@budibase/bbui"
import GridScrollWrapper from "./GridScrollWrapper.svelte"
import DataCell from "../cells/DataCell.svelte"
@ -21,6 +21,7 @@
renderedColumns,
} = getContext("grid")
const rowId = "new"
let isAdding = false
let newRow = {}
let touched = false
@ -33,32 +34,37 @@
// Create row
const savedRow = await rows.actions.addRow(newRow, 0)
if (savedRow) {
// Reset state
scroll.update(state => ({
...state,
top: 0,
}))
clear()
// Select the first cell if possible
if (firstColumn) {
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
}
// Reset state
isAdding = false
scroll.set({
left: 0,
top: 0,
})
}
}
const cancel = () => {
const clear = () => {
isAdding = false
$focusedCellId = null
$hoveredRowId = null
document.removeEventListener("keydown", handleKeyPress)
}
const startAdding = async () => {
if (isAdding) {
return
}
document.addEventListener("keydown", handleKeyPress)
newRow = {}
isAdding = true
$hoveredRowId = "new"
$hoveredRowId = rowId
if (firstColumn) {
$focusedCellId = `new-${firstColumn.name}`
$focusedCellId = `${rowId}-${firstColumn.name}`
// Also focus the cell if it is a text-like cell
if (["string", "number"].includes(firstColumn.schema.type)) {
@ -83,16 +89,17 @@
return
}
if (e.key === "Escape") {
cancel()
e.preventDefault()
clear()
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
addRow()
}
}
onMount(() => subscribe("add-row-inline", startAdding))
onMount(() => {
document.addEventListener("keydown", handleKeyPress)
return () => {
document.removeEventListener("keydown", handleKeyPress)
}
onDestroy(() => {
document.removeEventListener("keydown", handleKeyPress)
})
</script>
@ -153,7 +160,7 @@
</GridScrollWrapper>
<div class="buttons" transition:fade={{ duration: 130 }}>
<Button size="M" cta on:click={addRow}>Save</Button>
<Button size="M" secondary newStyles on:click={cancel}>Cancel</Button>
<Button size="M" secondary newStyles on:click={clear}>Cancel</Button>
</div>
</div>
{/if}

View File

@ -50,14 +50,14 @@ export const deriveStores = context => {
([$focusedCellId, $rowLookupMap, $enrichedRows]) => {
const rowId = $focusedCellId?.split("-")[0]
if (rowId === "new") {
// Edge case for new row
// Edge case for new rows (top and bottom row ID components have unique IDs)
if (rowId?.startsWith("new")) {
return { _id: rowId }
} else {
// All normal rows
const index = $rowLookupMap[rowId]
return $enrichedRows[index]
}
// All normal rows
const index = $rowLookupMap[rowId]
return $enrichedRows[index]
},
null
)