@r2digisolutions/components 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte +6 -8
- package/dist/components/organisms/DataGrid/DataGrid.svelte +46 -30
- package/dist/components/organisms/DataGrid/DataGrid.svelte.d.ts +2 -1
- package/dist/components/organisms/DataGrid/filterRows.js +11 -14
- package/dist/components/organisms/DataGrid/types.d.ts +6 -2
- package/dist/components/organisms/DataTable/DataTable.svelte +19 -13
- package/dist/components/organisms/DataTable/DataTable.svelte.d.ts +9 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/utils/columnAccessor.d.ts +8 -0
- package/dist/utils/columnAccessor.js +35 -0
- package/package.json +1 -1
|
@@ -180,13 +180,11 @@
|
|
|
180
180
|
|
|
181
181
|
/** Dock sticks to the viewport bottom; portal avoids parent transform/overflow traps. */
|
|
182
182
|
function portalDock(node: HTMLElement) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
};
|
|
183
|
+
$effect(() => {
|
|
184
|
+
if (!(placement === 'dock' && sticky)) return;
|
|
185
|
+
document.body.appendChild(node);
|
|
186
|
+
return () => node.remove();
|
|
187
|
+
});
|
|
190
188
|
}
|
|
191
189
|
|
|
192
190
|
const dockFixed = $derived(placement === 'dock' && sticky);
|
|
@@ -196,7 +194,7 @@
|
|
|
196
194
|
|
|
197
195
|
{#if count > 0}
|
|
198
196
|
<div
|
|
199
|
-
|
|
197
|
+
{@attach portalDock}
|
|
200
198
|
class={[
|
|
201
199
|
placement === 'dock' && 'pointer-events-none z-30 flex justify-center',
|
|
202
200
|
dockFixed && 'fixed inset-x-0 bottom-3 z-50 px-3',
|
|
@@ -102,21 +102,24 @@
|
|
|
102
102
|
import { MARK_COLORS, markTint, markSwatch } from './marks.js';
|
|
103
103
|
import { isColumnVisibleAtWidth } from './breakpoints.js';
|
|
104
104
|
import { filterRows, cellText, uniqueColumnValues } from './filterRows.js';
|
|
105
|
+
import { resolveAccessor, accessorPatchKey, resolveRowKey } from '../../../utils/columnAccessor.js';
|
|
106
|
+
import type { RowKey } from '../../../utils/columnAccessor.js';
|
|
105
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Portal via attachment + $effect so the node moves after mount/hydration
|
|
110
|
+
* (same pattern as Checkbox `{@attach setIndeterminate}`).
|
|
111
|
+
*/
|
|
106
112
|
function portalToBody(node: HTMLElement) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
node.remove();
|
|
112
|
-
}
|
|
113
|
-
};
|
|
113
|
+
$effect(() => {
|
|
114
|
+
document.body.appendChild(node);
|
|
115
|
+
return () => node.remove();
|
|
116
|
+
});
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
interface DataGridProps {
|
|
117
120
|
columns?: DataGridColumn<T>[];
|
|
118
121
|
rows?: T[];
|
|
119
|
-
rowKey?:
|
|
122
|
+
rowKey?: RowKey<T>;
|
|
120
123
|
selection?: GridSelection;
|
|
121
124
|
notes?: CellNote[];
|
|
122
125
|
marks?: GridMark[];
|
|
@@ -257,14 +260,19 @@
|
|
|
257
260
|
let contextTarget = $state<CellRef | null>(null);
|
|
258
261
|
let contextOpen = $state(false);
|
|
259
262
|
let contextAnchor = $state<ContextMenuAnchor | null>(null);
|
|
260
|
-
/**
|
|
261
|
-
|
|
263
|
+
/**
|
|
264
|
+
* SSR + first client paint: do not apply `hideBelow` yet (show full table).
|
|
265
|
+
* After mount, measure window and refine columns — no hydration mismatch.
|
|
266
|
+
*/
|
|
267
|
+
let viewportMeasured = $state(false);
|
|
268
|
+
let viewportWidth = $state(Number.POSITIVE_INFINITY);
|
|
262
269
|
let filterColumnDraft = $state('');
|
|
263
270
|
let filterValueDraft = $state('');
|
|
264
271
|
|
|
265
272
|
$effect(() => {
|
|
266
273
|
const onResize = () => {
|
|
267
274
|
viewportWidth = window.innerWidth;
|
|
275
|
+
viewportMeasured = true;
|
|
268
276
|
};
|
|
269
277
|
window.addEventListener('resize', onResize);
|
|
270
278
|
onResize();
|
|
@@ -272,12 +280,14 @@
|
|
|
272
280
|
});
|
|
273
281
|
|
|
274
282
|
const autoHiddenColumns = $derived(
|
|
275
|
-
|
|
276
|
-
(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
283
|
+
viewportMeasured
|
|
284
|
+
? columns.filter(
|
|
285
|
+
(c) =>
|
|
286
|
+
!c.hidden &&
|
|
287
|
+
c.hideBelow &&
|
|
288
|
+
!isColumnVisibleAtWidth(c.hideBelow, viewportWidth)
|
|
289
|
+
)
|
|
290
|
+
: []
|
|
281
291
|
);
|
|
282
292
|
|
|
283
293
|
/** Expand controls only when there is content to reveal. */
|
|
@@ -294,7 +304,13 @@
|
|
|
294
304
|
const visibleColumns = $derived(
|
|
295
305
|
columns.filter((c) => {
|
|
296
306
|
if (c.hidden) return false;
|
|
297
|
-
if (
|
|
307
|
+
if (
|
|
308
|
+
viewportMeasured &&
|
|
309
|
+
c.hideBelow &&
|
|
310
|
+
!isColumnVisibleAtWidth(c.hideBelow, viewportWidth)
|
|
311
|
+
) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
298
314
|
return true;
|
|
299
315
|
})
|
|
300
316
|
);
|
|
@@ -389,20 +405,20 @@
|
|
|
389
405
|
);
|
|
390
406
|
|
|
391
407
|
function getKey(row: T, index: number): string {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
408
|
+
try {
|
|
409
|
+
return resolveRowKey(row, rowKey, index);
|
|
410
|
+
} catch {
|
|
411
|
+
// Bad accessor/rowKey must not take down SSR (e.g. null profile).
|
|
412
|
+
return String(index);
|
|
413
|
+
}
|
|
396
414
|
}
|
|
397
415
|
|
|
398
416
|
function getValue(row: T, column: DataGridColumn<T>): unknown {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return (acc as Record<string, unknown>)[part];
|
|
403
|
-
}
|
|
417
|
+
try {
|
|
418
|
+
return resolveAccessor(row, column.accessor, column.id);
|
|
419
|
+
} catch {
|
|
404
420
|
return undefined;
|
|
405
|
-
}
|
|
421
|
+
}
|
|
406
422
|
}
|
|
407
423
|
|
|
408
424
|
function formatCell(value: unknown): string {
|
|
@@ -749,7 +765,7 @@
|
|
|
749
765
|
if (!editing || !activeEditor) return;
|
|
750
766
|
const { rowId, columnId } = editing;
|
|
751
767
|
const col = columns.find((c) => c.id === columnId);
|
|
752
|
-
const accessor = (col?.accessor
|
|
768
|
+
const accessor = accessorPatchKey(col?.accessor, columnId);
|
|
753
769
|
const draft = activeEditor.type === 'boolean' ? editBool : editValue;
|
|
754
770
|
const nextValue = coerceEditValue(activeEditor, draft);
|
|
755
771
|
editing = null;
|
|
@@ -2081,7 +2097,7 @@
|
|
|
2081
2097
|
{#if noteTargets || markPickerOpen || (showDock && dockCount > 0)}
|
|
2082
2098
|
<!-- Fixed to the viewport (portaled) so parent relative/overflow don't shift the dock. -->
|
|
2083
2099
|
<div
|
|
2084
|
-
|
|
2100
|
+
{@attach portalToBody}
|
|
2085
2101
|
class="pointer-events-none fixed inset-x-0 bottom-3 z-[60] flex justify-center px-3"
|
|
2086
2102
|
>
|
|
2087
2103
|
<div class="relative w-full max-w-2xl">
|
|
@@ -2218,7 +2234,7 @@
|
|
|
2218
2234
|
{@const ay = contextAnchor.y}
|
|
2219
2235
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
2220
2236
|
<div
|
|
2221
|
-
|
|
2237
|
+
{@attach portalToBody}
|
|
2222
2238
|
role="menu"
|
|
2223
2239
|
tabindex={-1}
|
|
2224
2240
|
aria-label="Context menu"
|
|
@@ -8,11 +8,12 @@ export { filterRows, cellText, uniqueColumnValues } from './filterRows.js';
|
|
|
8
8
|
import type { Snippet } from 'svelte';
|
|
9
9
|
import type { BulkAction } from '../../molecules/BulkActionBar/BulkActionBar.svelte';
|
|
10
10
|
import { type DataGridColumn, type DataGridViewMode, type DataGridFilter, type GridSelection, type CellNote, type GridMark, type ConditionalFormatRule, type SortDir } from './types.js';
|
|
11
|
+
import type { RowKey } from '../../../utils/columnAccessor.js';
|
|
11
12
|
declare function $$render<T extends Record<string, unknown> = Record<string, unknown>>(): {
|
|
12
13
|
props: {
|
|
13
14
|
columns?: DataGridColumn<T>[];
|
|
14
15
|
rows?: T[];
|
|
15
|
-
rowKey?:
|
|
16
|
+
rowKey?: RowKey<T>;
|
|
16
17
|
selection?: GridSelection;
|
|
17
18
|
notes?: CellNote[];
|
|
18
19
|
marks?: GridMark[];
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
return path.split('.').reduce((acc, part) => {
|
|
3
|
-
if (acc && typeof acc === 'object' && part in acc) {
|
|
4
|
-
return acc[part];
|
|
5
|
-
}
|
|
6
|
-
return undefined;
|
|
7
|
-
}, row);
|
|
8
|
-
}
|
|
1
|
+
import { resolveAccessor } from '../../../utils/columnAccessor.js';
|
|
9
2
|
export function cellText(row, column) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
try {
|
|
4
|
+
const value = resolveAccessor(row, column.accessor, column.id);
|
|
5
|
+
if (value == null)
|
|
6
|
+
return '';
|
|
7
|
+
if (typeof value === 'boolean')
|
|
8
|
+
return value ? 'Yes' : 'No';
|
|
9
|
+
return String(value);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
13
12
|
return '';
|
|
14
|
-
|
|
15
|
-
return value ? 'Yes' : 'No';
|
|
16
|
-
return String(value);
|
|
13
|
+
}
|
|
17
14
|
}
|
|
18
15
|
/** Client-side search + equality/contains filters. */
|
|
19
16
|
export function filterRows(rows, columns, query, filters) {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export interface DataGridColumn<Row = Record<string, unknown>> {
|
|
2
2
|
id: string;
|
|
3
3
|
header: string;
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* How to read the cell value:
|
|
6
|
+
* - `keyof Row` / string (supports dot-path: `'profile.displayName'`)
|
|
7
|
+
* - function: `(row) => row.profile.displayName` (preferred for nested/typed access)
|
|
8
|
+
*/
|
|
9
|
+
accessor?: keyof Row | string | ((row: Row) => unknown);
|
|
6
10
|
align?: 'left' | 'center' | 'right';
|
|
7
11
|
sortable?: boolean;
|
|
8
12
|
width?: string;
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
export interface DataTableColumn<Row = Record<string, unknown>> {
|
|
3
3
|
id: string;
|
|
4
4
|
header: string;
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* How to read the cell value:
|
|
7
|
+
* - `keyof Row` / string (supports dot-path: `'profile.displayName'`)
|
|
8
|
+
* - function: `(row) => row.profile.displayName` (preferred for nested/typed access)
|
|
9
|
+
* Custom render via `cell` snippet still overrides display.
|
|
10
|
+
*/
|
|
11
|
+
accessor?: keyof Row | string | ((row: Row) => unknown);
|
|
7
12
|
align?: 'left' | 'center' | 'right';
|
|
8
13
|
sortable?: boolean;
|
|
9
14
|
width?: string;
|
|
@@ -13,13 +18,15 @@
|
|
|
13
18
|
|
|
14
19
|
<script lang="ts" generics="T extends Record<string, unknown> = Record<string, unknown>">
|
|
15
20
|
import type { Snippet } from 'svelte';
|
|
21
|
+
import { resolveAccessor, resolveRowKey } from '../../../utils/columnAccessor.js';
|
|
22
|
+
import type { RowKey } from '../../../utils/columnAccessor.js';
|
|
16
23
|
|
|
17
24
|
type SortDir = 'asc' | 'desc' | null;
|
|
18
25
|
|
|
19
26
|
interface DataTableProps {
|
|
20
27
|
columns?: DataTableColumn<T>[];
|
|
21
28
|
rows?: T[];
|
|
22
|
-
rowKey?:
|
|
29
|
+
rowKey?: RowKey<T>;
|
|
23
30
|
sortable?: boolean;
|
|
24
31
|
striped?: boolean;
|
|
25
32
|
hoverable?: boolean;
|
|
@@ -53,20 +60,19 @@
|
|
|
53
60
|
let sortDir = $state<SortDir>(null);
|
|
54
61
|
|
|
55
62
|
function getKey(row: T, index: number): string {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
try {
|
|
64
|
+
return resolveRowKey(row, rowKey, index);
|
|
65
|
+
} catch {
|
|
66
|
+
return String(index);
|
|
67
|
+
}
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
function getValue(row: T, column: DataTableColumn<T>): unknown {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return (acc as Record<string, unknown>)[part];
|
|
67
|
-
}
|
|
71
|
+
try {
|
|
72
|
+
return resolveAccessor(row, column.accessor, column.id);
|
|
73
|
+
} catch {
|
|
68
74
|
return undefined;
|
|
69
|
-
}
|
|
75
|
+
}
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
function formatCell(value: unknown): string {
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
export interface DataTableColumn<Row = Record<string, unknown>> {
|
|
2
2
|
id: string;
|
|
3
3
|
header: string;
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* How to read the cell value:
|
|
6
|
+
* - `keyof Row` / string (supports dot-path: `'profile.displayName'`)
|
|
7
|
+
* - function: `(row) => row.profile.displayName` (preferred for nested/typed access)
|
|
8
|
+
* Custom render via `cell` snippet still overrides display.
|
|
9
|
+
*/
|
|
10
|
+
accessor?: keyof Row | string | ((row: Row) => unknown);
|
|
6
11
|
align?: 'left' | 'center' | 'right';
|
|
7
12
|
sortable?: boolean;
|
|
8
13
|
width?: string;
|
|
9
14
|
class?: string;
|
|
10
15
|
}
|
|
11
16
|
import type { Snippet } from 'svelte';
|
|
17
|
+
import type { RowKey } from '../../../utils/columnAccessor.js';
|
|
12
18
|
declare function $$render<T extends Record<string, unknown> = Record<string, unknown>>(): {
|
|
13
19
|
props: {
|
|
14
20
|
columns?: DataTableColumn<T>[];
|
|
15
21
|
rows?: T[];
|
|
16
|
-
rowKey?:
|
|
22
|
+
rowKey?: RowKey<T>;
|
|
17
23
|
sortable?: boolean;
|
|
18
24
|
striped?: boolean;
|
|
19
25
|
hoverable?: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -813,6 +813,8 @@ export { default as AudioEditorTemplate } from './components/templates/AudioEdit
|
|
|
813
813
|
export { themeStore } from './utils/theme.svelte.js';
|
|
814
814
|
export type { Theme } from './utils/theme.svelte.js';
|
|
815
815
|
export { createId } from './utils/id.js';
|
|
816
|
+
export { resolveAccessor, accessorPatchKey, resolveRowKey } from './utils/columnAccessor.js';
|
|
817
|
+
export type { ColumnAccessor, RowKey } from './utils/columnAccessor.js';
|
|
816
818
|
export { pageVisibility } from './utils/pageVisibility.svelte.js';
|
|
817
819
|
export type { VisibilityState } from './utils/pageVisibility.svelte.js';
|
|
818
820
|
export { network } from './utils/network.svelte.js';
|
package/dist/index.js
CHANGED
|
@@ -554,6 +554,7 @@ export { default as AudioEditorTemplate } from './components/templates/AudioEdit
|
|
|
554
554
|
// ── Utils ────────────────────────────────────────────────────────────────────
|
|
555
555
|
export { themeStore } from './utils/theme.svelte.js';
|
|
556
556
|
export { createId } from './utils/id.js';
|
|
557
|
+
export { resolveAccessor, accessorPatchKey, resolveRowKey } from './utils/columnAccessor.js';
|
|
557
558
|
export { pageVisibility } from './utils/pageVisibility.svelte.js';
|
|
558
559
|
export { network } from './utils/network.svelte.js';
|
|
559
560
|
export { DEFAULT_COLS, DEFAULT_ROW_HEIGHT, DEFAULT_GAP, GRID_DENSITY, clampItem, rectsOverlap, findCollisions, resolveCollisions, compactLayout, updateItem, addItem, removeItem, serializeLayout, parseLayout, layoutBounds, rescaleLayout, resizeItemByEdge } from './utils/layoutGrid.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ColumnAccessor<Row> = keyof Row | string | ((row: Row) => unknown);
|
|
2
|
+
export type RowKey<Row> = keyof Row | string | ((row: Row, index: number) => string);
|
|
3
|
+
/** Resolve a column value from a function, keyof, or dot-path accessor. */
|
|
4
|
+
export declare function resolveAccessor<Row>(row: Row, accessor: ColumnAccessor<Row> | undefined, fallbackId: string): unknown;
|
|
5
|
+
/** Resolve a stable row id (supports function, keyof, and dotted paths like `profile.id`). */
|
|
6
|
+
export declare function resolveRowKey<Row>(row: Row, rowKey: RowKey<Row> | undefined, index: number): string;
|
|
7
|
+
/** Field key for patches — functions can't be keys, so fall back to column id. */
|
|
8
|
+
export declare function accessorPatchKey(accessor: unknown, fallbackId: string): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function getByPath(row, path) {
|
|
2
|
+
return path.split('.').reduce((acc, part) => {
|
|
3
|
+
if (acc && typeof acc === 'object' && part in acc) {
|
|
4
|
+
return acc[part];
|
|
5
|
+
}
|
|
6
|
+
return undefined;
|
|
7
|
+
}, row);
|
|
8
|
+
}
|
|
9
|
+
/** Resolve a column value from a function, keyof, or dot-path accessor. */
|
|
10
|
+
export function resolveAccessor(row, accessor, fallbackId) {
|
|
11
|
+
if (typeof accessor === 'function')
|
|
12
|
+
return accessor(row);
|
|
13
|
+
const key = String(accessor ?? fallbackId);
|
|
14
|
+
return getByPath(row, key);
|
|
15
|
+
}
|
|
16
|
+
/** Resolve a stable row id (supports function, keyof, and dotted paths like `profile.id`). */
|
|
17
|
+
export function resolveRowKey(row, rowKey, index) {
|
|
18
|
+
if (typeof rowKey === 'function')
|
|
19
|
+
return rowKey(row, index);
|
|
20
|
+
if (rowKey != null && rowKey !== '') {
|
|
21
|
+
const val = getByPath(row, String(rowKey));
|
|
22
|
+
if (val != null)
|
|
23
|
+
return String(val);
|
|
24
|
+
}
|
|
25
|
+
if (row && typeof row === 'object' && 'id' in row && row.id != null) {
|
|
26
|
+
return String(row.id);
|
|
27
|
+
}
|
|
28
|
+
return String(index);
|
|
29
|
+
}
|
|
30
|
+
/** Field key for patches — functions can't be keys, so fall back to column id. */
|
|
31
|
+
export function accessorPatchKey(accessor, fallbackId) {
|
|
32
|
+
if (typeof accessor === 'function' || accessor == null)
|
|
33
|
+
return fallbackId;
|
|
34
|
+
return String(accessor);
|
|
35
|
+
}
|