Merge pull request #10429 from Budibase/more-grid-tweaks
More grid improvements
This commit is contained in:
commit
0769e332a6
|
@ -19,6 +19,7 @@
|
|||
export let updateValue = rows.actions.updateValue
|
||||
export let invertX = false
|
||||
export let invertY = false
|
||||
export let contentLines = 1
|
||||
|
||||
const emptyError = writable(null)
|
||||
|
||||
|
@ -84,5 +85,7 @@
|
|||
{readonly}
|
||||
{invertY}
|
||||
{invertX}
|
||||
{contentLines}
|
||||
/>
|
||||
<slot />
|
||||
</GridCell>
|
||||
|
|
|
@ -117,6 +117,9 @@
|
|||
.cell.error {
|
||||
--cell-color: var(--spectrum-global-color-red-500);
|
||||
}
|
||||
.cell.readonly {
|
||||
--cell-color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
.cell:not(.focused) {
|
||||
user-select: none;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
$: sortedBy = column.name === $sort.column
|
||||
$: canMoveLeft = orderable && idx > 0
|
||||
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
|
||||
$: ascendingLabel = column.schema?.type === "number" ? "low-high" : "A-Z"
|
||||
$: descendingLabel = column.schema?.type === "number" ? "high-low" : "Z-A"
|
||||
|
||||
const editColumn = () => {
|
||||
dispatch("edit-column", column.schema)
|
||||
|
@ -179,14 +181,14 @@
|
|||
on:click={sortAscending}
|
||||
disabled={column.name === $sort.column && $sort.order === "ascending"}
|
||||
>
|
||||
Sort A-Z
|
||||
Sort {ascendingLabel}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="SortOrderDown"
|
||||
on:click={sortDescending}
|
||||
disabled={column.name === $sort.column && $sort.order === "descending"}
|
||||
>
|
||||
Sort Z-A
|
||||
Sort {descendingLabel}
|
||||
</MenuItem>
|
||||
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
||||
Move left
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
export let api
|
||||
export let invertX = false
|
||||
export let invertY = false
|
||||
export let contentLines = 1
|
||||
|
||||
let isOpen = false
|
||||
let focusedOptionIdx = null
|
||||
|
@ -86,7 +87,11 @@
|
|||
class:open
|
||||
on:click|self={editable ? open : null}
|
||||
>
|
||||
<div class="values" on:click={editable ? open : null}>
|
||||
<div
|
||||
class="values"
|
||||
class:wrap={contentLines > 1}
|
||||
on:click={editable ? open : null}
|
||||
>
|
||||
{#each values as val}
|
||||
{@const color = getOptionColor(val)}
|
||||
{#if color}
|
||||
|
@ -160,6 +165,9 @@
|
|||
grid-row-gap: var(--cell-padding);
|
||||
overflow: hidden;
|
||||
padding: var(--cell-padding);
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.values.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.text {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
export let onChange
|
||||
export let invertX = false
|
||||
export let invertY = false
|
||||
export let contentLines = 1
|
||||
|
||||
const { API, dispatch } = getContext("grid")
|
||||
const color = getColor(0)
|
||||
|
@ -243,7 +244,11 @@
|
|||
|
||||
<div class="wrapper" class:editable class:focused style="--color:{color};">
|
||||
<div class="container">
|
||||
<div class="values" on:wheel={e => (focused ? e.stopPropagation() : null)}>
|
||||
<div
|
||||
class="values"
|
||||
class:wrap={editable || contentLines > 1}
|
||||
on:wheel={e => (focused ? e.stopPropagation() : null)}
|
||||
>
|
||||
{#each value || [] as relationship, idx}
|
||||
{#if relationship.primaryDisplay}
|
||||
<div class="badge">
|
||||
|
@ -376,6 +381,9 @@
|
|||
grid-row-gap: var(--cell-padding);
|
||||
overflow: hidden;
|
||||
padding: var(--cell-padding);
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.values.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.count {
|
||||
|
|
|
@ -3,16 +3,13 @@
|
|||
import { ActionButton, Popover, Select } from "@budibase/bbui"
|
||||
|
||||
const { sort, columns, stickyColumn } = getContext("grid")
|
||||
const orderOptions = [
|
||||
{ label: "A-Z", value: "ascending" },
|
||||
{ label: "Z-A", value: "descending" },
|
||||
]
|
||||
|
||||
let open = false
|
||||
let anchor
|
||||
|
||||
$: columnOptions = getColumnOptions($stickyColumn, $columns)
|
||||
$: checkValidSortColumn($sort.column, $stickyColumn, $columns)
|
||||
$: orderOptions = getOrderOptions($sort.column, columnOptions)
|
||||
|
||||
const getColumnOptions = (stickyColumn, columns) => {
|
||||
let options = []
|
||||
|
@ -20,6 +17,7 @@
|
|||
options.push({
|
||||
label: stickyColumn.label || stickyColumn.name,
|
||||
value: stickyColumn.name,
|
||||
type: stickyColumn.schema?.type,
|
||||
})
|
||||
}
|
||||
return [
|
||||
|
@ -27,10 +25,25 @@
|
|||
...columns.map(col => ({
|
||||
label: col.label || col.name,
|
||||
value: col.name,
|
||||
type: col.schema?.type,
|
||||
})),
|
||||
]
|
||||
}
|
||||
|
||||
const getOrderOptions = (column, columnOptions) => {
|
||||
const type = columnOptions.find(col => col.value === column)?.type
|
||||
return [
|
||||
{
|
||||
label: type === "number" ? "Low-high" : "A-Z",
|
||||
value: "ascending",
|
||||
},
|
||||
{
|
||||
label: type === "number" ? "High-low" : "Z-A",
|
||||
value: "descending",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const updateSortColumn = e => {
|
||||
sort.update(state => ({
|
||||
...state,
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
selectedCellMap,
|
||||
focusedRow,
|
||||
columnHorizontalInversionIndex,
|
||||
contentLines,
|
||||
} = getContext("grid")
|
||||
|
||||
$: rowSelected = !!$selectedRows[row._id]
|
||||
|
@ -44,6 +45,7 @@
|
|||
focused={$focusedCellId === cellId}
|
||||
selectedUser={$selectedCellMap[cellId]}
|
||||
width={column.width}
|
||||
contentLines={$contentLines}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<script>
|
||||
export let keybind
|
||||
export let padded = false
|
||||
export let overlay = false
|
||||
|
||||
$: parsedKeys = parseKeys(keybind)
|
||||
|
||||
const parseKeys = keybind => {
|
||||
return keybind?.split("+").map(key => {
|
||||
if (key.toLowerCase() === "ctrl") {
|
||||
return navigator.platform.startsWith("Mac") ? "⌘" : key
|
||||
} else if (key.toLowerCase() === "enter") {
|
||||
return "↵"
|
||||
}
|
||||
return key
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="keys" class:padded class:overlay>
|
||||
{#each parsedKeys as key}
|
||||
<div class="key">
|
||||
{key}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.keys {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 3px;
|
||||
}
|
||||
.keys.padded {
|
||||
padding: var(--cell-padding);
|
||||
}
|
||||
.key {
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
background-color: var(--spectrum-global-color-gray-200);
|
||||
color: var(--spectrum-global-color-gray-700);
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.overlay .key {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #eee;
|
||||
}
|
||||
</style>
|
|
@ -7,6 +7,7 @@
|
|||
import { GutterWidth } from "../lib/constants"
|
||||
import { NewRowID } from "../lib/constants"
|
||||
import GutterCell from "../cells/GutterCell.svelte"
|
||||
import KeyboardShortcut from "./KeyboardShortcut.svelte"
|
||||
|
||||
const {
|
||||
hoveredRowId,
|
||||
|
@ -27,13 +28,14 @@
|
|||
columnHorizontalInversionIndex,
|
||||
} = getContext("grid")
|
||||
|
||||
let visible = false
|
||||
let isAdding = false
|
||||
let newRow = {}
|
||||
let offset = 0
|
||||
|
||||
$: firstColumn = $stickyColumn || $renderedColumns[0]
|
||||
$: width = GutterWidth + ($stickyColumn?.width || 0)
|
||||
$: $tableId, (isAdding = false)
|
||||
$: $tableId, (visible = false)
|
||||
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
|
||||
|
||||
const shouldInvertY = (offset, inversionIndex, rows) => {
|
||||
|
@ -45,7 +47,8 @@
|
|||
|
||||
const addRow = async () => {
|
||||
// Blur the active cell and tick to let final value updates propagate
|
||||
$focusedCellAPI?.blur()
|
||||
isAdding = true
|
||||
$focusedCellId = null
|
||||
await tick()
|
||||
|
||||
// Create row
|
||||
|
@ -60,17 +63,19 @@
|
|||
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
|
||||
}
|
||||
}
|
||||
isAdding = false
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
isAdding = false
|
||||
visible = false
|
||||
$focusedCellId = null
|
||||
$hoveredRowId = null
|
||||
document.removeEventListener("keydown", handleKeyPress)
|
||||
}
|
||||
|
||||
const startAdding = async () => {
|
||||
if (isAdding) {
|
||||
if (visible) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -95,7 +100,7 @@
|
|||
|
||||
// Update state and select initial cell
|
||||
newRow = {}
|
||||
isAdding = true
|
||||
visible = true
|
||||
$hoveredRowId = NewRowID
|
||||
if (firstColumn) {
|
||||
$focusedCellId = `${NewRowID}-${firstColumn.name}`
|
||||
|
@ -115,7 +120,7 @@
|
|||
}
|
||||
|
||||
const handleKeyPress = e => {
|
||||
if (!isAdding) {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
|
@ -137,7 +142,7 @@
|
|||
</script>
|
||||
|
||||
<!-- Only show new row functionality if we have any columns -->
|
||||
{#if isAdding}
|
||||
{#if visible}
|
||||
<div
|
||||
class="container"
|
||||
class:floating={offset > 0}
|
||||
|
@ -148,6 +153,9 @@
|
|||
<div class="sticky-column" transition:fade={{ duration: 130 }}>
|
||||
<GutterCell on:expand={addViaModal} rowHovered>
|
||||
<Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
|
||||
{#if isAdding}
|
||||
<div in:fade={{ duration: 130 }} class="loading-overlay" />
|
||||
{/if}
|
||||
</GutterCell>
|
||||
{#if $stickyColumn}
|
||||
{@const cellId = `${NewRowID}-${$stickyColumn.name}`}
|
||||
|
@ -161,7 +169,14 @@
|
|||
{updateValue}
|
||||
rowIdx={0}
|
||||
{invertY}
|
||||
/>
|
||||
>
|
||||
{#if $stickyColumn?.schema?.autocolumn}
|
||||
<div class="readonly-overlay">Can't edit auto column</div>
|
||||
{/if}
|
||||
{#if isAdding}
|
||||
<div in:fade={{ duration: 130 }} class="loading-overlay" />
|
||||
{/if}
|
||||
</DataCell>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="normal-columns" transition:fade={{ duration: 130 }}>
|
||||
|
@ -181,15 +196,32 @@
|
|||
rowIdx={0}
|
||||
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
||||
{invertY}
|
||||
/>
|
||||
>
|
||||
{#if column?.schema?.autocolumn}
|
||||
<div class="readonly-overlay">Can't edit auto column</div>
|
||||
{/if}
|
||||
{#if isAdding}
|
||||
<div in:fade={{ duration: 130 }} class="loading-overlay" />
|
||||
{/if}
|
||||
</DataCell>
|
||||
{/key}
|
||||
{/each}
|
||||
</div>
|
||||
</GridScrollWrapper>
|
||||
</div>
|
||||
<div class="buttons" transition:fade={{ duration: 130 }}>
|
||||
<Button size="M" cta on:click={addRow}>Save</Button>
|
||||
<Button size="M" secondary newStyles on:click={clear}>Cancel</Button>
|
||||
<Button size="M" cta on:click={addRow} disabled={isAdding}>
|
||||
<div class="button-with-keys">
|
||||
Save
|
||||
<KeyboardShortcut overlay keybind="Ctrl+Enter" />
|
||||
</div>
|
||||
</Button>
|
||||
<Button size="M" secondary newStyles on:click={clear}>
|
||||
<div class="button-with-keys">
|
||||
Cancel
|
||||
<KeyboardShortcut overlay keybind="Esc" />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -240,6 +272,14 @@
|
|||
top: calc(var(--row-height) + var(--offset) + 24px);
|
||||
left: var(--gutter-width);
|
||||
}
|
||||
.button-with-keys {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
.button-with-keys :global(> div) {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
/* Sticky column styles */
|
||||
.sticky-column {
|
||||
|
@ -262,4 +302,33 @@
|
|||
width: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Readonly cell overlay */
|
||||
.readonly-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: var(--row-height);
|
||||
width: 100%;
|
||||
padding: var(--cell-padding);
|
||||
font-style: italic;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Overlay while row is being added */
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: var(--row-height);
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
background: var(--spectrum-global-color-gray-400);
|
||||
opacity: 0.25;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import HeaderCell from "../cells/HeaderCell.svelte"
|
||||
import { GutterWidth, BlankRowID } from "../lib/constants"
|
||||
import GutterCell from "../cells/GutterCell.svelte"
|
||||
import KeyboardShortcut from "./KeyboardShortcut.svelte"
|
||||
|
||||
const {
|
||||
rows,
|
||||
|
@ -21,6 +22,7 @@
|
|||
focusedRow,
|
||||
scrollLeft,
|
||||
dispatch,
|
||||
contentLines,
|
||||
} = getContext("grid")
|
||||
|
||||
$: rowCount = $rows.length
|
||||
|
@ -85,6 +87,7 @@
|
|||
selectedUser={$selectedCellMap[cellId]}
|
||||
width={$stickyColumn.width}
|
||||
column={$stickyColumn}
|
||||
contentLines={$contentLines}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -103,7 +106,9 @@
|
|||
<GridCell
|
||||
width={$stickyColumn.width}
|
||||
highlighted={$hoveredRowId === BlankRowID}
|
||||
/>
|
||||
>
|
||||
<KeyboardShortcut padded keybind="Ctrl+Enter" />
|
||||
</GridCell>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -15,8 +15,22 @@
|
|||
selectedRows,
|
||||
} = getContext("grid")
|
||||
|
||||
const ignoredOriginSelectors = [
|
||||
".spectrum-Modal",
|
||||
"#builder-side-panel-container",
|
||||
]
|
||||
|
||||
// Global key listener which intercepts all key events
|
||||
const handleKeyDown = e => {
|
||||
// Avoid processing events sourced from certain origins
|
||||
if (e.target?.closest) {
|
||||
for (let selector of ignoredOriginSelectors) {
|
||||
if (e.target.closest(selector)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing selected avoid processing further key presses
|
||||
if (!$focusedCellId) {
|
||||
if (e.key === "Tab" || e.key?.startsWith("Arrow")) {
|
||||
|
@ -60,11 +74,6 @@
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid processing events sourced from modals
|
||||
if (e.target?.closest?.(".spectrum-Modal")) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
|
||||
// Handle the key ourselves
|
||||
|
|
Loading…
Reference in New Issue