From 1fd244217ed16aa86c7bf206e2629d391255b01d Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 3 Nov 2025 14:31:34 +0530 Subject: [PATCH 1/4] add: in-row-editor navigation. --- package.json | 6 +- pnpm-lock.yaml | 20 +++---- .../table-[table]/+layout.svelte | 55 ++++++++++++++++++- .../table-[table]/layout/sidesheet.svelte | 10 +++- .../table-[table]/spreadsheet.svelte | 8 ++- .../table-[table]/store.ts | 6 +- 6 files changed, 87 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 5a2e77773d..1ea95fa6ea 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "@ai-sdk/svelte": "^1.1.24", "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@fe3277e", "@appwrite.io/pink-icons": "0.25.0", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e", "@faker-js/faker": "^9.9.0", "@popperjs/core": "^2.11.8", "@sentry/sveltekit": "^8.38.0", @@ -95,5 +95,5 @@ "svelte-preprocess" ] }, - "packageManager": "pnpm@10.15.1" + "packageManager": "pnpm@10.20.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82b9a6afc8..bbcf747ac6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,14 +18,14 @@ importers: specifier: 0.25.0 version: 0.25.0 '@appwrite.io/pink-icons-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e(svelte@5.25.3) '@appwrite.io/pink-legacy': specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e(svelte@5.25.3) '@faker-js/faker': specifier: ^9.9.0 version: 9.9.0 @@ -269,8 +269,8 @@ packages: peerDependencies: svelte: ^4.0.0 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7} + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e} version: 2.0.0-RC.1 peerDependencies: svelte: ^4.0.0 @@ -284,8 +284,8 @@ packages: '@appwrite.io/pink-legacy@1.0.3': resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==} - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7} + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 @@ -3709,7 +3709,7 @@ snapshots: dependencies: svelte: 5.25.3 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@d94920e(svelte@5.25.3)': dependencies: svelte: 5.25.3 @@ -3722,7 +3722,7 @@ snapshots: '@appwrite.io/pink-icons': 1.0.0 the-new-css-reset: 1.11.3 - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@d94920e(svelte@5.25.3)': dependencies: '@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3) '@floating-ui/dom': 1.6.13 diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte index f5908ae50a..61257da3f8 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte @@ -46,14 +46,21 @@ import { page } from '$app/state'; import { base } from '$app/paths'; import { canWriteTables } from '$lib/stores/roles'; - import { IconEye, IconLockClosed, IconPlus, IconPuzzle } from '@appwrite.io/pink-icons-svelte'; + import { + IconChevronDown, + IconChevronUp, + IconEye, + IconLockClosed, + IconPlus, + IconPuzzle + } from '@appwrite.io/pink-icons-svelte'; import SideSheet from './layout/sidesheet.svelte'; import EditRow from './rows/edit.svelte'; import EditRelatedRow from './rows/editRelated.svelte'; import EditColumn from './columns/edit.svelte'; import RowActivity from './rows/activity.svelte'; import EditRowPermissions from './rows/editPermissions.svelte'; - import { Dialog, Layout, Typography, Selector } from '@appwrite.io/pink-svelte'; + import { Dialog, Layout, Typography, Selector, Icon } from '@appwrite.io/pink-svelte'; import { Button, Seekbar } from '$lib/elements/forms'; import { generateFakeRecords, generateColumns } from '$lib/helpers/faker'; import { addNotification } from '$lib/stores/notifications'; @@ -66,6 +73,7 @@ import IndexesSuggestions from '../(suggestions)/indexes.svelte'; import { showIndexesSuggestions, tableColumnSuggestions } from '../(suggestions)'; + import { isTabletViewport } from '$lib/stores/viewport'; let editRow: EditRow; let editRelatedRow: EditRelatedRow; @@ -468,6 +476,49 @@ show: !!currentRowId, value: buildRowUrl(currentRowId) }}> + {#snippet topEndActions()} + {@const rows = $databaseRowSheetOptions.rows ?? []} + {@const currentIndex = $databaseRowSheetOptions.rowIndex ?? -1} + {@const isFirstRow = currentIndex <= 0} + {@const isLastRow = currentIndex >= rows.length - 1} + + {#if !$isTabletViewport} + + + + {/if} + {/snippet} + {#key currentRowId} = $props(); let form: Form; @@ -88,6 +90,12 @@ {/if} {/if} + + {#if topEndActions} + + {@render topEndActions()} + + {/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index ecf308d49d..53f6992946 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -109,6 +109,9 @@ paginatedRows.setPage(1, $rows.rows); } + // create index map for O(1) row lookups, reactive! + $: rowIndexMap = new Map($paginatedRows.items.map((row, index) => [row.$id, index])); + const tableId = page.params.table; const databaseId = page.params.database; const organizationId = data.organization.$id ?? data.project.teamId; @@ -543,11 +546,14 @@ } else if (type === 'row') { if (action === 'update') { databaseRowSheetOptions.update((opts) => { + const rowIndex = rowIndexMap.get(row.$id) ?? -1; return { ...opts, row, + rowIndex, show: true, - title: 'Update row' + title: 'Update row', + rows: $paginatedRows.items }; }); } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts index f5679dfb69..86dc2cf0a0 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts @@ -64,12 +64,16 @@ export const databaseRowSheetOptions = writable< DatabaseSheetOptions & { row: Models.Row; rowId?: string; + rows: Models.Row[]; + rowIndex?: number; } >({ title: null, show: false, row: null, - rowId: null // for loading from a given id + rowId: null, // for loading from a given id + rows: [], + rowIndex: -1 }); export const databaseRelatedRowSheetOptions = writable< From ea56166dac389add81e2b891222c7f946b465c25 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 3 Nov 2025 14:51:55 +0530 Subject: [PATCH 2/4] fix: shortcut not working. --- .../database-[database]/table-[table]/spreadsheet.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index 53f6992946..ae3ab197dd 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -803,8 +803,7 @@ expandKbdShortcut="Cmd+Enter" on:expandKbdShortcut={({ detail }) => { const focusedRowId = detail.rowId; - const focusedRow = $rows.rows.find((row) => row.$id === focusedRowId); - + const focusedRow = $paginatedRows.items.find((row) => row.$id === focusedRowId); previouslyFocusedElement = document.activeElement; onSelectSheetOption('update', null, 'row', focusedRow); }}> From d7e19c1ca9a15358fbcf8f70a5d093b24ff1c718 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 3 Nov 2025 15:35:03 +0530 Subject: [PATCH 3/4] fix: missing spatial deep check. --- .../table-[table]/rows/edit.svelte | 12 +++++++++++- .../table-[table]/spreadsheet.svelte | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte index 03b95bea6c..0754f64201 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte @@ -10,9 +10,15 @@ import { invalidate } from '$app/navigation'; import { table, type Columns, PROHIBITED_ROW_KEYS } from '../store'; import ColumnItem from './columns/columnItem.svelte'; - import { buildWildcardColumnsQuery, isRelationship, isRelationshipToMany } from './store'; + import { + buildWildcardColumnsQuery, + isRelationship, + isRelationshipToMany, + isSpatialType + } from './store'; import { Layout, Skeleton } from '@appwrite.io/pink-svelte'; import { deepClone } from '$lib/helpers/object'; + import deepEqual from 'deep-equal'; const tableId = page.params.table; const databaseId = page.params.database; @@ -90,6 +96,10 @@ const workColumn = $work?.[column.key]; const currentColumn = $doc?.[column.key]; + if (isSpatialType(column)) { + return deepEqual(workColumn, currentColumn); + } + if (column.array) { return !symmetricDifference(Array.from(workColumn), Array.from(currentColumn)).length; } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index ae3ab197dd..cab6dc8e58 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -804,6 +804,7 @@ on:expandKbdShortcut={({ detail }) => { const focusedRowId = detail.rowId; const focusedRow = $paginatedRows.items.find((row) => row.$id === focusedRowId); + previouslyFocusedElement = document.activeElement; onSelectSheetOption('update', null, 'row', focusedRow); }}> From 4d4bac863fa087f90eee30c0e0538a9cf27eb62e Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 10 Nov 2025 14:21:03 +0530 Subject: [PATCH 4/4] add: triggered based focus. --- .../table-[table]/+layout.svelte | 114 ++++++++++++------ .../table-[table]/layout/sidesheet.svelte | 2 +- .../table-[table]/rows/edit.svelte | 8 +- .../table-[table]/spreadsheet.svelte | 9 +- .../table-[table]/store.ts | 4 +- 5 files changed, 95 insertions(+), 42 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte index 8eeafff276..07f9bd7819 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte @@ -72,9 +72,8 @@ import { chunks } from '$lib/helpers/array'; import { Submit, trackEvent } from '$lib/actions/analytics'; - import IndexesSuggestions from '../(suggestions)/indexes.svelte'; - import { showIndexesSuggestions, tableColumnSuggestions } from '../(suggestions)'; import { isTabletViewport } from '$lib/stores/viewport'; + import IndexesSuggestions from '../(suggestions)/indexes.svelte'; let editRow: EditRow; let editRelatedRow: EditRelatedRow; @@ -87,6 +86,33 @@ let columnCreationHandler: ((response: RealtimeResponse) => void) | null = null; + // manual management of focus is needed! + const autoFocusAction = (node: HTMLElement, shouldFocus: boolean) => { + const button = node.querySelector('button'); + if (!button) return; + + const handleBlur = () => button.classList.remove('focus-visible'); + const applyFocus = (focus: boolean) => { + if (focus) { + button.classList.add('focus-visible'); + button.focus(); + } else { + button.classList.remove('focus-visible'); + } + }; + + button.addEventListener('blur', handleBlur); + applyFocus(shouldFocus); + + return { + update: applyFocus, + destroy() { + button.removeEventListener('blur', handleBlur); + button.classList.remove('focus-visible'); + } + }; + }; + onMount(() => { expandTabs.set(preferences.getKey('tableHeaderExpanded', true)); @@ -464,39 +490,47 @@ {@const isLastRow = currentIndex >= rows.length - 1} {#if !$isTabletViewport} - - - + {@const shouldFocusPrev = !$databaseRowSheetOptions.autoFocus && !isFirstRow} + {@const shouldFocusNext = + !$databaseRowSheetOptions.autoFocus && isFirstRow && !isLastRow} + +
+ +
+ +
+ +
{/if} {/snippet} @@ -504,7 +538,8 @@ + bind:rowId={$databaseRowSheetOptions.rowId} + autoFocus={$databaseRowSheetOptions.autoFocus} /> {/key} @@ -574,3 +609,10 @@ + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte index 5b77be33d2..afe51be679 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte @@ -92,7 +92,7 @@ {#if topEndActions} - + {@render topEndActions()} {/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte index 0754f64201..ab3879962d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte @@ -25,10 +25,12 @@ let { row = $bindable(), - rowId = $bindable(null) + rowId = $bindable(null), + autoFocus = true }: { row?: Models.Row | null; rowId?: string | null; + autoFocus?: boolean; } = $props(); let loading = $state(false); @@ -82,7 +84,9 @@ $effect(() => { if (row) { work = initWork(); - requestAnimationFrame(() => focusFirstInput()); + if (autoFocus) { + requestAnimationFrame(() => focusFirstInput()); + } } else { work = null; } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index 989d5e653c..5e3428bd47 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -809,6 +809,7 @@ const focusedRow = $paginatedRows.items.find((row) => row.$id === focusedRowId); previouslyFocusedElement = document.activeElement; + $databaseRowSheetOptions.autoFocus = false; onSelectSheetOption('update', null, 'row', focusedRow); }}> @@ -935,6 +936,7 @@ hide(); previouslyFocusedElement = document.activeElement; + $databaseRowSheetOptions.autoFocus = false; onSelectSheetOption( 'update', null, @@ -985,8 +987,10 @@ - onSelectSheetOption(option, null, 'row', row)} + onSelect={(option) => { + $databaseRowSheetOptions.autoFocus = true; + onSelectSheetOption(option, null, 'row', row); + }} onVisibilityChanged={(visible) => { canShowDatetimePopover = !visible; }}> @@ -1113,6 +1117,7 @@ rowColumn ); } else { + $databaseRowSheetOptions.autoFocus = true; onSelectSheetOption('update', null, 'row', row); } }} /> diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts index 3f51a93def..6c6a284890 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts @@ -72,6 +72,7 @@ export const databaseRowSheetOptions = writable< rowId?: string; rows: Models.Row[]; rowIndex?: number; + autoFocus?: boolean; } >({ title: null, @@ -79,7 +80,8 @@ export const databaseRowSheetOptions = writable< row: null, rowId: null, // for loading from a given id rows: [], - rowIndex: -1 + rowIndex: -1, + autoFocus: true }); export const databaseRelatedRowSheetOptions = writable<