Improve new row top component and update new row bottom component
This commit is contained in:
parent
9361c91ad4
commit
61f05492ad
|
@ -29,6 +29,7 @@
|
||||||
GutterWidth,
|
GutterWidth,
|
||||||
DefaultRowHeight,
|
DefaultRowHeight,
|
||||||
} from "../lib/constants"
|
} from "../lib/constants"
|
||||||
|
import NewRowBottom from "./NewRowBottom.svelte"
|
||||||
|
|
||||||
export let API = null
|
export let API = null
|
||||||
export let tableId = null
|
export let tableId = null
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import GridCell from "../cells/GridCell.svelte"
|
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 { Icon, Button } from "@budibase/bbui"
|
||||||
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
import DataCell from "../cells/DataCell.svelte"
|
import DataCell from "../cells/DataCell.svelte"
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
renderedColumns,
|
renderedColumns,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
|
const rowId = "new"
|
||||||
let isAdding = false
|
let isAdding = false
|
||||||
let newRow = {}
|
let newRow = {}
|
||||||
let touched = false
|
let touched = false
|
||||||
|
@ -33,32 +34,37 @@
|
||||||
// Create row
|
// Create row
|
||||||
const savedRow = await rows.actions.addRow(newRow, 0)
|
const savedRow = await rows.actions.addRow(newRow, 0)
|
||||||
if (savedRow) {
|
if (savedRow) {
|
||||||
|
// Reset state
|
||||||
|
scroll.update(state => ({
|
||||||
|
...state,
|
||||||
|
top: 0,
|
||||||
|
}))
|
||||||
|
clear()
|
||||||
|
|
||||||
// Select the first cell if possible
|
// Select the first cell if possible
|
||||||
if (firstColumn) {
|
if (firstColumn) {
|
||||||
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
|
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset state
|
|
||||||
isAdding = false
|
|
||||||
scroll.set({
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancel = () => {
|
const clear = () => {
|
||||||
isAdding = false
|
isAdding = false
|
||||||
$focusedCellId = null
|
$focusedCellId = null
|
||||||
$hoveredRowId = null
|
$hoveredRowId = null
|
||||||
|
document.removeEventListener("keydown", handleKeyPress)
|
||||||
}
|
}
|
||||||
|
|
||||||
const startAdding = async () => {
|
const startAdding = async () => {
|
||||||
|
if (isAdding) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
document.addEventListener("keydown", handleKeyPress)
|
||||||
newRow = {}
|
newRow = {}
|
||||||
isAdding = true
|
isAdding = true
|
||||||
$hoveredRowId = "new"
|
$hoveredRowId = rowId
|
||||||
if (firstColumn) {
|
if (firstColumn) {
|
||||||
$focusedCellId = `new-${firstColumn.name}`
|
$focusedCellId = `${rowId}-${firstColumn.name}`
|
||||||
|
|
||||||
// Also focus the cell if it is a text-like cell
|
// Also focus the cell if it is a text-like cell
|
||||||
if (["string", "number"].includes(firstColumn.schema.type)) {
|
if (["string", "number"].includes(firstColumn.schema.type)) {
|
||||||
|
@ -83,16 +89,17 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (e.key === "Escape") {
|
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(() => subscribe("add-row-inline", startAdding))
|
||||||
onMount(() => {
|
onDestroy(() => {
|
||||||
document.addEventListener("keydown", handleKeyPress)
|
document.removeEventListener("keydown", handleKeyPress)
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", handleKeyPress)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -153,7 +160,7 @@
|
||||||
</GridScrollWrapper>
|
</GridScrollWrapper>
|
||||||
<div class="buttons" transition:fade={{ duration: 130 }}>
|
<div class="buttons" transition:fade={{ duration: 130 }}>
|
||||||
<Button size="M" cta on:click={addRow}>Save</Button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -50,14 +50,14 @@ export const deriveStores = context => {
|
||||||
([$focusedCellId, $rowLookupMap, $enrichedRows]) => {
|
([$focusedCellId, $rowLookupMap, $enrichedRows]) => {
|
||||||
const rowId = $focusedCellId?.split("-")[0]
|
const rowId = $focusedCellId?.split("-")[0]
|
||||||
|
|
||||||
if (rowId === "new") {
|
// Edge case for new rows (top and bottom row ID components have unique IDs)
|
||||||
// Edge case for new row
|
if (rowId?.startsWith("new")) {
|
||||||
return { _id: rowId }
|
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
|
null
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue