Optimise virtual rendering for both columns and rows to handle infinitely large datasets
This commit is contained in:
parent
3ab0e95032
commit
4d3f669ae7
|
@ -10,8 +10,6 @@
|
||||||
import SpreadsheetHeader from "./SpreadsheetHeader.svelte"
|
import SpreadsheetHeader from "./SpreadsheetHeader.svelte"
|
||||||
import SpreadsheetBody from "./SpreadsheetBody.svelte"
|
import SpreadsheetBody from "./SpreadsheetBody.svelte"
|
||||||
import SpreadsheetCell from "./SpreadsheetCell.svelte"
|
import SpreadsheetCell from "./SpreadsheetCell.svelte"
|
||||||
import SpacerCell from "./SpacerCell.svelte"
|
|
||||||
import VerticalSpacer from "./VerticalSpacer.svelte"
|
|
||||||
import SpreadsheetRow from "./SpreadsheetRow.svelte"
|
import SpreadsheetRow from "./SpreadsheetRow.svelte"
|
||||||
|
|
||||||
export let table
|
export let table
|
||||||
|
@ -37,7 +35,7 @@
|
||||||
const tableId = writable(table?.tableId)
|
const tableId = writable(table?.tableId)
|
||||||
const changeCache = writable({})
|
const changeCache = writable({})
|
||||||
const newRows = writable([])
|
const newRows = writable([])
|
||||||
const visibleRows = writable([0, 0])
|
const visibleCells = writable({ y: [0, 0], x: [0, 0] })
|
||||||
|
|
||||||
// Build up spreadsheet context and additional stores
|
// Build up spreadsheet context and additional stores
|
||||||
const context = {
|
const context = {
|
||||||
|
@ -51,7 +49,7 @@
|
||||||
changeCache,
|
changeCache,
|
||||||
newRows,
|
newRows,
|
||||||
cellHeight,
|
cellHeight,
|
||||||
visibleRows,
|
visibleCells,
|
||||||
}
|
}
|
||||||
const { reorder, reorderPlaceholder } = createReorderStores(context)
|
const { reorder, reorderPlaceholder } = createReorderStores(context)
|
||||||
const resize = createResizeStore(context)
|
const resize = createResizeStore(context)
|
||||||
|
@ -68,7 +66,8 @@
|
||||||
$: generateColumns($fetch)
|
$: generateColumns($fetch)
|
||||||
$: rowCount = $rows.length
|
$: rowCount = $rows.length
|
||||||
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
||||||
$: updateSortedRows($fetch.rows, $newRows)
|
$: updateSortedRows($fetch, $newRows)
|
||||||
|
$: visibleRows = $rows.slice($visibleCells.y[0], $visibleCells.y[1])
|
||||||
|
|
||||||
const createFetch = datasource => {
|
const createFetch = datasource => {
|
||||||
return fetchData({
|
return fetchData({
|
||||||
|
@ -92,13 +91,16 @@
|
||||||
if (primaryDisplay) {
|
if (primaryDisplay) {
|
||||||
fields = [primaryDisplay, ...fields.filter(x => x !== primaryDisplay)]
|
fields = [primaryDisplay, ...fields.filter(x => x !== primaryDisplay)]
|
||||||
}
|
}
|
||||||
$columns = fields.map((field, idx) => ({
|
$columns = fields.map((field, idx) => {
|
||||||
name: field,
|
return {
|
||||||
width: defaultWidth,
|
idx,
|
||||||
left: 40 + idx * defaultWidth,
|
name: field,
|
||||||
schema: schema[field],
|
width: defaultWidth,
|
||||||
primaryDisplay: field === primaryDisplay,
|
left: 40 + idx * defaultWidth,
|
||||||
}))
|
schema: schema[field],
|
||||||
|
primaryDisplay: field === primaryDisplay,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,13 +159,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSortedRows = (unsortedRows, newRows) => {
|
const updateSortedRows = (unsortedRows, newRows) => {
|
||||||
let sortedRows = unsortedRows.slice()
|
let foo = unsortedRows.rows
|
||||||
sortedRows.sort((a, b) => {
|
for (let i = 0; i < 10; i++) {
|
||||||
const aIndex = newRows.indexOf(a._id)
|
foo = foo.concat(foo.map(x => ({ ...x, _id: x._id + "x" })))
|
||||||
const bIndex = newRows.indexOf(b._id)
|
}
|
||||||
return aIndex < bIndex ? -1 : 1
|
// let sortedRows = foo.slice()
|
||||||
})
|
// sortedRows.sort((a, b) => {
|
||||||
$rows = sortedRows
|
// const aIndex = newRows.indexOf(a._id)
|
||||||
|
// const bIndex = newRows.indexOf(b._id)
|
||||||
|
// return aIndex < bIndex ? -1 : 1
|
||||||
|
// })
|
||||||
|
$rows = foo.slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
// API for children to consume
|
// API for children to consume
|
||||||
|
@ -180,25 +186,18 @@
|
||||||
resize,
|
resize,
|
||||||
spreadsheetAPI,
|
spreadsheetAPI,
|
||||||
})
|
})
|
||||||
|
|
||||||
let sheetStyles = ""
|
|
||||||
let left = 40
|
|
||||||
for (let i = 0; i < 20; i++) {
|
|
||||||
sheetStyles += `--col-${i}-width:${160}px; --col-${i}-left:${left}px;`
|
|
||||||
left += 160
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={$component.styles}>
|
<div use:styleable={$component.styles}>
|
||||||
<div
|
<div
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:resize={$resize.columnIdx != null}
|
class:resize={$resize.columnIdx != null}
|
||||||
style="--cell-height:{cellHeight}px;{sheetStyles}"
|
style="--cell-height:{cellHeight}px;"
|
||||||
id="sheet-{rand}"
|
id="sheet-{rand}"
|
||||||
>
|
>
|
||||||
<SpreadsheetHeader />
|
<SpreadsheetHeader />
|
||||||
<SpreadsheetBody>
|
<SpreadsheetBody>
|
||||||
<div class="row" style="top: 0;">
|
<div class="row">
|
||||||
<!-- Field headers -->
|
<!-- Field headers -->
|
||||||
<SpreadsheetCell header label on:click={selectAll} width="40" left="0">
|
<SpreadsheetCell header label on:click={selectAll} width="40" left="0">
|
||||||
<input
|
<input
|
||||||
|
@ -213,10 +212,8 @@
|
||||||
reorderSource={$reorder.columnIdx === fieldIdx}
|
reorderSource={$reorder.columnIdx === fieldIdx}
|
||||||
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
|
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
|
||||||
on:mousedown={e => reorder.actions.startReordering(fieldIdx, e)}
|
on:mousedown={e => reorder.actions.startReordering(fieldIdx, e)}
|
||||||
id={`sheet-${rand}-header-${fieldIdx}`}
|
|
||||||
width={field.width}
|
width={field.width}
|
||||||
left={field.left}
|
left={field.left}
|
||||||
column={fieldIdx}
|
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
size="S"
|
size="S"
|
||||||
|
@ -232,12 +229,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- All real rows -->
|
<!-- All real rows -->
|
||||||
{#each $rows as row, rowIdx (row._id)}
|
{#each visibleRows as row, rowIdx (row._id)}
|
||||||
<SpreadsheetRow {row} {rowIdx} />
|
<SpreadsheetRow {row} rowIdx={rowIdx + $visibleCells.y[0]} />
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<!-- New row placeholder -->
|
<!-- New row placeholder -->
|
||||||
<div class="row" style="top:{($rows.length + 1) * cellHeight}px;">
|
<div class="row new" style="--top:{($rows.length + 1) * cellHeight}px;">
|
||||||
<SpreadsheetCell
|
<SpreadsheetCell
|
||||||
label
|
label
|
||||||
on:click={addRow}
|
on:click={addRow}
|
||||||
|
@ -295,6 +292,12 @@
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
width: 100%;
|
top: 0;
|
||||||
|
width: inherit;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.row.new {
|
||||||
|
position: absolute;
|
||||||
|
transform: translateY(var(--top));
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,57 +1,85 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import { Utils } from "@budibase/frontend-core"
|
|
||||||
|
|
||||||
const { columns, selectedCellId, rand, visibleRows, cellHeight, rows } =
|
const { columns, selectedCellId, rand, visibleCells, cellHeight, rows } =
|
||||||
getContext("spreadsheet")
|
getContext("spreadsheet")
|
||||||
|
|
||||||
|
const padding = 180
|
||||||
|
|
||||||
let ref
|
let ref
|
||||||
let height = 600
|
let width
|
||||||
let horizontallyScrolled = false
|
let height
|
||||||
|
let scrollLeft = 0
|
||||||
let scrollTop = 0
|
let scrollTop = 0
|
||||||
|
|
||||||
$: gridStyles = getGridStyles($columns)
|
$: computeVisibleCells($columns, scrollLeft, scrollTop, width, height)
|
||||||
$: computeVisibleRows(scrollTop, height)
|
$: contentHeight = ($rows.length + 2) * cellHeight + padding
|
||||||
$: contentHeight = ($rows.length + 2) * cellHeight + 180
|
$: contentWidth = computeContentWidth($columns)
|
||||||
$: contentWidth = computeWidth($columns)
|
$: horizontallyScrolled = scrollLeft > 0
|
||||||
$: console.log("new height")
|
|
||||||
|
|
||||||
const computeWidth = columns => {
|
const computeContentWidth = columns => {
|
||||||
console.log("width")
|
let total = 40 + padding
|
||||||
let width = 220
|
|
||||||
columns.forEach(col => {
|
columns.forEach(col => {
|
||||||
width += col.width
|
total += col.width
|
||||||
})
|
})
|
||||||
return width
|
return total
|
||||||
}
|
|
||||||
|
|
||||||
const getGridStyles = columns => {
|
|
||||||
console.log("grid")
|
|
||||||
const widths = columns?.map(x => x.width)
|
|
||||||
if (!widths?.length) {
|
|
||||||
return "--grid: 1fr;"
|
|
||||||
}
|
|
||||||
return `--grid: 40px ${widths.map(x => `${x}px`).join(" ")} 180px;`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the current scroll position
|
// Store the current scroll position
|
||||||
const handleScroll = e => {
|
const handleScroll = e => {
|
||||||
// Update horizontally scrolled flag
|
// Only update scroll offsets when a sizable change happens
|
||||||
horizontallyScrolled = e.target.scrollLeft > 0
|
if (Math.abs(scrollTop - e.target.scrollTop) > 10) {
|
||||||
|
scrollTop = e.target.scrollTop
|
||||||
// Only update scroll top offset when a sizable change happens
|
}
|
||||||
scrollTop = e.target.scrollTop
|
if (Math.abs(scrollLeft - e.target.scrollLeft) > 10) {
|
||||||
|
scrollLeft = e.target.scrollLeft
|
||||||
|
}
|
||||||
|
if (e.target.scrollLeft === 0) {
|
||||||
|
scrollLeft = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeVisibleRows = (scrollTop, height) => {
|
const computeVisibleCells = (
|
||||||
|
columns,
|
||||||
|
scrollLeft,
|
||||||
|
scrollTop,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
) => {
|
||||||
|
if (!columns.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute row visibility
|
||||||
const rows = Math.ceil(height / cellHeight) + 8
|
const rows = Math.ceil(height / cellHeight) + 8
|
||||||
const firstRow = Math.max(0, Math.floor(scrollTop / cellHeight) - 4)
|
const firstRow = Math.max(0, Math.floor(scrollTop / cellHeight) - 4)
|
||||||
visibleRows.set([firstRow, firstRow + rows])
|
const visibleRows = [firstRow, firstRow + rows]
|
||||||
|
|
||||||
|
// Compute column visibility
|
||||||
|
let startColIdx = 1
|
||||||
|
let rightEdge = columns[1].width
|
||||||
|
while (rightEdge < scrollLeft) {
|
||||||
|
startColIdx++
|
||||||
|
rightEdge += columns[startColIdx].width
|
||||||
|
}
|
||||||
|
let endColIdx = startColIdx + 1
|
||||||
|
let leftEdge = columns[0].width + 40 + rightEdge
|
||||||
|
while (leftEdge < width + scrollLeft) {
|
||||||
|
leftEdge += columns[endColIdx]?.width
|
||||||
|
endColIdx++
|
||||||
|
}
|
||||||
|
const visibleColumns = [Math.max(1, startColIdx - 2), endColIdx + 2]
|
||||||
|
|
||||||
|
visibleCells.set({
|
||||||
|
x: visibleColumns,
|
||||||
|
y: visibleRows,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe and record the height of the body
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
// Observe and record the height of the body
|
||||||
const observer = new ResizeObserver(entries => {
|
const observer = new ResizeObserver(entries => {
|
||||||
|
width = entries[0].contentRect.width
|
||||||
height = entries[0].contentRect.height
|
height = entries[0].contentRect.height
|
||||||
})
|
})
|
||||||
observer.observe(ref)
|
observer.observe(ref)
|
||||||
|
@ -59,6 +87,16 @@
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let sheetStyles = ""
|
||||||
|
let left = 0
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
if (i === 1) {
|
||||||
|
left += 40
|
||||||
|
}
|
||||||
|
sheetStyles += `--col-${i}-width:${160}px; --col-${i}-left:${left}px;`
|
||||||
|
left += 160
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -68,6 +106,7 @@
|
||||||
on:scroll={handleScroll}
|
on:scroll={handleScroll}
|
||||||
on:click|self={() => ($selectedCellId = null)}
|
on:click|self={() => ($selectedCellId = null)}
|
||||||
id={`sheet-${rand}-body`}
|
id={`sheet-${rand}-body`}
|
||||||
|
style={sheetStyles}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="content"
|
class="content"
|
||||||
|
@ -86,8 +125,20 @@
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
background: rgba(255, 0, 0, 0.1);
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add shadow to sticky cells when horizontally scrolled */
|
||||||
|
.horizontally-scrolled :global(.cell.sticky) {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
|
.horizontally-scrolled :global(.cell.sticky:after) {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
left: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to right, rgba(0, 0, 0, 0.08), transparent);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
export let selected = false
|
export let selected = false
|
||||||
export let reorderSource = false
|
export let reorderSource = false
|
||||||
export let reorderTarget = false
|
export let reorderTarget = false
|
||||||
export let id = null
|
export let left
|
||||||
export let column
|
export let width
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -27,8 +27,7 @@
|
||||||
on:mouseenter
|
on:mouseenter
|
||||||
on:click
|
on:click
|
||||||
on:mousedown
|
on:mousedown
|
||||||
{id}
|
style="--left: {left}px; --width:{width}px;"
|
||||||
style={`width:var(--col-${column}-width); left:var(--col-${column}-left);`}
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,6 +51,8 @@
|
||||||
background: var(--cell-background);
|
background: var(--cell-background);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transition: border-color 130ms ease-out;
|
transition: border-color 130ms ease-out;
|
||||||
|
width: var(--width);
|
||||||
|
transform: translateX(var(--left));
|
||||||
}
|
}
|
||||||
.cell.row-hovered {
|
.cell.row-hovered {
|
||||||
background: var(--cell-background-hover);
|
background: var(--cell-background-hover);
|
||||||
|
@ -88,9 +89,10 @@
|
||||||
/* Sticky styles */
|
/* Sticky styles */
|
||||||
.cell.sticky {
|
.cell.sticky {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 40px;
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
border-left-color: transparent;
|
border-left-color: transparent;
|
||||||
|
transform: none;
|
||||||
|
left: 40px;
|
||||||
}
|
}
|
||||||
.cell.sticky.selected {
|
.cell.sticky.selected {
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
@ -112,6 +114,7 @@
|
||||||
|
|
||||||
/* Label cells */
|
/* Label cells */
|
||||||
.cell.label {
|
.cell.label {
|
||||||
|
width: 40px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
|
|
@ -20,14 +20,18 @@
|
||||||
selectedRows,
|
selectedRows,
|
||||||
changeCache,
|
changeCache,
|
||||||
spreadsheetAPI,
|
spreadsheetAPI,
|
||||||
visibleRows,
|
visibleCells,
|
||||||
cellHeight,
|
cellHeight,
|
||||||
} = getContext("spreadsheet")
|
} = getContext("spreadsheet")
|
||||||
|
|
||||||
$: rowSelected = !!$selectedRows[row._id]
|
$: rowSelected = !!$selectedRows[row._id]
|
||||||
$: rowHovered = $hoveredRowId === row._id
|
$: rowHovered = $hoveredRowId === row._id
|
||||||
$: data = { ...row, ...$changeCache[row._id] }
|
$: data = { ...row, ...$changeCache[row._id] }
|
||||||
$: visible = rowIdx >= $visibleRows[0] && rowIdx <= $visibleRows[1]
|
$: visibleColumns = [
|
||||||
|
$columns[0],
|
||||||
|
...$columns.slice($visibleCells.x[0], $visibleCells.x[1]),
|
||||||
|
]
|
||||||
|
$: containsSelectedCell = $selectedCellId?.split("-")[0] === row._id
|
||||||
|
|
||||||
const getCellForField = field => {
|
const getCellForField = field => {
|
||||||
const type = field.schema.type
|
const type = field.schema.type
|
||||||
|
@ -53,64 +57,66 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if visible}
|
<div
|
||||||
<div class="row" style="--top:{(rowIdx + 1) * cellHeight}px;">
|
class="row"
|
||||||
|
style="--top:{(rowIdx + 1) * cellHeight}px;"
|
||||||
|
class:contains-selected-cell={containsSelectedCell}
|
||||||
|
>
|
||||||
|
<SpreadsheetCell
|
||||||
|
label
|
||||||
|
{rowSelected}
|
||||||
|
{rowHovered}
|
||||||
|
on:mouseenter={() => ($hoveredRowId = row._id)}
|
||||||
|
on:click={() => selectRow(row._id)}
|
||||||
|
>
|
||||||
|
{#if rowSelected || rowHovered}
|
||||||
|
<input type="checkbox" checked={rowSelected} />
|
||||||
|
{:else}
|
||||||
|
<span>
|
||||||
|
{rowIdx + 1}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</SpreadsheetCell>
|
||||||
|
{#each visibleColumns as column (column.name)}
|
||||||
|
{@const cellIdx = `${row._id}-${column.name}`}
|
||||||
<SpreadsheetCell
|
<SpreadsheetCell
|
||||||
label
|
|
||||||
{rowSelected}
|
{rowSelected}
|
||||||
{rowHovered}
|
{rowHovered}
|
||||||
|
sticky={column.idx === 0}
|
||||||
|
selected={$selectedCellId === cellIdx}
|
||||||
|
reorderSource={$reorder.columnIdx === column.idx}
|
||||||
|
reorderTarget={$reorder.swapColumnIdx === column.idx}
|
||||||
on:mouseenter={() => ($hoveredRowId = row._id)}
|
on:mouseenter={() => ($hoveredRowId = row._id)}
|
||||||
on:click={() => selectRow(row._id)}
|
on:click={() => ($selectedCellId = cellIdx)}
|
||||||
width="40"
|
width={column.width}
|
||||||
left="0"
|
left={column.left}
|
||||||
|
column={column.idx}
|
||||||
>
|
>
|
||||||
{#if rowSelected || rowHovered}
|
<svelte:component
|
||||||
<input type="checkbox" checked={rowSelected} />
|
this={getCellForField(column)}
|
||||||
{:else}
|
value={data[column.name]}
|
||||||
<span>
|
schema={column.schema}
|
||||||
{rowIdx + 1}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</SpreadsheetCell>
|
|
||||||
{#each $columns as field, fieldIdx}
|
|
||||||
{@const cellIdx = `${row._id}-${field.name}`}
|
|
||||||
<SpreadsheetCell
|
|
||||||
{rowSelected}
|
|
||||||
{rowHovered}
|
|
||||||
sticky={fieldIdx === 0}
|
|
||||||
selected={$selectedCellId === cellIdx}
|
selected={$selectedCellId === cellIdx}
|
||||||
reorderSource={$reorder.columnIdx === fieldIdx}
|
onChange={val => spreadsheetAPI.updateValue(row._id, column, val)}
|
||||||
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
|
readonly={column.schema.autocolumn}
|
||||||
on:mouseenter={() => ($hoveredRowId = row._id)}
|
/>
|
||||||
on:click={() => ($selectedCellId = cellIdx)}
|
</SpreadsheetCell>
|
||||||
width={field.width}
|
{/each}
|
||||||
left={field.left}
|
</div>
|
||||||
column={fieldIdx}
|
|
||||||
>
|
|
||||||
<svelte:component
|
|
||||||
this={getCellForField(field)}
|
|
||||||
value={data[field.name]}
|
|
||||||
schema={field.schema}
|
|
||||||
selected={$selectedCellId === cellIdx}
|
|
||||||
onChange={val => spreadsheetAPI.updateValue(row._id, field, val)}
|
|
||||||
readonly={field.schema.autocolumn}
|
|
||||||
/>
|
|
||||||
</SpreadsheetCell>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.row-placeholder {
|
|
||||||
height: var(--cell-height);
|
|
||||||
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
|
||||||
background: var(--cell-background);
|
|
||||||
grid-column: 1/-1;
|
|
||||||
}
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--top);
|
top: 0;
|
||||||
width: 100%;
|
transform: translateY(var(--top));
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
|
.row.contains-selected-cell {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row :global(>:last-child) {
|
||||||
|
border-right-width: 1px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -27,17 +27,16 @@ export const createReorderStores = context => {
|
||||||
let breakpoints = []
|
let breakpoints = []
|
||||||
const cols = get(columns)
|
const cols = get(columns)
|
||||||
cols.forEach((col, idx) => {
|
cols.forEach((col, idx) => {
|
||||||
const header = document.getElementById(`sheet-${rand}-header-${idx}`)
|
breakpoints.push(col.left)
|
||||||
const bounds = header.getBoundingClientRect()
|
|
||||||
breakpoints.push(bounds.x)
|
|
||||||
if (idx === cols.length - 1) {
|
if (idx === cols.length - 1) {
|
||||||
breakpoints.push(bounds.x + bounds.width)
|
breakpoints.push(col.left + col.width)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
console.log(breakpoints, e.clientX)
|
||||||
|
|
||||||
// Get bounds of the selected header and sheet body
|
// Get bounds of the selected header and sheet body
|
||||||
const self = document.getElementById(`sheet-${rand}-header-${columnIdx}`)
|
|
||||||
const selfBounds = self.getBoundingClientRect()
|
const self = cols[columnIdx]
|
||||||
const body = document.getElementById(`sheet-${rand}-body`)
|
const body = document.getElementById(`sheet-${rand}-body`)
|
||||||
const bodyBounds = body.getBoundingClientRect()
|
const bodyBounds = body.getBoundingClientRect()
|
||||||
|
|
||||||
|
@ -49,9 +48,9 @@ export const createReorderStores = context => {
|
||||||
initialMouseX: e.clientX,
|
initialMouseX: e.clientX,
|
||||||
})
|
})
|
||||||
placeholder.set({
|
placeholder.set({
|
||||||
initialX: selfBounds.x - bodyBounds.x,
|
initialX: self.left - bodyBounds.x,
|
||||||
x: selfBounds.x - bodyBounds.x,
|
x: self.left - bodyBounds.x,
|
||||||
width: selfBounds.width,
|
width: self.width,
|
||||||
height: (get(rows).length + 2) * 32,
|
height: (get(rows).length + 2) * 32,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -103,13 +102,15 @@ export const createReorderStores = context => {
|
||||||
const stopReordering = () => {
|
const stopReordering = () => {
|
||||||
// Swap position of columns
|
// Swap position of columns
|
||||||
let { columnIdx, swapColumnIdx } = get(reorder)
|
let { columnIdx, swapColumnIdx } = get(reorder)
|
||||||
const newColumns = get(columns).slice()
|
columns.update(state => {
|
||||||
const removed = newColumns.splice(columnIdx, 1)
|
const removed = state.splice(columnIdx, 1)
|
||||||
if (--swapColumnIdx < columnIdx) {
|
if (--swapColumnIdx < columnIdx) {
|
||||||
swapColumnIdx++
|
swapColumnIdx++
|
||||||
}
|
}
|
||||||
newColumns.splice(swapColumnIdx, 0, removed[0])
|
state.splice(swapColumnIdx, 0, removed[0])
|
||||||
columns.set(newColumns)
|
state = state.map((col, idx) => ({ ...col, idx }))
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
reorder.set(reorderInitialState)
|
reorder.set(reorderInitialState)
|
||||||
|
|
|
@ -23,17 +23,16 @@ export const createResizeStore = context => {
|
||||||
const resize = writable(initialState)
|
const resize = writable(initialState)
|
||||||
|
|
||||||
const startResizing = (idx, e) => {
|
const startResizing = (idx, e) => {
|
||||||
|
const $columns = get(columns)
|
||||||
// Prevent propagation to stop reordering triggering
|
// Prevent propagation to stop reordering triggering
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
sheet = document.getElementById(`sheet-${rand}`)
|
width = $columns[idx].width
|
||||||
styles = getComputedStyle(sheet)
|
left = $columns[idx].left
|
||||||
width = parseInt(styles.getPropertyValue(`--col-${idx}-width`))
|
|
||||||
left = parseInt(styles.getPropertyValue(`--col-${idx}-left`))
|
|
||||||
initialWidth = width
|
initialWidth = width
|
||||||
initialMouseX = e.clientX
|
initialMouseX = e.clientX
|
||||||
columnIdx = idx
|
columnIdx = idx
|
||||||
columnCount = get(columns).length
|
columnCount = $columns.length
|
||||||
|
|
||||||
// Add mouse event listeners to handle resizing
|
// Add mouse event listeners to handle resizing
|
||||||
document.addEventListener("mousemove", onResizeMouseMove)
|
document.addEventListener("mousemove", onResizeMouseMove)
|
||||||
|
@ -48,17 +47,54 @@ export const createResizeStore = context => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
columns.update(state => {
|
||||||
|
state[columnIdx].width = newWidth
|
||||||
|
let offset = state[columnIdx].left + newWidth
|
||||||
|
for (let i = columnIdx + 1; i < state.length; i++) {
|
||||||
|
state[i].left = offset
|
||||||
|
offset += state[i].width
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
|
||||||
|
// let newStyle = `--col-${columnIdx}-width:${newWidth}px;`
|
||||||
|
//
|
||||||
|
// let offset = left + newWidth
|
||||||
|
// for (let i = columnIdx + 1; i < columnCount; i++) {
|
||||||
|
// const colWidth = parseInt(styles.getPropertyValue(`--col-${i}-width`))
|
||||||
|
// newStyle += `--col-${i}-left:${offset}px;`
|
||||||
|
// offset += colWidth
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// sheet.style.cssText += newStyle
|
||||||
|
|
||||||
|
|
||||||
|
// let cells = sheet.querySelectorAll(`[data-col="${columnIdx}"]`)
|
||||||
|
// let left
|
||||||
|
// cells.forEach(cell => {
|
||||||
|
// cell.style.width = `${newWidth}px`
|
||||||
|
// cell.dataset.width = newWidth
|
||||||
|
// if (!left) {
|
||||||
|
// left = parseInt(cell.dataset.left)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// let offset = left + newWidth
|
||||||
|
// for (let i = columnIdx + 1; i < columnCount; i++) {
|
||||||
|
// cells = sheet.querySelectorAll(`[data-col="${i}"]`)
|
||||||
|
// let colWidth
|
||||||
|
// cells.forEach(cell => {
|
||||||
|
// cell.style.transform = `translateX(${offset}px)`
|
||||||
|
// cell.dataset.left = offset
|
||||||
|
// if (!colWidth) {
|
||||||
|
// colWidth = parseInt(cell.dataset.width)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// offset += colWidth
|
||||||
|
// }
|
||||||
|
|
||||||
let newStyle = `--col-${columnIdx}-width:${newWidth}px;`
|
|
||||||
|
|
||||||
let offset = left + newWidth
|
|
||||||
for (let i = columnIdx + 1; i < columnCount; i++) {
|
|
||||||
const colWidth = 160//parseInt(styles.getPropertyValue(`--col-${i}-width`))
|
|
||||||
newStyle += `--col-${i}-left:${offset}px;`
|
|
||||||
offset += colWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
sheet.style.cssText += newStyle
|
|
||||||
width = newWidth
|
width = newWidth
|
||||||
|
|
||||||
// Update width of column
|
// Update width of column
|
||||||
|
|
Loading…
Reference in New Issue