From 524d1cd0d429270a7952d3c42700720a6fa3432e Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 20 Oct 2025 18:09:40 +0530 Subject: [PATCH 1/7] add: custom expand support with shortcuts. --- v2/pink-sb/src/lib/spreadsheet/Cell.svelte | 28 ++- v2/pink-sb/src/lib/spreadsheet/Root.svelte | 47 ++++- v2/pink-sb/src/lib/spreadsheet/index.ts | 3 + .../src/lib/spreadsheet/row/Base.svelte | 2 +- .../spreadsheet/Spreadsheet.stories.svelte | 193 ++++++++++++++++++ v2/pink-sb/src/stories/spreadsheet/helper.ts | 2 +- 6 files changed, 268 insertions(+), 7 deletions(-) diff --git a/v2/pink-sb/src/lib/spreadsheet/Cell.svelte b/v2/pink-sb/src/lib/spreadsheet/Cell.svelte index a8d4220a65..ff22f27796 100644 --- a/v2/pink-sb/src/lib/spreadsheet/Cell.svelte +++ b/v2/pink-sb/src/lib/spreadsheet/Cell.svelte @@ -34,6 +34,7 @@ let wasDraggable = false; let originalValue = value; let rowIndex: number = -1; + let rowId: string | undefined = undefined; /* edit slot close() triggers blur which calls commitChange, this prevents that */ let isClosingFloatingEditor = false; @@ -151,12 +152,17 @@ return; } - if (e.key === 'Enter' && isEditable && !isAction && !isSelect) { + // Only check modifiers if expand shortcut is configured + const hasModifier = root.expandKbdShortcut + ? e.metaKey || e.ctrlKey || e.shiftKey || e.altKey + : false; + + if (e.key === 'Enter' && !hasModifier && isEditable && !isAction && !isSelect) { originalValue = value; root.setEditing(id); e.preventDefault(); return; - } else if (e.key === 'Enter' && isAction) { + } else if (e.key === 'Enter' && !hasModifier && isAction) { const actionElement = cellEl.firstElementChild as HTMLElement; if (actionElement && typeof actionElement.click === 'function') { actionElement.click(); @@ -198,10 +204,24 @@ typeof cellEl !== 'undefined' && !isEmptyCell ) { - rowIndex = getContext('row'); + const rowContext = getContext<{ rowIndex: number; rowId?: string }>('row'); + rowId = rowContext.rowId; + rowIndex = rowContext.rowIndex; root.registerForNavigation(cellEl, rowIndex, columnIndex); } + function handleCellFocus() { + if (rowId && rowIndex > 0 && !isEditing) { + root.setFocusedRow(rowId, rowIndex - 1); + } + } + + function handleCellBlur() { + if (!isEditing) { + root.setFocusedRow(null, null); + } + } + $: if (isEditing) { tick().then(() => { const selects = cellEl?.querySelector('button.input') as HTMLDivElement; @@ -290,6 +310,8 @@ }} on:drop={root.endDrag} on:keydown={handleCellKeydown} + on:focus={handleCellFocus} + on:blur={handleCellBlur} on:mouseenter={() => { if (isHeader && options?.draggable) { isHeaderBeingHovered = true; diff --git a/v2/pink-sb/src/lib/spreadsheet/Root.svelte b/v2/pink-sb/src/lib/spreadsheet/Root.svelte index 7a38a883a7..d0bc6521a5 100644 --- a/v2/pink-sb/src/lib/spreadsheet/Root.svelte +++ b/v2/pink-sb/src/lib/spreadsheet/Root.svelte @@ -21,6 +21,7 @@ export let emptyCells: false | number = false; export let selection: true | 'hidden' | 'disabled' = true; export let borderRadius: 'xs' | 's' | 'm' | undefined = undefined; + export let expandKbdShortcut: string | undefined = undefined; export let bottomActionTooltip: | { @@ -57,6 +58,7 @@ let currentlyHoveredColumn: string | null = null; let currentlyEditingCellId: string | null = null; + let currentFocusedRow: { rowId: string; rowIndex: number } | null = null; let cellGridRegistry: (HTMLElement | undefined)[][] = []; let dragManager: DragManager; @@ -296,6 +298,14 @@ currentlyEditingCellId = cell; } + function setFocusedRow(rowId: string | null, rowIndex: number | null) { + if (rowId !== null && rowIndex !== null) { + currentFocusedRow = { rowId, rowIndex }; + } else { + currentFocusedRow = null; + } + } + function startDrag(columnId: string, event?: DragEvent) { draggingColumn = columnId; dragManager.startDrag(columnId, event); @@ -524,6 +534,31 @@ (document.activeElement as HTMLElement | null)?.blur(); } + function handleExpandKbdShortcut(event: KeyboardEvent) { + if (!expandKbdShortcut || !currentFocusedRow || currentlyEditingCellId) return; + + const parts = expandKbdShortcut.split('+').map((p) => p.trim().toLowerCase()); + const expectedKey = parts[parts.length - 1]; + const expectedModifiers = parts.slice(0, -1).sort().join('+'); + + // build current pressed combination + const pressedModifiers: string[] = []; + if (event.metaKey || event.ctrlKey) pressedModifiers.push('cmd'); + if (event.shiftKey) pressedModifiers.push('shift'); + if (event.altKey) pressedModifiers.push('alt'); + const actualModifiers = pressedModifiers.sort().join('+'); + + // check if key and modifiers match + if (event.key.toLowerCase() !== expectedKey) return; + if (expectedModifiers !== actualModifiers) return; + + event.preventDefault(); + dispatch('expandKbdShortcut', { + rowId: currentFocusedRow.rowId, + rowIndex: currentFocusedRow.rowIndex + }); + } + $: emptyRowsCount = typeof emptyCells === 'number' ? emptyCells : 0; $: someRowsSelected = @@ -562,7 +597,10 @@ unregisterForNavigation, moveFocus, setColumnHeaderHovered, - currentlyHoveredColumnHeader: currentlyHoveredColumn + currentlyHoveredColumnHeader: currentlyHoveredColumn, + expandKbdShortcut, + currentFocusedRow, + setFocusedRow } as RootProp; const virtualizer = createVirtualizer({ @@ -621,7 +659,12 @@ } - + { + clearNavFocusOnEscape(e); + handleExpandKbdShortcut(e); + }} +/>
void; currentlyHoveredColumnHeader: string; setColumnHeaderHovered: (col: string | null | undefined) => void; + expandKbdShortcut?: string | undefined; + currentFocusedRow: { rowId: string; rowIndex: number } | null; + setFocusedRow: (rowId: string | null, rowIndex: number | null) => void; }>; export type Alignment = diff --git a/v2/pink-sb/src/lib/spreadsheet/row/Base.svelte b/v2/pink-sb/src/lib/spreadsheet/row/Base.svelte index de81271cc4..33b038bd88 100644 --- a/v2/pink-sb/src/lib/spreadsheet/row/Base.svelte +++ b/v2/pink-sb/src/lib/spreadsheet/row/Base.svelte @@ -66,7 +66,7 @@ if (root.keyboardNavigation && !isEmptyRow) { const rowIndex = isHeader ? 0 : (index ?? 0) + 1; - setContext('row', rowIndex); + setContext('row', { rowIndex, rowId: id }); } $: fontSizeStyle = (() => { diff --git a/v2/pink-sb/src/stories/spreadsheet/Spreadsheet.stories.svelte b/v2/pink-sb/src/stories/spreadsheet/Spreadsheet.stories.svelte index 3e8e7d9b8e..e4675059ad 100644 --- a/v2/pink-sb/src/stories/spreadsheet/Spreadsheet.stories.svelte +++ b/v2/pink-sb/src/stories/spreadsheet/Spreadsheet.stories.svelte @@ -54,6 +54,8 @@ import Textarea from '$lib/input/Textarea.svelte'; import { createSparsePagedDataStore } from '$lib/spreadsheet/index.js'; import Text from '$lib/input/Text.svelte'; + import { IconArrowExpand } from '@appwrite.io/pink-icons-svelte'; + import Keyboard, { SpecialCharacter } from '$lib/Keyboard.svelte'; let showAddRowModal = false; let showAddColumnModal = false; @@ -187,6 +189,10 @@ columnName = null; showAddColumnModal = false; } + + let showExpandModal = false; + let showExpandIconForId: number | null = null; + let expandedRowData: { rowId: string; rowIndex: number } | null = null; @@ -1064,6 +1070,193 @@ + + { + expandedRowData = detail; + showExpandModal = true; + }} + > + + {#each dynamicColumns as col} + + {#if col.meta?.isPrimary} + + {col.id} + + {:else if col.isAction} + (showAddColumnModal = true)} + > + + + {:else} + {col.id} + {/if} + + + + + {#if col.meta?.icon} + + {/if} + + + + + {/each} + + + {#each dynamicData as row, index} + + {#each dynamicColumns as col} + {#if col.id === 'id'} + + + + + Expand row + + + + + + + + + + + + {:else} + + {#if col.isAction} + + + + {:else} + {getCellValue(row, col.id)} + {/if} + + {/if} + {/each} + + {/each} + + + + {selectedRows.length ? `${selectedRows.length} records selected` : `10 records`} + + + + + + Keyboard shortcut (Cmd+Enter) triggered for: + + + Row ID: + {expandedRowData?.rowId || 'N/A'} + + + Row Index: + {expandedRowData?.rowIndex ?? 'N/A'} + + + + + + (showExpandModal = false)} + >Close + + + + + diff --git a/v2/pink-sb/src/stories/Keyboard.stories.svelte b/v2/pink-sb/src/stories/Keyboard.stories.svelte index 397281349e..fc3f7d9e8b 100644 --- a/v2/pink-sb/src/stories/Keyboard.stories.svelte +++ b/v2/pink-sb/src/stories/Keyboard.stories.svelte @@ -7,7 +7,7 @@ title: 'Components/Keyboard', component: Keyboard, args: { - key: 'A' + size: 'm' } }; @@ -18,15 +18,17 @@ + + From ad1222bc74330a67a805e2b7232d094729838614 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 21 Oct 2025 12:09:32 +0530 Subject: [PATCH 3/7] update: story. --- .../spreadsheet/Spreadsheet.stories.svelte | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/v2/pink-sb/src/stories/spreadsheet/Spreadsheet.stories.svelte b/v2/pink-sb/src/stories/spreadsheet/Spreadsheet.stories.svelte index e4675059ad..7c905b17fa 100644 --- a/v2/pink-sb/src/stories/spreadsheet/Spreadsheet.stories.svelte +++ b/v2/pink-sb/src/stories/spreadsheet/Spreadsheet.stories.svelte @@ -1149,18 +1149,17 @@ alignItems="center" alignContent="center" justifyContent="space-between" - style="max-height: 20px;" > - {getCellValue(row, col.id)} - {@const opacity = showExpandIconForId === index ? '1' : '0'} + {@const opacityValue = showExpandIconForId === index ? '1' : '0'}