@lavalogic/scoria 0.37.55 → 0.38.0

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.
Files changed (49) hide show
  1. package/dist/Components/Table/ARCHITECTURE.md +22 -22
  2. package/dist/Components/Table/Misc/ColumnPanel.svelte +1 -61
  3. package/dist/Components/Table/Misc/ColumnPanel.svelte.d.ts +2 -24
  4. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +3 -92
  5. package/dist/Components/Table/Misc/ColumnPanelModal.svelte.d.ts +2 -24
  6. package/dist/Components/Table/Misc/ColumnPanelModalProps.d.ts +2 -2
  7. package/dist/Components/Table/Misc/TableConfigurationModal.svelte +644 -0
  8. package/dist/Components/Table/Misc/TableConfigurationModal.svelte.d.ts +26 -0
  9. package/dist/Components/Table/Misc/TableConfigurationModalProps.d.ts +13 -0
  10. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +27 -0
  11. package/dist/Components/Table/Misc/TableSidebar.svelte +45 -2
  12. package/dist/Components/Table/Misc/TableViewDropdown.svelte +175 -0
  13. package/dist/Components/Table/Misc/TableViewDropdown.svelte.d.ts +25 -0
  14. package/dist/Components/Table/SubApis.svelte.js +0 -41
  15. package/dist/Components/Table/Types/Columns/JSONTableLayout.d.ts +5 -5
  16. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.d.ts +5 -5
  17. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.js +5 -5
  18. package/dist/Components/Table/Types/Context/PreferencesState.svelte.d.ts +106 -147
  19. package/dist/Components/Table/Types/Context/PreferencesState.svelte.js +156 -203
  20. package/dist/Components/Table/Types/Context/TableContext.svelte.d.ts +323 -44
  21. package/dist/Components/Table/Types/Context/TableContext.svelte.js +687 -187
  22. package/dist/Components/Table/Types/Context/TableInitOptions.d.ts +9 -0
  23. package/dist/Components/Table/Types/Context/ViewState.svelte.d.ts +152 -0
  24. package/dist/Components/Table/Types/Context/ViewState.svelte.js +223 -0
  25. package/dist/Components/Table/Types/ExpandedPanel.d.ts +9 -5
  26. package/dist/Components/Table/Types/ExpandedPanel.js +9 -5
  27. package/dist/Components/Table/Types/Persistence/DatatableView.d.ts +95 -0
  28. package/dist/Components/Table/Types/Persistence/DatatableViewEnvelope.d.ts +21 -0
  29. package/dist/Components/Table/Types/Persistence/DatatableViewEnvelope.js +1 -0
  30. package/dist/Components/Table/Types/Persistence/DatatableViewKind.d.ts +10 -0
  31. package/dist/Components/Table/Types/Persistence/DatatableViewKind.js +1 -0
  32. package/dist/Components/Table/Types/Persistence/JSONActiveView.d.ts +58 -0
  33. package/dist/Components/Table/Types/Persistence/JSONActiveView.js +41 -0
  34. package/dist/Components/Table/Types/Persistence/JSONTableFilter.d.ts +53 -0
  35. package/dist/Components/Table/Types/Persistence/JSONTableFilter.js +42 -0
  36. package/dist/Components/Table/Types/Persistence/RemoteTableLayoutAdapter.d.ts +47 -0
  37. package/dist/Components/Table/Types/Persistence/RemoteTableLayoutAdapter.js +1 -0
  38. package/dist/Components/Table/Types/Public/CreateTableOptions.d.ts +10 -0
  39. package/dist/Components/Table/Types/Public/TablePersistence.d.ts +1 -4
  40. package/dist/Components/Table/Types/Public/TableSubApis.d.ts +0 -18
  41. package/dist/Components/Table/Types/Public/index.d.ts +1 -1
  42. package/dist/Components/Table/createTable.svelte.js +2 -0
  43. package/dist/index.d.ts +8 -1
  44. package/dist/index.js +9 -0
  45. package/package.json +1 -1
  46. package/dist/Components/Table/Types/Columns/Definitions/ColumnDefSet.d.ts +0 -20
  47. package/dist/Components/Table/Types/Columns/Definitions/JSONColumnDefSet.d.ts +0 -25
  48. /package/dist/Components/Table/{Types/Columns/Definitions/ColumnDefSet.js → Misc/TableConfigurationModalProps.js} +0 -0
  49. /package/dist/Components/Table/Types/{Columns/Definitions/JSONColumnDefSet.js → Persistence/DatatableView.js} +0 -0
@@ -1,25 +1,8 @@
1
1
  import { dev } from '$app/environment';
2
2
  import { readJSON, readVersionedJSON, writeJSON } from '../../../../Helpers/Storage.js';
3
3
  import { isJSONTableLayout, TABLE_LAYOUT_SCHEMA_VERSION, } from '../Columns/JSONTableLayout.js';
4
- /**
5
- * Conservative typeguard for the persisted preset list. Storage is
6
- * same-origin-writable so only the array shape can be trusted;
7
- * downstream `reassignDefsToPresets` performs deeper checks and drops
8
- * presets that fail. Asserts the outer envelope plus the bare minimum
9
- * per entry: a string `name` and an array `columnDefs`.
10
- */
11
- export function isJSONColumnDefSetArray(raw) {
12
- if (!Array.isArray(raw)) {
13
- return false;
14
- }
15
- return raw.every((entry) => {
16
- if (typeof entry !== 'object' || entry === null) {
17
- return false;
18
- }
19
- const candidate = entry;
20
- return typeof candidate.name === 'string' && Array.isArray(candidate.columnDefs);
21
- });
22
- }
4
+ import { isJSONActiveView, TABLE_ACTIVE_VIEW_SCHEMA_VERSION, } from '../Persistence/JSONActiveView.js';
5
+ import { isJSONTableFilter, TABLE_FILTER_SCHEMA_VERSION, } from '../Persistence/JSONTableFilter.js';
23
6
  /**
24
7
  * Typeguard for the persisted user-level table settings blob. Every
25
8
  * field is optional, but every present field must be boolean; anything
@@ -38,34 +21,22 @@ export function isSavedTableSettings(raw) {
38
21
  optionalBoolean('copyWithHeaders') &&
39
22
  optionalBoolean('commaSeparatedCopy'));
40
23
  }
41
- /**
42
- * Typeguard for a non-empty string. Used for the persisted selected
43
- * preset name slot.
44
- */
45
- export function isNonEmptyString(raw) {
46
- return typeof raw === 'string' && raw.length > 0;
47
- }
48
24
  /**
49
25
  * Sub-context owning every persistence / preferences concern:
50
26
  *
51
- * - `_columnPresets` (persisted preset list `$state`);
52
- * - `_selectedPreset` (currently-active preset `$state`);
53
27
  * - `userIdSegment` (sanitised user-id segment used in storage keys;
54
28
  * `$derived` over `getUserId()`);
55
- * - `saveColumnPreset(name)` (snapshot the live layout, persist the
56
- * list, mark the new preset as selected);
57
- * - `setSelectedPreset(preset)` (apply the preset to the live layout
58
- * and persist the selected name);
59
- * - `loadPresets()` / `loadSelectedPresetName()` /
60
- * `loadSavedSettings()` (boot-time hydration helpers; the parent
61
- * owns the orchestration because it depends on
62
- * `setupDefaultColumnDefs` running first);
63
- * - `clearPresetsAndSelection()` (drop both persisted slots);
64
- * - `persistSettings(settings)` (write the per-table TableSettings
65
- * blob);
66
- * - schema-validation typeguards (`isJSONColumnDefSetArray`,
67
- * `isSavedTableSettings`, `isNonEmptyString`) - exported alongside
68
- * the class.
29
+ * - `loadSavedSettings()` / `persistSettings()` (the per-table
30
+ * TableSettings blob: compact, alternate rows, etc.);
31
+ * - `loadLayout()` / `persistLayout()` / `clearLayout()` (the
32
+ * auto-saved live-layout working copy);
33
+ * - `loadActiveView()` / `persistActiveView()` / `clearActiveView()`
34
+ * (the DB-backed view selection envelope);
35
+ * - the filter counterparts (`loadFilter` / `persistFilter` /
36
+ * `clearFilter`, `loadActiveFilterView` / `persistActiveFilterView` /
37
+ * `clearActiveFilterView`);
38
+ * - schema-validation typeguards (`isSavedTableSettings`) - exported
39
+ * alongside the class.
69
40
  *
70
41
  * Every `localStorage` access is funnelled through this sub-context so
71
42
  * the `scoria-table:<user>:<tableName>:<suffix>` namespace convention
@@ -73,15 +44,11 @@ export function isNonEmptyString(raw) {
73
44
  */
74
45
  export class PreferencesState {
75
46
  _deps;
76
- _columnPresets = $state([]);
77
- //FIXME why is this allowed to be undefined? Retained as
78
- // `undefined`-capable for backwards source compatibility.
79
- _selectedPreset = $state();
80
47
  /**
81
48
  * Sanitised segment of the persisted-storage key path. Resolves to
82
49
  * `getUserId() ?? 'anon'` so different users on a shared browser
83
- * cannot read each other's column presets, and so anonymous
84
- * sessions still have a stable namespace.
50
+ * cannot read each other's persisted layout / filter state, and so
51
+ * anonymous sessions still have a stable namespace.
85
52
  *
86
53
  * The `$derived.by` form is required here (over plain
87
54
  * `$derived(expr)`) because class-field initialisers run BEFORE
@@ -98,77 +65,19 @@ export class PreferencesState {
98
65
  this._deps = deps;
99
66
  }
100
67
  /**
101
- * The current list of saved column presets. Setter is private to
102
- * the sub-context; the parent writes through `setPresets` during
103
- * the boot-time hydration flow.
104
- */
105
- get columnPresets() {
106
- return this._columnPresets;
107
- }
108
- /**
109
- * Writer for `columnPresets`. Public so the parent boot flow can
110
- * publish the hydrated list; not part of the consumer-facing
111
- * surface (consumer-facing writes go through `saveColumnPreset`).
112
- */
113
- setPresets(v) {
114
- this._columnPresets = v;
115
- }
116
- /**
117
- * The currently-active preset (the one whose name is persisted in
118
- * the `selected-preset-name` slot). Setter is public so the parent
119
- * boot flow can publish the resolved preset; the consumer-facing
120
- * writer is `setSelectedPreset` below (which also persists the
121
- * name).
122
- */
123
- get selectedPreset() {
124
- return this._selectedPreset;
125
- }
126
- set selectedPreset(v) {
127
- this._selectedPreset = v;
128
- }
129
- /**
130
- * Build the canonical `scoria-table:<user>:<tableName>:<suffix>`
131
- * storage key. Centralised so every persisted-state slot reaches
132
- * for the same namespacing convention; the `<user>` segment is
133
- * `getUserId() ?? 'anon'` (see `userIdSegment`).
134
- */
135
- storageKey(suffix) {
136
- return `scoria-table:${this.userIdSegment}:${this._deps.getTableName()}:${suffix}`;
137
- }
138
- /**
139
- * Boot-time hydration helper. Reads the persisted preset list from
140
- * `localStorage`, validates it against `isJSONColumnDefSetArray`,
141
- * and returns the parsed array (or an empty array on every failure
142
- * path). The parent runs this once `setupDefaultColumnDefs` has
143
- * completed so the live `columnDefs` can be compared against the
144
- * persisted snapshot.
68
+ * Build the canonical `scoria-table:<user>:<scope>:<suffix>` storage
69
+ * key. Centralised so every persisted-state slot reaches for the same
70
+ * namespacing convention; the `<user>` segment is `getUserId() ??
71
+ * 'anon'` (see `userIdSegment`).
145
72
  *
146
- * `localStorage` access is gated by the parent's `browser` check;
147
- * this method assumes the caller already verified the environment.
73
+ * The `<scope>` segment is the host-supplied `datatableUuid` when one
74
+ * is present, falling back to the table `name`. Preferring the UUID
75
+ * keeps two tables that share a `name` option from colliding on the
76
+ * same localStorage slots.
148
77
  */
149
- loadPresets() {
150
- return readJSON(localStorage, this.storageKey('presets'), isJSONColumnDefSetArray, []);
151
- }
152
- /**
153
- * Boot-time hydration helper. Reads the persisted selected-preset
154
- * name. Returns `null` for missing / empty / storage-error slots
155
- * so the parent can fall back to the first preset (or the default).
156
- * Emits a dev-only warning on `getItem` failure.
157
- */
158
- loadSelectedPresetName() {
159
- let savedPresetName = null;
160
- try {
161
- savedPresetName = localStorage.getItem(this.storageKey('selected-preset-name'));
162
- }
163
- catch (e) {
164
- if (dev) {
165
- console.warn('selected-preset-name read failed:', e);
166
- }
167
- }
168
- if (!isNonEmptyString(savedPresetName)) {
169
- return null;
170
- }
171
- return savedPresetName;
78
+ storageKey(suffix) {
79
+ const scope = this._deps.getDatatableUuid() ?? this._deps.getTableName();
80
+ return `scoria-table:${this.userIdSegment}:${scope}:${suffix}`;
172
81
  }
173
82
  /**
174
83
  * Boot-time hydration helper. Reads the persisted per-table
@@ -184,15 +93,6 @@ export class PreferencesState {
184
93
  }
185
94
  return saved;
186
95
  }
187
- /**
188
- * Drop the persisted presets and selected-preset-name slots. Used
189
- * by the parent's boot flow when the saved snapshot disagrees with
190
- * the live column-def shape (schema drift or column-set change).
191
- */
192
- clearPresetsAndSelection() {
193
- localStorage.removeItem(this.storageKey('presets'));
194
- localStorage.removeItem(this.storageKey('selected-preset-name'));
195
- }
196
96
  /**
197
97
  * Boot-time hydration helper. Reads the auto-saved live-layout
198
98
  * snapshot (column order / visibility / pinning / sizing the user
@@ -241,98 +141,151 @@ export class PreferencesState {
241
141
  }
242
142
  }
243
143
  /**
244
- * Persist the selected-preset name as plain text (no JSON
245
- * envelope - the slot is a single string by design).
144
+ * Boot-time hydration helper. Reads the persisted active-view
145
+ * envelope (which saved view, if any, the live working copy belongs
146
+ * to, plus its dirty flag). Returns `null` for a missing slot, a
147
+ * schema-version mismatch, or a value that fails validation, so the
148
+ * parent can fall back to the Default view.
149
+ *
150
+ * `localStorage` access is gated by the parent's `browser` check;
151
+ * this method assumes the caller already verified the environment.
246
152
  */
247
- persistSelectedPresetName(name) {
248
- localStorage.setItem(this.storageKey('selected-preset-name'), name);
153
+ loadActiveView() {
154
+ // Reference-equality sentinel: `readVersionedJSON` demands a
155
+ // fallback of the value type, so a distinct object instance is
156
+ // used to distinguish "no usable slot" from a real snapshot.
157
+ const fallback = {
158
+ active: { kind: 'default' },
159
+ dirty: false,
160
+ };
161
+ const saved = readVersionedJSON(localStorage, this.storageKey('active-view'), TABLE_ACTIVE_VIEW_SCHEMA_VERSION, isJSONActiveView, fallback);
162
+ return saved === fallback ? null : saved;
249
163
  }
250
164
  /**
251
- * Persist the per-table settings blob (`isCompact`,
252
- * `singleClickEditing`, etc.) as JSON.
165
+ * Persist the active-view envelope. Written by the parent (debounced,
166
+ * alongside the working-copy layout) on every layout change, wrapped
167
+ * in a versioned envelope so a future schema bump drops stale
168
+ * snapshots cleanly. Storage failures are swallowed inside
169
+ * `writeJSON`.
253
170
  */
254
- persistSettings(settings) {
255
- localStorage.setItem(this.storageKey('settings'), JSON.stringify(settings));
171
+ persistActiveView(v) {
172
+ writeJSON(localStorage, this.storageKey('active-view'), v, TABLE_ACTIVE_VIEW_SCHEMA_VERSION);
256
173
  }
257
174
  /**
258
- * Persist the full preset list as JSON. Each preset's
259
- * `columnPinningState` (Map-based) is flattened into entry arrays
260
- * because Map does not survive `JSON.stringify`; the reader (via
261
- * `loadPresets` -> `reassignDefsToPresets`) rehydrates it through
262
- * `adaptJSONPinningState`.
175
+ * Drop the persisted active-view envelope. Called when table state is
176
+ * explicitly reset; the next mount falls back to the Default view.
263
177
  */
264
- persistPresets(presets) {
265
- const jsonFriendly = presets.map((preset) => {
266
- return {
267
- ...preset,
268
- columnDefs: preset.columnDefs.map((it) => {
269
- return { ...it.toJSON(), header: it.header.toString() };
270
- }),
271
- columnPinningState: {
272
- left: [...(preset.columnPinningState.left?.entries() ?? [])],
273
- right: [...(preset.columnPinningState.right?.entries() ?? [])],
274
- },
275
- };
276
- });
277
- localStorage.setItem(this.storageKey('presets'), JSON.stringify(jsonFriendly));
178
+ clearActiveView() {
179
+ try {
180
+ localStorage.removeItem(this.storageKey('active-view'));
181
+ }
182
+ catch (e) {
183
+ if (dev) {
184
+ console.warn('table active-view slot clear failed:', e);
185
+ }
186
+ }
278
187
  }
279
188
  /**
280
- * Apply a saved column preset to the live table layout and persist
281
- * the selection by name:
282
- *
283
- * 1. Replace `columnDefs`, `columnVisibility`, `columnPinning`.
284
- * 2. Forward the preset's `sortingState` to the data-repository
285
- * updater (no-op when the repo has no `updateSorting` hook).
286
- * 3. Update `selectedPreset`.
287
- * 4. Persist the preset name to `selected-preset-name`.
189
+ * Boot-time hydration helper. Reads the auto-saved live-filter
190
+ * snapshot (the per-column filter values and modes the user built up
191
+ * in the quick-filter row / advanced filter panel without saving a
192
+ * named view). Returns `null` for a missing slot, a schema-version
193
+ * mismatch, or a value that fails validation, so the parent can fall
194
+ * back to the empty filter state.
288
195
  *
289
- * Bound as a class property so callers (notably the toolbar
290
- * dropdown) can pass the reference around without binding `this`.
196
+ * The filter counterpart of `loadLayout`. `localStorage` access is
197
+ * gated by the parent's `browser` check; this method assumes the
198
+ * caller already verified the environment.
291
199
  */
292
- setSelectedPreset = (preset) => {
293
- if (this._deps.getDebug()) {
294
- console.log('setting selected preset:', preset);
295
- }
296
- this._deps.setColumnDefs(preset.columnDefs);
297
- this._deps.applySortingState(preset.sortingState);
298
- this._deps.setColumnVisibility(preset.visibilityState);
299
- this._deps.setColumnPinning(preset.columnPinningState);
300
- this._selectedPreset = preset;
301
- this.persistSelectedPresetName(preset.name);
302
- };
200
+ loadFilter() {
201
+ // Reference-equality sentinel: `readVersionedJSON` demands a
202
+ // fallback of the value type, so a distinct object instance is
203
+ // used to distinguish "no usable slot" from a real snapshot.
204
+ const fallback = {
205
+ filters: [],
206
+ modes: {},
207
+ };
208
+ const saved = readVersionedJSON(localStorage, this.storageKey('filter'), TABLE_FILTER_SCHEMA_VERSION, isJSONTableFilter, fallback);
209
+ return saved === fallback ? null : saved;
210
+ }
303
211
  /**
304
- * Snapshot the current column layout into a new preset, replacing
305
- * any existing preset whose name matches case-insensitively, and
306
- * persist the full preset list to `localStorage`. Marks the new
307
- * preset as selected (which also persists its name). No-op when
308
- * `name` is empty.
212
+ * Persist the live-filter snapshot. Written by the parent (debounced)
213
+ * on every filter value / mode change, wrapped in a versioned
214
+ * envelope so a future schema bump drops stale snapshots cleanly.
215
+ * Storage failures are swallowed inside `writeJSON`. The filter
216
+ * counterpart of `persistLayout`.
309
217
  */
310
- saveColumnPreset = (name) => {
311
- if (!name) {
312
- return;
218
+ persistFilter(filter) {
219
+ writeJSON(localStorage, this.storageKey('filter'), filter, TABLE_FILTER_SCHEMA_VERSION);
220
+ }
221
+ /**
222
+ * Drop the persisted live-filter snapshot. Called when the snapshot
223
+ * no longer matches the table's column set (schema drift) or when
224
+ * table state is explicitly reset. The filter counterpart of
225
+ * `clearLayout`.
226
+ */
227
+ clearFilter() {
228
+ try {
229
+ localStorage.removeItem(this.storageKey('filter'));
313
230
  }
314
- const visibilityState = this._deps.getColumnVisibility();
315
- const columnPinningState = this._deps.getColumnPinning();
316
- const columnDefs = this._deps.getColumnDefs();
317
- const sortingState = this._deps.getSortingArray() ?? [];
318
- const newPreset = {
319
- columnDefs,
320
- name,
321
- visibilityState,
322
- columnPinningState,
323
- sortingState,
231
+ catch (e) {
232
+ if (dev) {
233
+ console.warn('table filter slot clear failed:', e);
234
+ }
235
+ }
236
+ }
237
+ /**
238
+ * Boot-time hydration helper. Reads the persisted *filter*
239
+ * active-view envelope (which saved filter view, if any, the live
240
+ * filter working copy belongs to, plus its dirty flag). Returns
241
+ * `null` for a missing slot, a schema-version mismatch, or a value
242
+ * that fails validation, so the parent can fall back to the Default
243
+ * filter view. The filter counterpart of `loadActiveView`.
244
+ *
245
+ * `localStorage` access is gated by the parent's `browser` check;
246
+ * this method assumes the caller already verified the environment.
247
+ */
248
+ loadActiveFilterView() {
249
+ // Reference-equality sentinel: `readVersionedJSON` demands a
250
+ // fallback of the value type, so a distinct object instance is
251
+ // used to distinguish "no usable slot" from a real snapshot.
252
+ const fallback = {
253
+ active: { kind: 'default' },
254
+ dirty: false,
324
255
  };
325
- const lowercasePresetname = name.toLocaleLowerCase();
326
- const indexToReplace = this._columnPresets.findIndex((oldPreset) => oldPreset.name.toLocaleLowerCase() === lowercasePresetname);
327
- const newPresets = this._columnPresets.concat();
328
- if (indexToReplace > -1) {
329
- newPresets[indexToReplace] = newPreset;
256
+ const saved = readVersionedJSON(localStorage, this.storageKey('active-filter-view'), TABLE_ACTIVE_VIEW_SCHEMA_VERSION, isJSONActiveView, fallback);
257
+ return saved === fallback ? null : saved;
258
+ }
259
+ /**
260
+ * Persist the *filter* active-view envelope. Written by the parent
261
+ * (debounced, alongside the working-copy filter) on every filter
262
+ * change, wrapped in a versioned envelope so a future schema bump
263
+ * drops stale snapshots cleanly. Storage failures are swallowed
264
+ * inside `writeJSON`. The filter counterpart of `persistActiveView`.
265
+ */
266
+ persistActiveFilterView(v) {
267
+ writeJSON(localStorage, this.storageKey('active-filter-view'), v, TABLE_ACTIVE_VIEW_SCHEMA_VERSION);
268
+ }
269
+ /**
270
+ * Drop the persisted *filter* active-view envelope. Called when table
271
+ * state is explicitly reset; the next mount falls back to the Default
272
+ * filter view. The filter counterpart of `clearActiveView`.
273
+ */
274
+ clearActiveFilterView() {
275
+ try {
276
+ localStorage.removeItem(this.storageKey('active-filter-view'));
330
277
  }
331
- else {
332
- newPresets.push(newPreset);
278
+ catch (e) {
279
+ if (dev) {
280
+ console.warn('table active-filter-view slot clear failed:', e);
281
+ }
333
282
  }
334
- this._columnPresets = newPresets;
335
- this.persistPresets(newPresets);
336
- this.setSelectedPreset(newPreset);
337
- };
283
+ }
284
+ /**
285
+ * Persist the per-table settings blob (`isCompact`,
286
+ * `singleClickEditing`, etc.) as JSON.
287
+ */
288
+ persistSettings(settings) {
289
+ localStorage.setItem(this.storageKey('settings'), JSON.stringify(settings));
290
+ }
338
291
  }