@lavalogic/scoria 0.37.53 → 0.37.54

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.
@@ -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, generateUUID, syntheticColumnId } from '../../Helpers/Helpers.svelte.js';
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 = mintSyntheticId('display', opts.idHint);
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 = mintSyntheticId('bubble', opts.idHint);
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 = mintSyntheticId('progress', opts.idHint);
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 = mintSyntheticId('validity', opts?.idHint);
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: mintSyntheticId('expand'),
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 `id` and `header`
6
- * because the class generates a stable id via `generateUUID` and pins
7
- * the header to `'Actions'`.
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: the generated id uses `generateUUID()` (UUIDv7); two `ActionsDef`
29
- * instances constructed in the same render pass will have distinct ids
30
- * so consumers must keep a single instance per table.
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: the generated id uses `generateUUID()` (UUIDv7); two `ActionsDef`
10
- * instances constructed in the same render pass will have distinct ids
11
- * so consumers must keep a single instance per table.
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,
@@ -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'` and the id is generated via `generateUUID`
19
- * (UUIDv7).
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
  *
@@ -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'` and the id is generated via `generateUUID`
8
- * (UUIDv7).
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,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lavalogic/scoria",
3
3
  "description": "Svelte components used for the FloWMS Web Frontend",
4
- "version": "0.37.53",
4
+ "version": "0.37.54",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },