@lavalogic/scoria 0.37.53 → 0.37.55
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/Table/ColumnFactory.svelte.js +37 -22
- package/dist/Components/Table/Types/Columns/Definitions/Actions/ActionsDef.svelte.d.ts +15 -6
- package/dist/Components/Table/Types/Columns/Definitions/Actions/ActionsDef.svelte.js +5 -4
- package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.d.ts +10 -2
- package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.js +4 -3
- package/dist/Components/Table/Types/Columns/JSONTableLayout.d.ts +13 -4
- package/dist/Components/Table/Types/Columns/JSONTableLayout.js +7 -3
- package/dist/Components/Table/Types/Context/TableContext.svelte.js +21 -6
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* `minSize`, …) via the `commonProps(...)` helper. `id` is passed explicitly
|
|
12
12
|
* at each call site so its narrowed literal type survives the object spread.
|
|
13
13
|
*/
|
|
14
|
-
import { formatDateTime,
|
|
14
|
+
import { formatDateTime, syntheticColumnId } from '../../Helpers/Helpers.svelte.js';
|
|
15
15
|
import { CheckboxDef } from './Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js';
|
|
16
16
|
import { DateInputDef } from './Types/Columns/Definitions/Accessors/DateInputDef.svelte.js';
|
|
17
17
|
import { NumberInputDef } from './Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js';
|
|
@@ -130,22 +130,6 @@ function commonProps(opts, kind) {
|
|
|
130
130
|
filterGroup: opts.filterGroup,
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
|
-
/** Generate a column id for a kind whose option type does not require an
|
|
134
|
-
* `id` from the caller.
|
|
135
|
-
*
|
|
136
|
-
* - With `hint`: used verbatim. `idHint` is the server-side-filtering
|
|
137
|
-
* contract; the column id flows through `createRemoteRowSource` as
|
|
138
|
-
* the query-string key the server expects, so a synthetic prefix
|
|
139
|
-
* would make every server reject the field as unknown.
|
|
140
|
-
* - Without `hint`: fall back to `${prefix}-${UUID}` so the id is unique
|
|
141
|
-
* within the table. Such columns cannot participate in server-driven
|
|
142
|
-
* filtering (the server has no way to know the random suffix). */
|
|
143
|
-
function mintSyntheticId(prefix, hint) {
|
|
144
|
-
if (hint != null) {
|
|
145
|
-
return syntheticColumnId(hint);
|
|
146
|
-
}
|
|
147
|
-
return syntheticColumnId(`${prefix}-${generateUUID()}`);
|
|
148
|
-
}
|
|
149
133
|
/** Resolve an `options` field that may be a static array or an async loader
|
|
150
134
|
* into the always-loader form the Def expects. The spread drops the
|
|
151
135
|
* readonly modifier the Def's signature lacks. Loaders may return either
|
|
@@ -185,6 +169,35 @@ function formatLocaleDate(value) {
|
|
|
185
169
|
return '';
|
|
186
170
|
}
|
|
187
171
|
export function createColumnFactory() {
|
|
172
|
+
// Per-factory deterministic counters for synthetic (hint-less) column
|
|
173
|
+
// ids. The `columns: (col) => [...]` closure runs the factory methods
|
|
174
|
+
// in the same order every time `createTable` rebuilds the table, so
|
|
175
|
+
// `${prefix}-${n}` is stable across re-creations - which the
|
|
176
|
+
// persisted-layout and column-preset round-trips key on. A random
|
|
177
|
+
// UUID per build silently broke both (every mount produced fresh ids
|
|
178
|
+
// that no saved snapshot could match).
|
|
179
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
180
|
+
const syntheticIdCounters = new Map();
|
|
181
|
+
/** Mint a column id for a kind whose option type does not require an
|
|
182
|
+
* `id` from the caller.
|
|
183
|
+
*
|
|
184
|
+
* - With `hint`: used verbatim. `idHint` is the server-side-filtering
|
|
185
|
+
* contract; the column id flows through `createRemoteRowSource` as
|
|
186
|
+
* the query-string key the server expects, so a synthetic prefix
|
|
187
|
+
* would make every server reject the field as unknown.
|
|
188
|
+
* - Without `hint`: fall back to a deterministic `${prefix}-${n}`
|
|
189
|
+
* (n is a per-prefix, per-factory counter) so the id is unique
|
|
190
|
+
* within the table and stable across rebuilds. Such columns cannot
|
|
191
|
+
* participate in server-driven filtering (the server has no column
|
|
192
|
+
* matching the synthetic id). */
|
|
193
|
+
function mintId(prefix, hint) {
|
|
194
|
+
if (hint != null) {
|
|
195
|
+
return syntheticColumnId(hint);
|
|
196
|
+
}
|
|
197
|
+
const n = syntheticIdCounters.get(prefix) ?? 0;
|
|
198
|
+
syntheticIdCounters.set(prefix, n + 1);
|
|
199
|
+
return syntheticColumnId(`${prefix}-${n}`);
|
|
200
|
+
}
|
|
188
201
|
const factory = {
|
|
189
202
|
// ─── Accessor kinds ─────────────────────────────────────────
|
|
190
203
|
text(opts) {
|
|
@@ -296,7 +309,7 @@ export function createColumnFactory() {
|
|
|
296
309
|
},
|
|
297
310
|
// ─── Display kinds ──────────────────────────────────────────
|
|
298
311
|
display(opts) {
|
|
299
|
-
const id =
|
|
312
|
+
const id = mintId('display', opts.idHint);
|
|
300
313
|
const normalisedOptions = opts.getOptions
|
|
301
314
|
? normaliseOptionsLoader(opts.getOptions)
|
|
302
315
|
: undefined;
|
|
@@ -357,7 +370,7 @@ export function createColumnFactory() {
|
|
|
357
370
|
return makeSpec('display', def);
|
|
358
371
|
},
|
|
359
372
|
bubble(opts) {
|
|
360
|
-
const id =
|
|
373
|
+
const id = mintId('bubble', opts.idHint);
|
|
361
374
|
const normalisedOptions = opts.getOptions
|
|
362
375
|
? normaliseOptionsLoader(opts.getOptions)
|
|
363
376
|
: undefined;
|
|
@@ -384,7 +397,7 @@ export function createColumnFactory() {
|
|
|
384
397
|
return makeSpec('bubble', def);
|
|
385
398
|
},
|
|
386
399
|
progress(opts) {
|
|
387
|
-
const id =
|
|
400
|
+
const id = mintId('progress', opts.idHint);
|
|
388
401
|
// Normalise scalar ratio to `[fraction, 1]` tuple for the Def.
|
|
389
402
|
const tupleAccessor = (row, index) => {
|
|
390
403
|
const raw = opts.value(row, index);
|
|
@@ -417,7 +430,7 @@ export function createColumnFactory() {
|
|
|
417
430
|
return makeSpec('progress', def);
|
|
418
431
|
},
|
|
419
432
|
validity(opts) {
|
|
420
|
-
const id =
|
|
433
|
+
const id = mintId('validity', opts?.idHint);
|
|
421
434
|
const fallbackValid = () => 'Valid';
|
|
422
435
|
const def = new ValidityDef({
|
|
423
436
|
id,
|
|
@@ -437,6 +450,7 @@ export function createColumnFactory() {
|
|
|
437
450
|
rowSelection(opts) {
|
|
438
451
|
const def = new RowSelectionDef({
|
|
439
452
|
isSelectable: opts?.isSelectable ?? (() => true),
|
|
453
|
+
id: mintId('is-selectable'),
|
|
440
454
|
});
|
|
441
455
|
// RowSelection has no public width option — apply the per-kind
|
|
442
456
|
// default so the column does not fall back to the 48px ColumnDef
|
|
@@ -464,6 +478,7 @@ export function createColumnFactory() {
|
|
|
464
478
|
const def = new ActionsDef({
|
|
465
479
|
actions: wrapped,
|
|
466
480
|
isEnabled: isEnabledFn ? (item) => isEnabledFn(item) : undefined,
|
|
481
|
+
id: mintId('actions'),
|
|
467
482
|
});
|
|
468
483
|
// Actions has no public width option — apply the per-kind default.
|
|
469
484
|
def.width = KIND_DEFAULT_WIDTHS.actions;
|
|
@@ -480,7 +495,7 @@ export function createColumnFactory() {
|
|
|
480
495
|
: (inner) => inner[opts.childRowId];
|
|
481
496
|
const userIsEnabled = opts.isEnabled;
|
|
482
497
|
const def = new ExpandDef({
|
|
483
|
-
id:
|
|
498
|
+
id: mintId('expand'),
|
|
484
499
|
header: opts.header ?? '',
|
|
485
500
|
isEnabled: userIsEnabled ?? (() => true),
|
|
486
501
|
accessorFn: opts.childRows,
|
|
@@ -2,9 +2,10 @@ import type { ColourSet } from '../../../../../../scss/colours.js';
|
|
|
2
2
|
import type { TableActionButton } from '../../TableActionButton.js';
|
|
3
3
|
import { ColumnDef, type ColumnDefProps } from '../ColumnDef.svelte.js';
|
|
4
4
|
/**
|
|
5
|
-
* Construction-time props for `ActionsDef`. Omits `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Construction-time props for `ActionsDef`. Omits `header` (pinned to
|
|
6
|
+
* `'Actions'`) and makes `id` optional: the typed column factory passes
|
|
7
|
+
* a deterministic `actions-<n>` id, and a `generateUUID`-suffixed id is
|
|
8
|
+
* minted only when none is supplied.
|
|
8
9
|
*
|
|
9
10
|
* Notes:
|
|
10
11
|
* - `isEnabled` defaults to `true` for every button if omitted.
|
|
@@ -19,15 +20,23 @@ export interface ActionsDefProps<T extends object, in FilterValueType> extends O
|
|
|
19
20
|
isEnabled?: (item: T, index: number, button: TableActionButton<T>) => boolean | Promise<boolean>;
|
|
20
21
|
/** Per-row, per-button colour override. */
|
|
21
22
|
getColourSet?: (item: T, index: number, button: TableActionButton<T>) => ColourSet;
|
|
23
|
+
/**
|
|
24
|
+
* Optional explicit column id. The typed column factory passes a
|
|
25
|
+
* deterministic id (`actions-<n>`) so the column keeps the same id
|
|
26
|
+
* across table re-creations - which persisted-layout and preset
|
|
27
|
+
* round-trips key on. When omitted a random id is minted.
|
|
28
|
+
*/
|
|
29
|
+
id?: ColumnDefProps<T, FilterValueType>['id'];
|
|
22
30
|
}
|
|
23
31
|
/**
|
|
24
32
|
* `ActionsDef<T, FilterValueType>` is a column whose cells render a
|
|
25
33
|
* row of buttons. It is non-sortable, non-filterable, and pins its
|
|
26
34
|
* header to `'Actions'`.
|
|
27
35
|
*
|
|
28
|
-
* Note:
|
|
29
|
-
*
|
|
30
|
-
*
|
|
36
|
+
* Note: when constructed without an explicit `id` the fallback id uses
|
|
37
|
+
* `generateUUID()`, which is NOT stable across table rebuilds; consumers
|
|
38
|
+
* that need a persisted-layout-safe id (the typed column factory) pass
|
|
39
|
+
* a deterministic `id` instead.
|
|
31
40
|
*/
|
|
32
41
|
export declare class ActionsDef<T extends object, in FilterValueType> extends ColumnDef<T, FilterValueType> {
|
|
33
42
|
constructor(props: ActionsDefProps<T, FilterValueType>);
|
|
@@ -6,14 +6,15 @@ import { ColumnDef } from '../ColumnDef.svelte.js';
|
|
|
6
6
|
* row of buttons. It is non-sortable, non-filterable, and pins its
|
|
7
7
|
* header to `'Actions'`.
|
|
8
8
|
*
|
|
9
|
-
* Note:
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Note: when constructed without an explicit `id` the fallback id uses
|
|
10
|
+
* `generateUUID()`, which is NOT stable across table rebuilds; consumers
|
|
11
|
+
* that need a persisted-layout-safe id (the typed column factory) pass
|
|
12
|
+
* a deterministic `id` instead.
|
|
12
13
|
*/
|
|
13
14
|
export class ActionsDef extends ColumnDef {
|
|
14
15
|
constructor(props) {
|
|
15
16
|
super({
|
|
16
|
-
id: syntheticColumnId(`actions-${generateUUID()}`),
|
|
17
|
+
id: props.id ?? syntheticColumnId(`actions-${generateUUID()}`),
|
|
17
18
|
header: 'Actions',
|
|
18
19
|
allowSorting: false,
|
|
19
20
|
allowFiltering: false,
|
package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.d.ts
CHANGED
|
@@ -11,12 +11,20 @@ export interface RowSelectionDefProps<T extends object, FilterValueType = BoolFi
|
|
|
11
11
|
isSelectable: (item: T, index: number) => boolean | Promise<boolean>;
|
|
12
12
|
/** Optional row validity predicate. */
|
|
13
13
|
isValid?: ColumnDefProps<T, FilterValueType>['isValid'];
|
|
14
|
+
/**
|
|
15
|
+
* Optional explicit column id. The typed column factory passes a
|
|
16
|
+
* deterministic id (`is-selectable-<n>`) so the column keeps the
|
|
17
|
+
* same id across table re-creations - which persisted-layout and
|
|
18
|
+
* preset round-trips key on. When omitted a random id is minted.
|
|
19
|
+
*/
|
|
20
|
+
id?: ColumnDefProps<T, FilterValueType>['id'];
|
|
14
21
|
}
|
|
15
22
|
/**
|
|
16
23
|
* `RowSelectionDef<T, FilterValueType>` is the checkbox column that
|
|
17
24
|
* drives row selection. Non-sortable and non-filterable; the header
|
|
18
|
-
* reads `'Row Selected'
|
|
19
|
-
* (
|
|
25
|
+
* reads `'Row Selected'`. The id is taken from `props.id` when supplied
|
|
26
|
+
* (the typed column factory passes a deterministic `is-selectable-<n>`)
|
|
27
|
+
* and otherwise falls back to a `generateUUID`-suffixed id.
|
|
20
28
|
*
|
|
21
29
|
* @remarks
|
|
22
30
|
*
|
package/dist/Components/Table/Types/Columns/Definitions/RowSelection/RowSelectionDef.svelte.js
CHANGED
|
@@ -4,8 +4,9 @@ import { ColumnDef } from '../ColumnDef.svelte.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* `RowSelectionDef<T, FilterValueType>` is the checkbox column that
|
|
6
6
|
* drives row selection. Non-sortable and non-filterable; the header
|
|
7
|
-
* reads `'Row Selected'
|
|
8
|
-
* (
|
|
7
|
+
* reads `'Row Selected'`. The id is taken from `props.id` when supplied
|
|
8
|
+
* (the typed column factory passes a deterministic `is-selectable-<n>`)
|
|
9
|
+
* and otherwise falls back to a `generateUUID`-suffixed id.
|
|
9
10
|
*
|
|
10
11
|
* @remarks
|
|
11
12
|
*
|
|
@@ -28,7 +29,7 @@ export class RowSelectionDef extends ColumnDef {
|
|
|
28
29
|
? { isSelectable: propsOrIsSelectable, isValid: legacyIsValid }
|
|
29
30
|
: propsOrIsSelectable;
|
|
30
31
|
super({
|
|
31
|
-
id: syntheticColumnId(`is-selectable-${generateUUID()}`),
|
|
32
|
+
id: props.id ?? syntheticColumnId(`is-selectable-${generateUUID()}`),
|
|
32
33
|
header: 'Row Selected',
|
|
33
34
|
allowSorting: false,
|
|
34
35
|
allowFiltering: false,
|
|
@@ -23,15 +23,24 @@ export interface JSONTableLayout {
|
|
|
23
23
|
visibility: VisibilityState;
|
|
24
24
|
/** Pinned-left / pinned-right column ids with their pinned widths. */
|
|
25
25
|
pinning: JSONColumnPinningState;
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Widths (px) of columns the user has explicitly resized, keyed by
|
|
28
|
+
* column id. Columns absent from this map keep their factory-default
|
|
29
|
+
* width; on restore each entry sets the def's `width` and marks it
|
|
30
|
+
* `userResized` so the grid renders the fixed track.
|
|
31
|
+
*/
|
|
27
32
|
sizing: ColumnSizingState;
|
|
28
33
|
}
|
|
29
34
|
/**
|
|
30
35
|
* Schema version for the persisted `JSONTableLayout` envelope. Bump when
|
|
31
|
-
* the shape
|
|
32
|
-
* silently drops envelopes written under a different
|
|
36
|
+
* the shape or field semantics change in a backwards-incompatible way;
|
|
37
|
+
* `readVersionedJSON` silently drops envelopes written under a different
|
|
38
|
+
* version.
|
|
39
|
+
*
|
|
40
|
+
* v2: `sizing` switched from a snapshot of the (vestigial) `columnSizing`
|
|
41
|
+
* map to the real per-`ColumnDef` `width` of user-resized columns.
|
|
33
42
|
*/
|
|
34
|
-
export declare const TABLE_LAYOUT_SCHEMA_VERSION =
|
|
43
|
+
export declare const TABLE_LAYOUT_SCHEMA_VERSION = 2;
|
|
35
44
|
/**
|
|
36
45
|
* Conservative typeguard for a persisted `JSONTableLayout`. localStorage
|
|
37
46
|
* is same-origin-writable so the decoded value is untrusted; this checks
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema version for the persisted `JSONTableLayout` envelope. Bump when
|
|
3
|
-
* the shape
|
|
4
|
-
* silently drops envelopes written under a different
|
|
3
|
+
* the shape or field semantics change in a backwards-incompatible way;
|
|
4
|
+
* `readVersionedJSON` silently drops envelopes written under a different
|
|
5
|
+
* version.
|
|
6
|
+
*
|
|
7
|
+
* v2: `sizing` switched from a snapshot of the (vestigial) `columnSizing`
|
|
8
|
+
* map to the real per-`ColumnDef` `width` of user-resized columns.
|
|
5
9
|
*/
|
|
6
|
-
export const TABLE_LAYOUT_SCHEMA_VERSION =
|
|
10
|
+
export const TABLE_LAYOUT_SCHEMA_VERSION = 2;
|
|
7
11
|
/**
|
|
8
12
|
* Conservative typeguard for a persisted `JSONTableLayout`. localStorage
|
|
9
13
|
* is same-origin-writable so the decoded value is untrusted; this checks
|
|
@@ -1826,11 +1826,23 @@ export class TableContext {
|
|
|
1826
1826
|
left: [...(this.columnPinning.left?.entries() ?? [])],
|
|
1827
1827
|
right: [...(this.columnPinning.right?.entries() ?? [])],
|
|
1828
1828
|
};
|
|
1829
|
+
// Width state lives on each `ColumnDef` (`width` + `userResized`),
|
|
1830
|
+
// not in `columnSizing`: the grid template reads the def fields and
|
|
1831
|
+
// the resize-handle drag writes them. Persist only columns the user
|
|
1832
|
+
// has actually resized; the rest keep their factory-default width.
|
|
1833
|
+
// Reading `userResized` / `width` here also makes the auto-persist
|
|
1834
|
+
// `$effect` re-run on every drag-resize.
|
|
1835
|
+
const sizing = {};
|
|
1836
|
+
for (const def of this.columnDefs) {
|
|
1837
|
+
if (def.userResized) {
|
|
1838
|
+
sizing[def.id] = def.width;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1829
1841
|
return {
|
|
1830
1842
|
columnOrder,
|
|
1831
1843
|
visibility: { ...this.columnVisibility },
|
|
1832
1844
|
pinning,
|
|
1833
|
-
sizing
|
|
1845
|
+
sizing,
|
|
1834
1846
|
};
|
|
1835
1847
|
}
|
|
1836
1848
|
/**
|
|
@@ -1863,7 +1875,7 @@ export class TableContext {
|
|
|
1863
1875
|
return def;
|
|
1864
1876
|
});
|
|
1865
1877
|
this.columnDefs = ordered;
|
|
1866
|
-
// Visibility
|
|
1878
|
+
// Visibility: keep only ids still present in the table.
|
|
1867
1879
|
const visibility = {};
|
|
1868
1880
|
for (const [id, visible] of Object.entries(layout.visibility)) {
|
|
1869
1881
|
if (byId.has(id)) {
|
|
@@ -1871,13 +1883,16 @@ export class TableContext {
|
|
|
1871
1883
|
}
|
|
1872
1884
|
}
|
|
1873
1885
|
this.columnVisibility = visibility;
|
|
1874
|
-
|
|
1886
|
+
// Sizing: restore user-resized widths onto the matching defs. The
|
|
1887
|
+
// grid template reads `def.width` + `def.userResized`, so set both;
|
|
1888
|
+
// columns absent from the snapshot keep their factory default.
|
|
1875
1889
|
for (const [id, width] of Object.entries(layout.sizing)) {
|
|
1876
|
-
|
|
1877
|
-
|
|
1890
|
+
const def = byId.get(id);
|
|
1891
|
+
if (def) {
|
|
1892
|
+
def.width = width;
|
|
1893
|
+
def.userResized = true;
|
|
1878
1894
|
}
|
|
1879
1895
|
}
|
|
1880
|
-
this.columnSizing = sizing;
|
|
1881
1896
|
this.columnPinning = adaptJSONPinningState(layout.pinning);
|
|
1882
1897
|
}
|
|
1883
1898
|
/**
|