Test absolute positioning

This commit is contained in:
Andrew Kingston 2023-02-24 09:46:44 +00:00
parent b8e7e0b701
commit 3ab0e95032
5 changed files with 185 additions and 121 deletions

View File

@ -92,9 +92,10 @@
if (primaryDisplay) {
fields = [primaryDisplay, ...fields.filter(x => x !== primaryDisplay)]
}
$columns = fields.map(field => ({
$columns = fields.map((field, idx) => ({
name: field,
width: defaultWidth,
left: 40 + idx * defaultWidth,
schema: schema[field],
primaryDisplay: field === primaryDisplay,
}))
@ -179,47 +180,56 @@
resize,
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>
<div use:styleable={$component.styles}>
<div
class="wrapper"
class:resize={$resize.columnIdx != null}
style="--cell-height:{cellHeight}px;"
style="--cell-height:{cellHeight}px;{sheetStyles}"
id="sheet-{rand}"
>
<SpreadsheetHeader />
<SpreadsheetBody>
<!-- Field headers -->
<SpreadsheetCell header label on:click={selectAll}>
<input
type="checkbox"
checked={rowCount && selectedRowCount === rowCount}
/>
</SpreadsheetCell>
{#each $columns as field, fieldIdx}
<SpreadsheetCell
header
sticky={fieldIdx === 0}
reorderSource={$reorder.columnIdx === fieldIdx}
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
on:mousedown={e => reorder.actions.startReordering(fieldIdx, e)}
id={`sheet-${rand}-header-${fieldIdx}`}
>
<Icon
size="S"
name={getIconForField(field)}
color="var(--spectrum-global-color-gray-600)"
<div class="row" style="top: 0;">
<!-- Field headers -->
<SpreadsheetCell header label on:click={selectAll} width="40" left="0">
<input
type="checkbox"
checked={rowCount && selectedRowCount === rowCount}
/>
<span>
{field.name}
</span>
<ResizeSlider columnIdx={fieldIdx} />
</SpreadsheetCell>
{/each}
<SpacerCell
header
reorderTarget={$reorder.swapColumnIdx === $columns.length}
/>
{#each $columns as field, fieldIdx}
<SpreadsheetCell
header
sticky={fieldIdx === 0}
reorderSource={$reorder.columnIdx === fieldIdx}
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
on:mousedown={e => reorder.actions.startReordering(fieldIdx, e)}
id={`sheet-${rand}-header-${fieldIdx}`}
width={field.width}
left={field.left}
column={fieldIdx}
>
<Icon
size="S"
name={getIconForField(field)}
color="var(--spectrum-global-color-gray-600)"
/>
<span>
{field.name}
</span>
<ResizeSlider columnIdx={fieldIdx} />
</SpreadsheetCell>
{/each}
</div>
<!-- All real rows -->
{#each $rows as row, rowIdx (row._id)}
@ -227,28 +237,30 @@
{/each}
<!-- New row placeholder -->
<SpreadsheetCell
label
on:click={addRow}
on:mouseenter={() => ($hoveredRowId = "new")}
rowHovered={$hoveredRowId === "new"}
>
<Icon hoverable name="Add" size="S" />
</SpreadsheetCell>
{#each $columns as field, fieldIdx}
<div class="row" style="top:{($rows.length + 1) * cellHeight}px;">
<SpreadsheetCell
sticky={fieldIdx === 0}
rowHovered={$hoveredRowId === "new"}
reorderSource={$reorder.columnIdx === fieldIdx}
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
on:click={() => addRow(field)}
label
on:click={addRow}
on:mouseenter={() => ($hoveredRowId = "new")}
/>
{/each}
<SpacerCell reorderTarget={$reorder.swapColumnIdx === $columns.length} />
<!-- Vertical spacer to pad bottom of sheet -->
<VerticalSpacer />
rowHovered={$hoveredRowId === "new"}
width="40"
left="0"
>
<Icon hoverable name="Add" size="S" />
</SpreadsheetCell>
{#each $columns as field, fieldIdx}
<SpreadsheetCell
sticky={fieldIdx === 0}
rowHovered={$hoveredRowId === "new"}
reorderSource={$reorder.columnIdx === fieldIdx}
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
on:click={() => addRow(field)}
on:mouseenter={() => ($hoveredRowId = "new")}
width={field.width}
left={field.left}
/>
{/each}
</div>
</SpreadsheetBody>
<!-- Placeholder overlay for new column position -->
@ -279,4 +291,10 @@
.wrapper::-webkit-scrollbar-track {
background: var(--cell-background);
}
.row {
display: flex;
position: sticky;
width: 100%;
}
</style>

View File

@ -2,18 +2,31 @@
import { getContext, onMount } from "svelte"
import { Utils } from "@budibase/frontend-core"
const { columns, selectedCellId, rand, visibleRows, cellHeight } =
const { columns, selectedCellId, rand, visibleRows, cellHeight, rows } =
getContext("spreadsheet")
let ref
let height = 0
let height = 600
let horizontallyScrolled = false
let scrollTop = 0
$: gridStyles = getGridStyles($columns)
$: computeVisibleRows(scrollTop, height)
$: contentHeight = ($rows.length + 2) * cellHeight + 180
$: contentWidth = computeWidth($columns)
$: console.log("new height")
const computeWidth = columns => {
console.log("width")
let width = 220
columns.forEach(col => {
width += col.width
})
return width
}
const getGridStyles = columns => {
console.log("grid")
const widths = columns?.map(x => x.width)
if (!widths?.length) {
return "--grid: 1fr;"
@ -30,11 +43,11 @@
scrollTop = e.target.scrollTop
}
const computeVisibleRows = Utils.debounce((scrollTop, height) => {
const rows = Math.ceil(height / cellHeight) + 16
const firstRow = Math.max(0, Math.floor(scrollTop / cellHeight) - 8)
const computeVisibleRows = (scrollTop, height) => {
const rows = Math.ceil(height / cellHeight) + 8
const firstRow = Math.max(0, Math.floor(scrollTop / cellHeight) - 4)
visibleRows.set([firstRow, firstRow + rows])
}, 50)
}
// Observe and record the height of the body
onMount(() => {
@ -53,35 +66,28 @@
class="spreadsheet"
class:horizontally-scrolled={horizontallyScrolled}
on:scroll={handleScroll}
style={gridStyles}
on:click|self={() => ($selectedCellId = null)}
id={`sheet-${rand}-body`}
>
<slot />
<div
class="content"
style="height:{contentHeight}px; width:{contentWidth}px;"
>
<slot />
</div>
</div>
<style>
.spreadsheet {
display: grid;
grid-template-columns: var(--grid);
justify-content: flex-start;
align-items: stretch;
display: block;
overflow: auto;
max-height: 600px;
height: 800px;
position: relative;
cursor: default;
}
.content {
background: rgba(255, 0, 0, 0.1);
}
/* 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>

View File

@ -9,6 +9,7 @@
export let reorderSource = false
export let reorderTarget = false
export let id = null
export let column
</script>
<div
@ -27,6 +28,7 @@
on:click
on:mousedown
{id}
style={`width:var(--col-${column}-width); left:var(--col-${column}-left);`}
>
<slot />
</div>
@ -48,7 +50,7 @@
font-size: var(--cell-font-size);
gap: var(--cell-spacing);
background: var(--cell-background);
position: relative;
position: absolute;
transition: border-color 130ms ease-out;
}
.cell.row-hovered {
@ -71,8 +73,6 @@
/* Header cells */
.cell.header {
background: var(--spectrum-global-color-gray-200);
position: sticky;
top: 0;
padding: 0 var(--cell-padding);
z-index: 3;
border-color: var(--spectrum-global-color-gray-400);

View File

@ -21,6 +21,7 @@
changeCache,
spreadsheetAPI,
visibleRows,
cellHeight,
} = getContext("spreadsheet")
$: rowSelected = !!$selectedRows[row._id]
@ -52,27 +53,27 @@
}
</script>
{#if !visible}
<div class="row-placeholder" />
{:else}
<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 $columns as field, fieldIdx}
{@const cellIdx = `${row._id}-${field.name}`}
{#key cellIdx}
{#if visible}
<div class="row" style="--top:{(rowIdx + 1) * cellHeight}px;">
<SpreadsheetCell
label
{rowSelected}
{rowHovered}
on:mouseenter={() => ($hoveredRowId = row._id)}
on:click={() => selectRow(row._id)}
width="40"
left="0"
>
{#if rowSelected || rowHovered}
<input type="checkbox" checked={rowSelected} />
{:else}
<span>
{rowIdx + 1}
</span>
{/if}
</SpreadsheetCell>
{#each $columns as field, fieldIdx}
{@const cellIdx = `${row._id}-${field.name}`}
<SpreadsheetCell
{rowSelected}
{rowHovered}
@ -82,6 +83,9 @@
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
on:mouseenter={() => ($hoveredRowId = row._id)}
on:click={() => ($selectedCellId = cellIdx)}
width={field.width}
left={field.left}
column={fieldIdx}
>
<svelte:component
this={getCellForField(field)}
@ -92,9 +96,8 @@
readonly={field.schema.autocolumn}
/>
</SpreadsheetCell>
{/key}
{/each}
<SpacerCell reorderTarget={$reorder.swapColumnIdx === $columns.length} />
{/each}
</div>
{/if}
<style>
@ -104,4 +107,10 @@
background: var(--cell-background);
grid-column: 1/-1;
}
.row {
display: flex;
position: absolute;
top: var(--top);
width: 100%;
}
</style>

View File

@ -1,26 +1,39 @@
import { writable, get } from "svelte/store"
import { domDebounce } from "../../../../utils/domDebounce.js"
const MinColumnWidth = 100
export const createResizeStore = context => {
const { columns } = context
const { columns, rand } = context
const initialState = {
initialMouseX: null,
initialWidth: null,
columnIdx: null,
}
let initialWidth = 0
let width = 0
let left = 0
let initialMouseX = null
let sheet
let columnIdx = 0
let columnCount = 0
let styles
const resize = writable(initialState)
const startResizing = (columnIdx, e) => {
const startResizing = (idx, e) => {
// Prevent propagation to stop reordering triggering
e.stopPropagation()
// Update state
resize.set({
columnIdx,
initialWidth: get(columns)[columnIdx].width,
initialMouseX: e.clientX,
})
sheet = document.getElementById(`sheet-${rand}`)
styles = getComputedStyle(sheet)
width = parseInt(styles.getPropertyValue(`--col-${idx}-width`))
left = parseInt(styles.getPropertyValue(`--col-${idx}-left`))
initialWidth = width
initialMouseX = e.clientX
columnIdx = idx
columnCount = get(columns).length
// Add mouse event listeners to handle resizing
document.addEventListener("mousemove", onResizeMouseMove)
@ -28,21 +41,39 @@ export const createResizeStore = context => {
}
const onResizeMouseMove = e => {
const $resize = get(resize)
const dx = e.clientX - $resize.initialMouseX
const width = get(columns)[$resize.columnIdx].width
const newWidth = Math.max(MinColumnWidth, $resize.initialWidth + dx)
const dx = e.clientX - initialMouseX
const newWidth = Math.round(Math.max(MinColumnWidth, initialWidth + dx))
// Skip small updates
if (Math.abs(width - newWidth) < 20) {
if (Math.abs(width - newWidth) < 10) {
return
}
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
// Update width of column
columns.update(state => {
state[$resize.columnIdx].width = newWidth
return state
})
// columns.update(state => {
// state[$resize.columnIdx].width = Math.round(newWidth)
//
// // Update left offset of other columns
// let offset = 40
// state.forEach(col => {
// col.left = offset
// offset += col.width
// })
//
// return state
// })
}
const stopResizing = () => {