Merge pull request #13510 from Budibase/grid-enhancements
Table enhancements
This commit is contained in:
commit
8a72be5fab
|
@ -43,6 +43,9 @@
|
|||
on:mouseup
|
||||
on:click
|
||||
on:contextmenu
|
||||
on:touchstart
|
||||
on:touchend
|
||||
on:touchcancel
|
||||
{style}
|
||||
>
|
||||
{#if error}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
export let column
|
||||
export let idx
|
||||
export let orderable = true
|
||||
|
||||
const {
|
||||
reorder,
|
||||
|
@ -66,6 +65,7 @@
|
|||
$: resetSearchValue(column.name)
|
||||
$: searching = searchValue != null
|
||||
$: debouncedUpdateFilter(searchValue)
|
||||
$: orderable = !column.primaryDisplay
|
||||
|
||||
const getSortingLabels = type => {
|
||||
switch (type) {
|
||||
|
@ -112,16 +112,17 @@
|
|||
}
|
||||
|
||||
const onMouseDown = e => {
|
||||
if (e.button === 0 && orderable) {
|
||||
if ((e.touches?.length || e.button === 0) && orderable) {
|
||||
timeout = setTimeout(() => {
|
||||
reorder.actions.startReordering(column.name, e)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseUp = e => {
|
||||
if (e.button === 0 && orderable) {
|
||||
const onMouseUp = () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,6 +259,9 @@
|
|||
<GridCell
|
||||
on:mousedown={onMouseDown}
|
||||
on:mouseup={onMouseUp}
|
||||
on:touchstart={onMouseDown}
|
||||
on:touchend={onMouseUp}
|
||||
on:touchcancel={onMouseUp}
|
||||
on:contextmenu={onContextMenu}
|
||||
width={column.width}
|
||||
left={column.left}
|
||||
|
@ -347,7 +351,8 @@
|
|||
<MenuItem
|
||||
icon="Label"
|
||||
on:click={makeDisplayColumn}
|
||||
disabled={idx === "sticky" || !canBeDisplayColumn(column.schema.type)}
|
||||
disabled={column.primaryDisplay ||
|
||||
!canBeDisplayColumn(column.schema.type)}
|
||||
>
|
||||
Use as display column
|
||||
</MenuItem>
|
||||
|
@ -378,7 +383,7 @@
|
|||
Move right
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
disabled={idx === "sticky" || !$config.showControls}
|
||||
disabled={column.primaryDisplay || !$config.showControls}
|
||||
icon="VisibilityOff"
|
||||
on:click={hideColumn}
|
||||
>
|
||||
|
|
|
@ -81,6 +81,7 @@
|
|||
size="S"
|
||||
value={column.visible}
|
||||
on:change={e => toggleVisibility(column, e.detail)}
|
||||
disabled={column.primaryDisplay}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
refreshing,
|
||||
config,
|
||||
filter,
|
||||
inlineFilters,
|
||||
columnRenderMap,
|
||||
} = getContext("grid")
|
||||
|
||||
|
@ -157,7 +158,11 @@
|
|||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<TempTooltip
|
||||
text="Click here to create your first row"
|
||||
condition={hasNoRows && $loaded && !$filter?.length && !$refreshing}
|
||||
condition={hasNoRows &&
|
||||
$loaded &&
|
||||
!$filter?.length &&
|
||||
!$inlineFilters?.length &&
|
||||
!$refreshing}
|
||||
type={TooltipType.Info}
|
||||
>
|
||||
{#if !visible && !selectedRowCount && $config.canAddRows}
|
||||
|
|
|
@ -20,3 +20,10 @@ export const getColumnIcon = column => {
|
|||
|
||||
return result || "Text"
|
||||
}
|
||||
|
||||
export const parseEventLocation = e => {
|
||||
return {
|
||||
x: e.clientX ?? e.touches?.[0]?.clientX,
|
||||
y: e.clientY ?? e.touches?.[0]?.clientY,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
class="resize-slider"
|
||||
class:visible={activeColumn === $stickyColumn.name}
|
||||
on:mousedown={e => resize.actions.startResizing($stickyColumn, e)}
|
||||
on:touchstart={e => resize.actions.startResizing($stickyColumn, e)}
|
||||
on:dblclick={() => resize.actions.resetSize($stickyColumn)}
|
||||
style="left:{GutterWidth + $stickyColumn.width}px;"
|
||||
>
|
||||
|
@ -32,6 +33,7 @@
|
|||
class="resize-slider"
|
||||
class:visible={activeColumn === column.name}
|
||||
on:mousedown={e => resize.actions.startResizing(column, e)}
|
||||
on:touchstart={e => resize.actions.startResizing(column, e)}
|
||||
on:dblclick={() => resize.actions.resetSize(column)}
|
||||
style={getStyle(column, offset, $scrollLeft)}
|
||||
>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { getContext } from "svelte"
|
||||
import { domDebounce } from "../../../utils/utils"
|
||||
import { DefaultRowHeight, ScrollBarSize } from "../lib/constants"
|
||||
import { parseEventLocation } from "../lib/utils"
|
||||
|
||||
const {
|
||||
scroll,
|
||||
|
@ -53,17 +54,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
const getLocation = e => {
|
||||
return {
|
||||
y: e.touches?.[0]?.clientY ?? e.clientY,
|
||||
x: e.touches?.[0]?.clientX ?? e.clientX,
|
||||
}
|
||||
}
|
||||
|
||||
// V scrollbar drag handlers
|
||||
const startVDragging = e => {
|
||||
e.preventDefault()
|
||||
initialMouse = getLocation(e).y
|
||||
initialMouse = parseEventLocation(e).y
|
||||
initialScroll = $scrollTop
|
||||
document.addEventListener("mousemove", moveVDragging)
|
||||
document.addEventListener("touchmove", moveVDragging)
|
||||
|
@ -73,7 +67,7 @@
|
|||
closeMenu()
|
||||
}
|
||||
const moveVDragging = domDebounce(e => {
|
||||
const delta = getLocation(e).y - initialMouse
|
||||
const delta = parseEventLocation(e).y - initialMouse
|
||||
const weight = delta / availHeight
|
||||
const newScrollTop = initialScroll + weight * $maxScrollTop
|
||||
scroll.update(state => ({
|
||||
|
@ -92,7 +86,7 @@
|
|||
// H scrollbar drag handlers
|
||||
const startHDragging = e => {
|
||||
e.preventDefault()
|
||||
initialMouse = getLocation(e).x
|
||||
initialMouse = parseEventLocation(e).x
|
||||
initialScroll = $scrollLeft
|
||||
document.addEventListener("mousemove", moveHDragging)
|
||||
document.addEventListener("touchmove", moveHDragging)
|
||||
|
@ -102,7 +96,7 @@
|
|||
closeMenu()
|
||||
}
|
||||
const moveHDragging = domDebounce(e => {
|
||||
const delta = getLocation(e).x - initialMouse
|
||||
const delta = parseEventLocation(e).x - initialMouse
|
||||
const weight = delta / availWidth
|
||||
const newScrollLeft = initialScroll + weight * $maxScrollLeft
|
||||
scroll.update(state => ({
|
||||
|
|
|
@ -48,22 +48,28 @@ export const createStores = () => {
|
|||
export const deriveStores = context => {
|
||||
const { columns, stickyColumn } = context
|
||||
|
||||
// Derive if we have any normal columns
|
||||
const hasNonAutoColumn = derived(
|
||||
// Quick access to all columns
|
||||
const allColumns = derived(
|
||||
[columns, stickyColumn],
|
||||
([$columns, $stickyColumn]) => {
|
||||
let allCols = $columns || []
|
||||
if ($stickyColumn) {
|
||||
allCols = [...allCols, $stickyColumn]
|
||||
}
|
||||
const normalCols = allCols.filter(column => {
|
||||
return !column.schema?.autocolumn
|
||||
})
|
||||
return normalCols.length > 0
|
||||
return allCols
|
||||
}
|
||||
)
|
||||
|
||||
// Derive if we have any normal columns
|
||||
const hasNonAutoColumn = derived(allColumns, $allColumns => {
|
||||
const normalCols = $allColumns.filter(column => {
|
||||
return !column.schema?.autocolumn
|
||||
})
|
||||
return normalCols.length > 0
|
||||
})
|
||||
|
||||
return {
|
||||
allColumns,
|
||||
hasNonAutoColumn,
|
||||
}
|
||||
}
|
||||
|
@ -142,24 +148,26 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
export const initialise = context => {
|
||||
const { definition, columns, stickyColumn, enrichedSchema } = context
|
||||
const {
|
||||
definition,
|
||||
columns,
|
||||
stickyColumn,
|
||||
allColumns,
|
||||
enrichedSchema,
|
||||
compact,
|
||||
} = context
|
||||
|
||||
// Merge new schema fields with existing schema in order to preserve widths
|
||||
enrichedSchema.subscribe($enrichedSchema => {
|
||||
const processColumns = $enrichedSchema => {
|
||||
if (!$enrichedSchema) {
|
||||
columns.set([])
|
||||
stickyColumn.set(null)
|
||||
return
|
||||
}
|
||||
const $definition = get(definition)
|
||||
const $columns = get(columns)
|
||||
const $allColumns = get(allColumns)
|
||||
const $stickyColumn = get(stickyColumn)
|
||||
|
||||
// Generate array of all columns to easily find pre-existing columns
|
||||
let allColumns = $columns || []
|
||||
if ($stickyColumn) {
|
||||
allColumns.push($stickyColumn)
|
||||
}
|
||||
const $compact = get(compact)
|
||||
|
||||
// Find primary display
|
||||
let primaryDisplay
|
||||
|
@ -171,7 +179,7 @@ export const initialise = context => {
|
|||
// Get field list
|
||||
let fields = []
|
||||
Object.keys($enrichedSchema).forEach(field => {
|
||||
if (field !== primaryDisplay) {
|
||||
if ($compact || field !== primaryDisplay) {
|
||||
fields.push(field)
|
||||
}
|
||||
})
|
||||
|
@ -181,7 +189,7 @@ export const initialise = context => {
|
|||
fields
|
||||
.map(field => {
|
||||
const fieldSchema = $enrichedSchema[field]
|
||||
const oldColumn = allColumns?.find(x => x.name === field)
|
||||
const oldColumn = $allColumns?.find(x => x.name === field)
|
||||
return {
|
||||
name: field,
|
||||
label: fieldSchema.displayName || field,
|
||||
|
@ -189,9 +197,18 @@ export const initialise = context => {
|
|||
width: fieldSchema.width || oldColumn?.width || DefaultColumnWidth,
|
||||
visible: fieldSchema.visible ?? true,
|
||||
order: fieldSchema.order ?? oldColumn?.order,
|
||||
primaryDisplay: field === primaryDisplay,
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// If we don't have a pinned column then primary display will be in
|
||||
// the normal columns list, and should be first
|
||||
if (a.name === primaryDisplay) {
|
||||
return -1
|
||||
} else if (b.name === primaryDisplay) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Sort by order first
|
||||
const orderA = a.order
|
||||
const orderB = b.order
|
||||
|
@ -214,12 +231,12 @@ export const initialise = context => {
|
|||
)
|
||||
|
||||
// Update sticky column
|
||||
if (!primaryDisplay) {
|
||||
if ($compact || !primaryDisplay) {
|
||||
stickyColumn.set(null)
|
||||
return
|
||||
}
|
||||
const stickySchema = $enrichedSchema[primaryDisplay]
|
||||
const oldStickyColumn = allColumns?.find(x => x.name === primaryDisplay)
|
||||
const oldStickyColumn = $allColumns?.find(x => x.name === primaryDisplay)
|
||||
stickyColumn.set({
|
||||
name: primaryDisplay,
|
||||
label: stickySchema.displayName || primaryDisplay,
|
||||
|
@ -228,6 +245,13 @@ export const initialise = context => {
|
|||
visible: true,
|
||||
order: 0,
|
||||
left: GutterWidth,
|
||||
primaryDisplay: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Process columns when schema changes
|
||||
enrichedSchema.subscribe(processColumns)
|
||||
|
||||
// Process columns when compact flag changes
|
||||
compact.subscribe(() => processColumns(get(enrichedSchema)))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { get, writable, derived } from "svelte/store"
|
||||
import { parseEventLocation } from "../lib/utils"
|
||||
|
||||
const reorderInitialState = {
|
||||
sourceColumn: null,
|
||||
|
@ -33,6 +34,7 @@ export const createActions = context => {
|
|||
stickyColumn,
|
||||
ui,
|
||||
maxScrollLeft,
|
||||
width,
|
||||
} = context
|
||||
|
||||
let autoScrollInterval
|
||||
|
@ -55,6 +57,11 @@ export const createActions = context => {
|
|||
x: 0,
|
||||
column: $stickyColumn.name,
|
||||
})
|
||||
} else if (!$visibleColumns[0].primaryDisplay) {
|
||||
breakpoints.unshift({
|
||||
x: 0,
|
||||
column: null,
|
||||
})
|
||||
}
|
||||
|
||||
// Update state
|
||||
|
@ -69,6 +76,9 @@ export const createActions = context => {
|
|||
// Add listeners to handle mouse movement
|
||||
document.addEventListener("mousemove", onReorderMouseMove)
|
||||
document.addEventListener("mouseup", stopReordering)
|
||||
document.addEventListener("touchmove", onReorderMouseMove)
|
||||
document.addEventListener("touchend", stopReordering)
|
||||
document.addEventListener("touchcancel", stopReordering)
|
||||
|
||||
// Trigger a move event immediately so ensure a candidate column is chosen
|
||||
onReorderMouseMove(e)
|
||||
|
@ -77,7 +87,7 @@ export const createActions = context => {
|
|||
// Callback when moving the mouse when reordering columns
|
||||
const onReorderMouseMove = e => {
|
||||
// Immediately handle the current position
|
||||
const x = e.clientX
|
||||
const { x } = parseEventLocation(e)
|
||||
reorder.update(state => ({
|
||||
...state,
|
||||
latestX: x,
|
||||
|
@ -86,7 +96,7 @@ export const createActions = context => {
|
|||
|
||||
// Check if we need to start auto-scrolling
|
||||
const $reorder = get(reorder)
|
||||
const proximityCutoff = 140
|
||||
const proximityCutoff = Math.min(140, get(width) / 6)
|
||||
const speedFactor = 8
|
||||
const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
|
||||
const leftProximity = Math.max(0, x - $reorder.gridLeft)
|
||||
|
@ -158,19 +168,22 @@ export const createActions = context => {
|
|||
// Ensure auto-scrolling is stopped
|
||||
stopAutoScroll()
|
||||
|
||||
// Swap position of columns
|
||||
let { sourceColumn, targetColumn } = get(reorder)
|
||||
moveColumn(sourceColumn, targetColumn)
|
||||
|
||||
// Reset state
|
||||
reorder.set(reorderInitialState)
|
||||
|
||||
// Remove event handlers
|
||||
document.removeEventListener("mousemove", onReorderMouseMove)
|
||||
document.removeEventListener("mouseup", stopReordering)
|
||||
document.removeEventListener("touchmove", onReorderMouseMove)
|
||||
document.removeEventListener("touchend", stopReordering)
|
||||
document.removeEventListener("touchcancel", stopReordering)
|
||||
|
||||
// Save column changes
|
||||
await columns.actions.saveChanges()
|
||||
// Ensure there's actually a change
|
||||
let { sourceColumn, targetColumn } = get(reorder)
|
||||
if (sourceColumn !== targetColumn) {
|
||||
moveColumn(sourceColumn, targetColumn)
|
||||
await columns.actions.saveChanges()
|
||||
}
|
||||
|
||||
// Reset state
|
||||
reorder.set(reorderInitialState)
|
||||
}
|
||||
|
||||
// Moves a column after another columns.
|
||||
|
@ -185,8 +198,7 @@ export const createActions = context => {
|
|||
if (--targetIdx < sourceIdx) {
|
||||
targetIdx++
|
||||
}
|
||||
state.splice(targetIdx, 0, removed[0])
|
||||
return state.slice()
|
||||
return state.toSpliced(targetIdx, 0, removed[0])
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { writable, get, derived } from "svelte/store"
|
||||
import { MinColumnWidth, DefaultColumnWidth } from "../lib/constants"
|
||||
import { parseEventLocation } from "../lib/utils"
|
||||
|
||||
const initialState = {
|
||||
initialMouseX: null,
|
||||
|
@ -24,8 +25,11 @@ export const createActions = context => {
|
|||
|
||||
// Starts resizing a certain column
|
||||
const startResizing = (column, e) => {
|
||||
const { x } = parseEventLocation(e)
|
||||
|
||||
// Prevent propagation to stop reordering triggering
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
ui.actions.blur()
|
||||
|
||||
// Find and cache index
|
||||
|
@ -39,7 +43,7 @@ export const createActions = context => {
|
|||
width: column.width,
|
||||
left: column.left,
|
||||
initialWidth: column.width,
|
||||
initialMouseX: e.clientX,
|
||||
initialMouseX: x,
|
||||
column: column.name,
|
||||
columnIdx,
|
||||
})
|
||||
|
@ -47,12 +51,16 @@ export const createActions = context => {
|
|||
// Add mouse event listeners to handle resizing
|
||||
document.addEventListener("mousemove", onResizeMouseMove)
|
||||
document.addEventListener("mouseup", stopResizing)
|
||||
document.addEventListener("touchmove", onResizeMouseMove)
|
||||
document.addEventListener("touchend", stopResizing)
|
||||
document.addEventListener("touchcancel", stopResizing)
|
||||
}
|
||||
|
||||
// Handler for moving the mouse to resize columns
|
||||
const onResizeMouseMove = e => {
|
||||
const { initialMouseX, initialWidth, width, columnIdx } = get(resize)
|
||||
const dx = e.clientX - initialMouseX
|
||||
const { x } = parseEventLocation(e)
|
||||
const dx = x - initialMouseX
|
||||
const newWidth = Math.round(Math.max(MinColumnWidth, initialWidth + dx))
|
||||
|
||||
// Ignore small changes
|
||||
|
@ -87,6 +95,9 @@ export const createActions = context => {
|
|||
resize.set(initialState)
|
||||
document.removeEventListener("mousemove", onResizeMouseMove)
|
||||
document.removeEventListener("mouseup", stopResizing)
|
||||
document.removeEventListener("touchmove", onResizeMouseMove)
|
||||
document.removeEventListener("touchend", stopResizing)
|
||||
document.removeEventListener("touchcancel", stopResizing)
|
||||
|
||||
// Persist width if it changed
|
||||
if ($resize.width !== $resize.initialWidth) {
|
||||
|
|
|
@ -98,7 +98,7 @@ export const deriveStores = context => {
|
|||
|
||||
// Derive whether we should use the compact UI, depending on width
|
||||
const compact = derived([stickyColumn, width], ([$stickyColumn, $width]) => {
|
||||
return ($stickyColumn?.width || 0) + $width + GutterWidth < 1100
|
||||
return ($stickyColumn?.width || 0) + $width + GutterWidth < 800
|
||||
})
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue