@r2digisolutions/components 0.3.3 → 0.3.5
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/atoms/Checkbox/Checkbox.svelte +8 -1
- package/dist/components/atoms/Grid/Grid.stories.d.ts +7 -7
- package/dist/components/atoms/Input/Input.svelte +7 -2
- package/dist/components/atoms/Textarea/Textarea.svelte +8 -2
- package/dist/components/atoms/Toggle/Toggle.svelte +7 -1
- package/dist/components/atoms/Tooltip/Tooltip.svelte +5 -1
- package/dist/components/molecules/BulkActionBar/BulkActionBar.svelte +6 -8
- package/dist/components/molecules/ColorPicker/ColorPicker.svelte +6 -1
- package/dist/components/molecules/DropdownMenu/DropdownMenu.svelte +9 -3
- package/dist/components/molecules/Select/Select.svelte +10 -5
- package/dist/components/organisms/DataGrid/DataGrid.svelte +16 -21
- package/dist/components/organisms/DataGrid/DataGrid.svelte.d.ts +2 -1
- package/dist/components/organisms/DataGrid/filterRows.js +2 -10
- package/dist/components/organisms/DataGrid/types.d.ts +6 -2
- package/dist/components/organisms/DataTable/DataTable.svelte +12 -14
- package/dist/components/organisms/DataTable/DataTable.svelte.d.ts +9 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/utils/columnAccessor.d.ts +8 -0
- package/dist/utils/columnAccessor.js +35 -0
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.js +7 -0
- package/package.json +1 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { createId } from '../../../utils/id.js';
|
|
3
|
+
|
|
2
4
|
interface CheckboxProps {
|
|
3
5
|
checked?: boolean;
|
|
4
6
|
indeterminate?: boolean;
|
|
@@ -32,7 +34,12 @@
|
|
|
32
34
|
onclick
|
|
33
35
|
}: CheckboxProps = $props();
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
/** Assigned after mount so SSR HTML matches the first client paint. */
|
|
38
|
+
let autoId = $state<string | undefined>(undefined);
|
|
39
|
+
$effect(() => {
|
|
40
|
+
if (id == null) autoId ??= createId('checkbox');
|
|
41
|
+
});
|
|
42
|
+
const inputId = $derived(id ?? autoId);
|
|
36
43
|
|
|
37
44
|
const sizeClasses = {
|
|
38
45
|
sm: 'h-3.5 w-3.5 rounded',
|
|
@@ -2,13 +2,13 @@ import type { StoryObj } from '@storybook/svelte';
|
|
|
2
2
|
declare const meta: {
|
|
3
3
|
title: string;
|
|
4
4
|
component: import("svelte").Component<{
|
|
5
|
-
cols?: (1 | 2 |
|
|
6
|
-
base?: 1 | 2 |
|
|
7
|
-
sm?: 1 | 2 |
|
|
8
|
-
md?: 1 | 2 |
|
|
9
|
-
lg?: 1 | 2 |
|
|
10
|
-
xl?: 1 | 2 |
|
|
11
|
-
'2xl'?: 1 | 2 |
|
|
5
|
+
cols?: (1 | 2 | 3 | 9 | 4 | 8 | 5 | 6 | 7 | 10 | 11 | 12) | {
|
|
6
|
+
base?: 1 | 2 | 3 | 9 | 4 | 8 | 5 | 6 | 7 | 10 | 11 | 12;
|
|
7
|
+
sm?: 1 | 2 | 3 | 9 | 4 | 8 | 5 | 6 | 7 | 10 | 11 | 12;
|
|
8
|
+
md?: 1 | 2 | 3 | 9 | 4 | 8 | 5 | 6 | 7 | 10 | 11 | 12;
|
|
9
|
+
lg?: 1 | 2 | 3 | 9 | 4 | 8 | 5 | 6 | 7 | 10 | 11 | 12;
|
|
10
|
+
xl?: 1 | 2 | 3 | 9 | 4 | 8 | 5 | 6 | 7 | 10 | 11 | 12;
|
|
11
|
+
'2xl'?: 1 | 2 | 3 | 9 | 4 | 8 | 5 | 6 | 7 | 10 | 11 | 12;
|
|
12
12
|
};
|
|
13
13
|
}, {}, "">;
|
|
14
14
|
tags: string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import { createId } from '../../../utils/id.js';
|
|
3
4
|
|
|
4
5
|
type InputStatus = 'default' | 'error' | 'success' | 'warning';
|
|
5
6
|
type InputSize = 'sm' | 'md' | 'lg';
|
|
@@ -60,8 +61,12 @@
|
|
|
60
61
|
onkeydown
|
|
61
62
|
}: InputProps = $props();
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
let autoId = $state<string | undefined>(undefined);
|
|
65
|
+
$effect(() => {
|
|
66
|
+
if (id == null) autoId ??= createId('input');
|
|
67
|
+
});
|
|
68
|
+
const inputId = $derived(id ?? autoId);
|
|
69
|
+
const helperId = $derived(inputId ? `${inputId}-helper` : undefined);
|
|
65
70
|
const hasClear = $derived(clearable && value.length > 0 && !disabled && !readonly);
|
|
66
71
|
|
|
67
72
|
const wrapperSizeClasses: Record<InputSize, string> = {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { createId } from '../../../utils/id.js';
|
|
3
|
+
|
|
2
4
|
interface TextareaProps {
|
|
3
5
|
id?: string;
|
|
4
6
|
name?: string;
|
|
@@ -45,8 +47,12 @@
|
|
|
45
47
|
onkeydown
|
|
46
48
|
}: TextareaProps = $props();
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
let autoId = $state<string | undefined>(undefined);
|
|
51
|
+
$effect(() => {
|
|
52
|
+
if (id == null) autoId ??= createId('textarea');
|
|
53
|
+
});
|
|
54
|
+
const textareaId = $derived(id ?? autoId);
|
|
55
|
+
const helperId = $derived(textareaId ? `${textareaId}-helper` : undefined);
|
|
50
56
|
const characterCount = $derived(value.length);
|
|
51
57
|
|
|
52
58
|
const statusRingClasses: Record<string, string> = {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { createId } from '../../../utils/id.js';
|
|
3
|
+
|
|
2
4
|
interface ToggleProps {
|
|
3
5
|
checked?: boolean;
|
|
4
6
|
disabled?: boolean;
|
|
@@ -21,7 +23,11 @@
|
|
|
21
23
|
onchange
|
|
22
24
|
}: ToggleProps = $props();
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
let autoId = $state<string | undefined>(undefined);
|
|
27
|
+
$effect(() => {
|
|
28
|
+
if (id == null) autoId ??= createId('toggle');
|
|
29
|
+
});
|
|
30
|
+
const inputId = $derived(id ?? autoId);
|
|
25
31
|
|
|
26
32
|
const trackClasses = {
|
|
27
33
|
sm: 'h-4 w-7',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import { createId } from '../../../utils/id.js';
|
|
3
4
|
|
|
4
5
|
type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
|
|
5
6
|
|
|
@@ -27,7 +28,10 @@
|
|
|
27
28
|
let tipStyle = $state('');
|
|
28
29
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
let tipId = $state<string | undefined>(undefined);
|
|
32
|
+
$effect(() => {
|
|
33
|
+
tipId ??= createId('tooltip');
|
|
34
|
+
});
|
|
31
35
|
|
|
32
36
|
function position() {
|
|
33
37
|
if (!triggerEl || !tipEl) return;
|
|
@@ -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',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { createId } from '../../../utils/id.js';
|
|
3
|
+
|
|
2
4
|
export type ColorFormat = 'hex' | 'rgb' | 'hsl';
|
|
3
5
|
type ColorPickerSize = 'sm' | 'md' | 'lg';
|
|
4
6
|
|
|
@@ -83,7 +85,10 @@
|
|
|
83
85
|
let dragging: 'sv' | 'hue' | 'alpha' | null = null;
|
|
84
86
|
let syncing = false;
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
let panelId = $state<string | undefined>(undefined);
|
|
89
|
+
$effect(() => {
|
|
90
|
+
panelId ??= createId('color-picker');
|
|
91
|
+
});
|
|
87
92
|
|
|
88
93
|
const rgba = $derived(hsvToRgb(hue, sat, val));
|
|
89
94
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Component, Snippet } from 'svelte';
|
|
3
3
|
import { on } from 'svelte/events';
|
|
4
|
+
import { createId } from '../../../utils/id.js';
|
|
4
5
|
|
|
5
6
|
export interface DropdownItem {
|
|
6
7
|
id: string;
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
let {
|
|
55
|
-
id
|
|
56
|
+
id: idProp,
|
|
56
57
|
items = [],
|
|
57
58
|
label = 'Options',
|
|
58
59
|
align = 'start',
|
|
@@ -73,6 +74,13 @@
|
|
|
73
74
|
onopenchange
|
|
74
75
|
}: DropdownMenuProps = $props();
|
|
75
76
|
|
|
77
|
+
let autoId = $state<string | undefined>(undefined);
|
|
78
|
+
$effect(() => {
|
|
79
|
+
if (idProp == null) autoId ??= createId('menu');
|
|
80
|
+
});
|
|
81
|
+
const id = $derived(idProp ?? autoId);
|
|
82
|
+
const menuId = $derived(id ? `${id}-list` : undefined);
|
|
83
|
+
|
|
76
84
|
let triggerEl = $state<HTMLButtonElement | null>(null);
|
|
77
85
|
let menuEl = $state<HTMLDivElement | null>(null);
|
|
78
86
|
let listEl = $state<HTMLDivElement | null>(null);
|
|
@@ -82,8 +90,6 @@
|
|
|
82
90
|
let path = $state<string[]>([]);
|
|
83
91
|
let lastSelectedId = $state<string | null>(null);
|
|
84
92
|
|
|
85
|
-
const menuId = $derived(`${id}-list`);
|
|
86
|
-
|
|
87
93
|
function findPathToItem(list: DropdownItem[], targetId: string, parents: string[] = []): string[] | null {
|
|
88
94
|
for (const item of list) {
|
|
89
95
|
if (item.id === targetId) return parents;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { on } from 'svelte/events';
|
|
3
|
+
import { createId } from '../../../utils/id.js';
|
|
3
4
|
|
|
4
5
|
export interface SelectOption {
|
|
5
6
|
value: string;
|
|
@@ -60,9 +61,13 @@
|
|
|
60
61
|
/** After keyboard nav, ignore hover until the mouse actually moves */
|
|
61
62
|
let ignoreHover = $state(false);
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
let autoId = $state<string | undefined>(undefined);
|
|
65
|
+
$effect(() => {
|
|
66
|
+
if (id == null) autoId ??= createId('select');
|
|
67
|
+
});
|
|
68
|
+
const selectId = $derived(id ?? autoId);
|
|
69
|
+
const listboxId = $derived(selectId ? `${selectId}-listbox` : undefined);
|
|
70
|
+
const helperId = $derived(selectId ? `${selectId}-helper` : undefined);
|
|
66
71
|
|
|
67
72
|
function findPathToValue(
|
|
68
73
|
list: SelectOption[],
|
|
@@ -148,7 +153,7 @@
|
|
|
148
153
|
);
|
|
149
154
|
|
|
150
155
|
const activeOptionId = $derived(
|
|
151
|
-
highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : undefined
|
|
156
|
+
listboxId && highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : undefined
|
|
152
157
|
);
|
|
153
158
|
|
|
154
159
|
const sizeClasses = {
|
|
@@ -586,7 +591,7 @@
|
|
|
586
591
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
587
592
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
588
593
|
<div
|
|
589
|
-
id={`${listboxId}-option-${index}`}
|
|
594
|
+
id={listboxId ? `${listboxId}-option-${index}` : undefined}
|
|
590
595
|
role="option"
|
|
591
596
|
aria-selected={isSelected}
|
|
592
597
|
aria-disabled={option.disabled || undefined}
|
|
@@ -102,20 +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
|
-
}
|
|
112
|
-
};
|
|
113
|
+
$effect(() => {
|
|
114
|
+
document.body.appendChild(node);
|
|
115
|
+
return () => node.remove();
|
|
116
|
+
});
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
interface DataGridProps {
|
|
116
120
|
columns?: DataGridColumn<T>[];
|
|
117
121
|
rows?: T[];
|
|
118
|
-
rowKey?:
|
|
122
|
+
rowKey?: RowKey<T>;
|
|
119
123
|
selection?: GridSelection;
|
|
120
124
|
notes?: CellNote[];
|
|
121
125
|
marks?: GridMark[];
|
|
@@ -388,20 +392,11 @@
|
|
|
388
392
|
);
|
|
389
393
|
|
|
390
394
|
function getKey(row: T, index: number): string {
|
|
391
|
-
|
|
392
|
-
if (rowKey && row[rowKey] != null) return String(row[rowKey]);
|
|
393
|
-
if ('id' in row && row.id != null) return String(row.id);
|
|
394
|
-
return String(index);
|
|
395
|
+
return resolveRowKey(row, rowKey, index);
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
function getValue(row: T, column: DataGridColumn<T>): unknown {
|
|
398
|
-
|
|
399
|
-
return key.split('.').reduce<unknown>((acc, part) => {
|
|
400
|
-
if (acc && typeof acc === 'object' && part in (acc as object)) {
|
|
401
|
-
return (acc as Record<string, unknown>)[part];
|
|
402
|
-
}
|
|
403
|
-
return undefined;
|
|
404
|
-
}, row);
|
|
399
|
+
return resolveAccessor(row, column.accessor, column.id);
|
|
405
400
|
}
|
|
406
401
|
|
|
407
402
|
function formatCell(value: unknown): string {
|
|
@@ -748,7 +743,7 @@
|
|
|
748
743
|
if (!editing || !activeEditor) return;
|
|
749
744
|
const { rowId, columnId } = editing;
|
|
750
745
|
const col = columns.find((c) => c.id === columnId);
|
|
751
|
-
const accessor = (col?.accessor
|
|
746
|
+
const accessor = accessorPatchKey(col?.accessor, columnId);
|
|
752
747
|
const draft = activeEditor.type === 'boolean' ? editBool : editValue;
|
|
753
748
|
const nextValue = coerceEditValue(activeEditor, draft);
|
|
754
749
|
editing = null;
|
|
@@ -2080,7 +2075,7 @@
|
|
|
2080
2075
|
{#if noteTargets || markPickerOpen || (showDock && dockCount > 0)}
|
|
2081
2076
|
<!-- Fixed to the viewport (portaled) so parent relative/overflow don't shift the dock. -->
|
|
2082
2077
|
<div
|
|
2083
|
-
|
|
2078
|
+
{@attach portalToBody}
|
|
2084
2079
|
class="pointer-events-none fixed inset-x-0 bottom-3 z-[60] flex justify-center px-3"
|
|
2085
2080
|
>
|
|
2086
2081
|
<div class="relative w-full max-w-2xl">
|
|
@@ -2217,7 +2212,7 @@
|
|
|
2217
2212
|
{@const ay = contextAnchor.y}
|
|
2218
2213
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
2219
2214
|
<div
|
|
2220
|
-
|
|
2215
|
+
{@attach portalToBody}
|
|
2221
2216
|
role="menu"
|
|
2222
2217
|
tabindex={-1}
|
|
2223
2218
|
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,14 +1,6 @@
|
|
|
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
|
-
const
|
|
11
|
-
const value = getByPath(row, key);
|
|
3
|
+
const value = resolveAccessor(row, column.accessor, column.id);
|
|
12
4
|
if (value == null)
|
|
13
5
|
return '';
|
|
14
6
|
if (typeof value === 'boolean')
|
|
@@ -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,11 @@
|
|
|
53
60
|
let sortDir = $state<SortDir>(null);
|
|
54
61
|
|
|
55
62
|
function getKey(row: T, index: number): string {
|
|
56
|
-
|
|
57
|
-
if (rowKey && row[rowKey] != null) return String(row[rowKey]);
|
|
58
|
-
if ('id' in row && row.id != null) return String(row.id);
|
|
59
|
-
return String(index);
|
|
63
|
+
return resolveRowKey(row, rowKey, index);
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
function getValue(row: T, column: DataTableColumn<T>): unknown {
|
|
63
|
-
|
|
64
|
-
return key.split('.').reduce<unknown>((acc, part) => {
|
|
65
|
-
if (acc && typeof acc === 'object' && part in (acc as object)) {
|
|
66
|
-
return (acc as Record<string, unknown>)[part];
|
|
67
|
-
}
|
|
68
|
-
return undefined;
|
|
69
|
-
}, row);
|
|
67
|
+
return resolveAccessor(row, column.accessor, column.id);
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
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
|
@@ -812,6 +812,9 @@ export { default as VideoEditorTemplate } from './components/templates/VideoEdit
|
|
|
812
812
|
export { default as AudioEditorTemplate } from './components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte';
|
|
813
813
|
export { themeStore } from './utils/theme.svelte.js';
|
|
814
814
|
export type { Theme } from './utils/theme.svelte.js';
|
|
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';
|
|
815
818
|
export { pageVisibility } from './utils/pageVisibility.svelte.js';
|
|
816
819
|
export type { VisibilityState } from './utils/pageVisibility.svelte.js';
|
|
817
820
|
export { network } from './utils/network.svelte.js';
|
package/dist/index.js
CHANGED
|
@@ -553,6 +553,8 @@ export { default as VideoEditorTemplate } from './components/templates/VideoEdit
|
|
|
553
553
|
export { default as AudioEditorTemplate } from './components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte';
|
|
554
554
|
// ── Utils ────────────────────────────────────────────────────────────────────
|
|
555
555
|
export { themeStore } from './utils/theme.svelte.js';
|
|
556
|
+
export { createId } from './utils/id.js';
|
|
557
|
+
export { resolveAccessor, accessorPatchKey, resolveRowKey } from './utils/columnAccessor.js';
|
|
556
558
|
export { pageVisibility } from './utils/pageVisibility.svelte.js';
|
|
557
559
|
export { network } from './utils/network.svelte.js';
|
|
558
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
|
+
}
|
package/dist/utils/id.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Monotonic counter for client-assigned DOM ids (not used during SSR). */
|
|
2
|
+
let count = 0;
|
|
3
|
+
/** Create a unique DOM id. Prefer calling from `$effect` so SSR/hydration stay in sync. */
|
|
4
|
+
export function createId(prefix) {
|
|
5
|
+
count += 1;
|
|
6
|
+
return `${prefix}-${count}`;
|
|
7
|
+
}
|