@lavalogic/scoria 0.38.10 → 0.38.12
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/README.md +58 -58
- package/dist/Components/AccordionGroup.svelte +46 -46
- package/dist/Components/AccordionGroupButton.svelte +76 -76
- package/dist/Components/Action.svelte +136 -136
- package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +702 -702
- package/dist/Components/Base/BaseModal.svelte +125 -125
- package/dist/Components/Base/ContextWrapper.svelte +72 -72
- package/dist/Components/Base/Pagination.svelte +198 -198
- package/dist/Components/Base/Snippets.svelte +36 -36
- package/dist/Components/BigRadioSet.svelte +38 -38
- package/dist/Components/Bubble.svelte +101 -101
- package/dist/Components/Button.svelte +209 -209
- package/dist/Components/Checkbox.svelte +86 -86
- package/dist/Components/Contexts/ContextMenu.svelte +164 -164
- package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -15
- package/dist/Components/Contexts/ContextMenuOption.svelte +60 -60
- package/dist/Components/Contexts/ToastContainer.svelte +65 -65
- package/dist/Components/Contexts/Tooltip.svelte +37 -37
- package/dist/Components/DataMatrixIcon.svelte +112 -112
- package/dist/Components/DateInput.svelte +148 -148
- package/dist/Components/DatePicker.svelte +300 -300
- package/dist/Components/DesktopModal.svelte +90 -90
- package/dist/Components/Dial.svelte +204 -204
- package/dist/Components/DimensionInput.svelte +170 -170
- package/dist/Components/DragMenu/DragMenuHolder.svelte +119 -119
- package/dist/Components/DragMenu/DraggableCard.svelte +77 -77
- package/dist/Components/FileCard.svelte +24 -24
- package/dist/Components/FileUpload.svelte +236 -236
- package/dist/Components/GridInputSet.svelte +29 -29
- package/dist/Components/HorizontalTabGroup.svelte +82 -82
- package/dist/Components/HorizontalTabGroupButton.svelte +44 -44
- package/dist/Components/Icon.svelte +128 -128
- package/dist/Components/Icons.js +357 -357
- package/dist/Components/LoadingAnimation.svelte +29 -29
- package/dist/Components/LoadingModal.svelte +45 -45
- package/dist/Components/LoadingOverlay.svelte +25 -25
- package/dist/Components/Modal.svelte +258 -258
- package/dist/Components/MultiSelect.svelte +220 -220
- package/dist/Components/Nav/NavServiceMenuList.svelte +43 -43
- package/dist/Components/Nav/NavTab.svelte +62 -62
- package/dist/Components/Nav/ServicesNav.svelte +80 -80
- package/dist/Components/NumberInput.svelte +157 -157
- package/dist/Components/PageHeading.svelte +25 -25
- package/dist/Components/PasswordInput.svelte +88 -88
- package/dist/Components/Portal.svelte +36 -36
- package/dist/Components/QuerySelect.svelte +366 -366
- package/dist/Components/SingleSelect.svelte +250 -250
- package/dist/Components/StepButton.svelte +70 -70
- package/dist/Components/StepForm.svelte +140 -140
- package/dist/Components/Switch.svelte +69 -69
- package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +51 -51
- package/dist/Components/Table/Body/Rows/ColumnList.svelte +59 -59
- package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +79 -79
- package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +120 -120
- package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +105 -105
- package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +87 -87
- package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +126 -126
- package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +159 -159
- package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +246 -246
- package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +216 -216
- package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +179 -179
- package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +197 -197
- package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +190 -190
- package/dist/Components/Table/Body/Rows/TableRow.svelte +290 -290
- package/dist/Components/Table/Headers/HighlightAllHeader.svelte +41 -41
- package/dist/Components/Table/Headers/SelectAllHeader.svelte +30 -30
- package/dist/Components/Table/Misc/FilterInput.svelte +37 -16
- package/dist/Components/Table/Misc/SidePanel.svelte +35 -35
- package/dist/Components/Table/Misc/TableConfigurationModal.svelte +71 -0
- package/dist/Components/Table/Misc/ToolboxPanel.svelte +100 -100
- package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +107 -107
- package/dist/Components/Table/Misc/UpdateViewOptionsModal.svelte +225 -0
- package/dist/Components/Table/Misc/UpdateViewOptionsModal.svelte.d.ts +26 -0
- package/dist/Components/Table/Misc/UpdateViewOptionsModalProps.d.ts +21 -0
- package/dist/Components/Table/Misc/UpdateViewOptionsModalProps.js +1 -0
- package/dist/Components/Table/Types/Context/TableContext.svelte.d.ts +49 -1
- package/dist/Components/Table/Types/Context/TableContext.svelte.js +142 -1
- package/dist/Components/Table/Types/DataRepository/BuiltInRemoteRepository.svelte.d.ts +2 -2
- package/dist/Components/Table/Types/DataRepository/BuiltInRemoteRepository.svelte.js +20 -2
- package/dist/Components/Table/Types/DataRepository/IDataRepository.d.ts +9 -1
- package/dist/Components/Table/Types/DataRepository/ILocalDataRepository.d.ts +1 -1
- package/dist/Components/Table/Types/DataRepository/IRemoteDataRepository.d.ts +1 -1
- package/dist/Components/Table/Types/DataRepository/TableDataRepository.svelte.d.ts +1 -1
- package/dist/Components/Table/Types/Persistence/DatatableView.d.ts +25 -0
- package/dist/Components/Test/TestContextConsumer.svelte +13 -13
- package/dist/Components/TextArea.svelte +130 -130
- package/dist/Components/TextInput.svelte +176 -176
- package/dist/Components/ThreeStateRadio.svelte +131 -131
- package/dist/Components/Touch/BoolCard.svelte +45 -45
- package/dist/Components/Touch/DateModal.svelte +209 -209
- package/dist/Components/Touch/EditCard.svelte +59 -59
- package/dist/Components/Touch/InfoCard.svelte +31 -31
- package/dist/Components/Touch/InfoTextCard.svelte +28 -28
- package/dist/Components/Touch/ModalExplanation.svelte +17 -17
- package/dist/Components/Touch/NumberKeyboard.svelte +225 -225
- package/dist/Components/Touch/NumberModal.svelte +79 -79
- package/dist/Components/Touch/PlusMinusCard.svelte +46 -46
- package/dist/Components/Touch/SummaryCard.svelte +93 -93
- package/dist/Components/Touch/TextAreaModal.svelte +95 -95
- package/dist/Components/Touch/TextModal.svelte +106 -106
- package/dist/Components/Touch/TouchCard.svelte +33 -33
- package/dist/Components/Touch/TouchModal.svelte +274 -274
- package/dist/Components/Touch/UtilityButton.svelte +109 -109
- package/dist/Components/VerticalTabGroup.svelte +79 -79
- package/dist/Components/VerticalTabGroupButton.svelte +58 -58
- package/dist/Helpers/Datamatrix.d.ts +19 -19
- package/dist/Helpers/Datamatrix.js +291 -291
- package/dist/Types/Examples/ExampleModal.svelte +35 -35
- package/dist/index.d.ts +1 -1
- package/dist/scss/_basics.scss +12 -12
- package/dist/scss/_colours.scss +134 -134
- package/dist/scss/_motion.scss +57 -57
- package/dist/scss/_sizing.scss +85 -85
- package/dist/scss/_transitions.scss +8 -8
- package/package.json +1 -1
|
@@ -2191,10 +2191,33 @@ export class TableContext {
|
|
|
2191
2191
|
// plain strings - look up cleanly against the live column set.
|
|
2192
2192
|
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
2193
2193
|
const liveColumnIds = new Set(this.columnDefs.map((def) => def.id));
|
|
2194
|
+
// 1. Clear the current filter VALUES. Without this, switching
|
|
2195
|
+
// saved filter views would only ADD entries from the new view
|
|
2196
|
+
// on top of whatever the old view set - any column the old
|
|
2197
|
+
// view filtered but the new one does not would keep its old
|
|
2198
|
+
// value and `FilterInput` would still show it.
|
|
2194
2199
|
if (typeof repo.resetFilters === 'function') {
|
|
2195
2200
|
repo.resetFilters();
|
|
2196
2201
|
}
|
|
2197
|
-
//
|
|
2202
|
+
// 2. Clear the current filter MODES the same way. `resetFilters`
|
|
2203
|
+
// deliberately keeps modes (so a "Clear filters" toolbar
|
|
2204
|
+
// action retains operator selections); but applying a saved
|
|
2205
|
+
// view is a *replace*, so a stale mode (e.g. the old view's
|
|
2206
|
+
// "Empty" operator) leaking into the new view would leave
|
|
2207
|
+
// `FilterInput`'s mode dropdown out of sync with the value it
|
|
2208
|
+
// renders.
|
|
2209
|
+
if (typeof repo.setFilterMode === 'function') {
|
|
2210
|
+
for (const id of [...repo.filterModes.keys()]) {
|
|
2211
|
+
repo.setFilterMode(id, undefined);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
// 3. Clear the QuerySelect typeahead-echo cache so the user
|
|
2215
|
+
// doesn't see a stale label hanging in a `QuerySelect` whose
|
|
2216
|
+
// underlying value just got reset. `_queryValues` is keyed by
|
|
2217
|
+
// `def.id`, the same key space `FilterInput` reads from.
|
|
2218
|
+
this._queryValues.clear();
|
|
2219
|
+
// 4. Apply the saved modes (before values so a value-bearing
|
|
2220
|
+
// mode like "Empty" sees its mode already set).
|
|
2198
2221
|
if (typeof repo.setFilterMode === 'function') {
|
|
2199
2222
|
for (const [id, mode] of Object.entries(filter.modes)) {
|
|
2200
2223
|
// Drop stale column ids and unrecognised mode strings.
|
|
@@ -2203,6 +2226,7 @@ export class TableContext {
|
|
|
2203
2226
|
}
|
|
2204
2227
|
}
|
|
2205
2228
|
}
|
|
2229
|
+
// 5. Apply the saved values.
|
|
2206
2230
|
if (typeof repo.filterBy === 'function') {
|
|
2207
2231
|
for (const entry of filter.filters) {
|
|
2208
2232
|
if (liveColumnIds.has(entry.id)) {
|
|
@@ -2498,6 +2522,123 @@ export class TableContext {
|
|
|
2498
2522
|
devCatch(e);
|
|
2499
2523
|
}
|
|
2500
2524
|
};
|
|
2525
|
+
/**
|
|
2526
|
+
* Save the live state of the table INTO an arbitrary saved view,
|
|
2527
|
+
* overwriting that view's stored body. Unlike `saveActiveView` -
|
|
2528
|
+
* which only works when the target IS the kind's active view -
|
|
2529
|
+
* this accepts any saved view: a "Save Layout" / "Save Filter"
|
|
2530
|
+
* button on a card can overwrite the saved view with the user's
|
|
2531
|
+
* current tweaks regardless of what the active view is.
|
|
2532
|
+
*
|
|
2533
|
+
* Flow:
|
|
2534
|
+
*
|
|
2535
|
+
* 1. capture the live layout / filter (chosen by `view.kind`);
|
|
2536
|
+
* 2. `await remoteLayouts.updateView({ id, body })`;
|
|
2537
|
+
* 3. swap the matching entry in that kind's saved-views list with
|
|
2538
|
+
* the authoritative server return;
|
|
2539
|
+
* 4. if the just-saved view IS the kind's currently active view,
|
|
2540
|
+
* repoint that kind's baseline at the new body and clear the
|
|
2541
|
+
* dirty flag - the dropdown immediately reads "<name>" instead
|
|
2542
|
+
* of "<name> (unsaved)".
|
|
2543
|
+
*
|
|
2544
|
+
* No-op (dev-logged) when remote saved views are disabled. Adapter
|
|
2545
|
+
* rejections are re-thrown so the caller can surface them in the
|
|
2546
|
+
* Table Configuration modal's inline error banner.
|
|
2547
|
+
*/
|
|
2548
|
+
saveLiveStateIntoView = async (view) => {
|
|
2549
|
+
const adapter = this.remoteLayouts;
|
|
2550
|
+
if (!adapter) {
|
|
2551
|
+
if (dev) {
|
|
2552
|
+
console.warn('saveLiveStateIntoView: no adapter - cannot save remotely');
|
|
2553
|
+
}
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
const body = view.kind === 'filter'
|
|
2557
|
+
? { kind: 'filter', filter: this._captureFilter() }
|
|
2558
|
+
: { kind: 'layout', layout: this._captureLayout() };
|
|
2559
|
+
const request = {
|
|
2560
|
+
id: view.id,
|
|
2561
|
+
body,
|
|
2562
|
+
};
|
|
2563
|
+
const updated = await adapter.updateView(request);
|
|
2564
|
+
// Swap the matching entry in the saved-views list so the picker
|
|
2565
|
+
// + modal reflect the new body without an extra refresh round-trip.
|
|
2566
|
+
this._viewState.setSavedViews(view.kind, this._viewState.savedViews(view.kind).map((v) => (v.id === updated.id ? updated : v)));
|
|
2567
|
+
// If the saved-into view IS the active view for its kind,
|
|
2568
|
+
// repoint the baseline so dirty re-compares against the new
|
|
2569
|
+
// body and clears.
|
|
2570
|
+
const active = this._viewState.activeView(view.kind);
|
|
2571
|
+
if (active.kind === 'saved' && active.id === updated.id) {
|
|
2572
|
+
this._viewState.selectSavedView(view.kind, { id: updated.id, name: updated.name });
|
|
2573
|
+
if (view.kind === 'filter') {
|
|
2574
|
+
this._filterBaseline =
|
|
2575
|
+
updated.body.kind === 'filter' ? updated.body.filter : this._captureFilter();
|
|
2576
|
+
this._persistActiveFilterView();
|
|
2577
|
+
}
|
|
2578
|
+
else {
|
|
2579
|
+
this._viewBaseline =
|
|
2580
|
+
updated.body.kind === 'layout' ? updated.body.layout : this._captureLayout();
|
|
2581
|
+
this._persistActiveView();
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
/**
|
|
2586
|
+
* Update a saved view's options - currently its `name` and
|
|
2587
|
+
* `visibility`. The view's body is left untouched (use
|
|
2588
|
+
* `saveLiveStateIntoView` or `saveActiveView` for that). Driven by
|
|
2589
|
+
* the "Update Options" modal on each My-* card.
|
|
2590
|
+
*
|
|
2591
|
+
* Flow:
|
|
2592
|
+
*
|
|
2593
|
+
* 1. `await remoteLayouts.updateView({ id, name?, visibility? })`;
|
|
2594
|
+
* 2. swap the matching entry in that kind's saved-views list with
|
|
2595
|
+
* the authoritative server return so the new name / visibility
|
|
2596
|
+
* are reflected in every reactive surface;
|
|
2597
|
+
* 3. if the renamed view IS the kind's currently active view, push
|
|
2598
|
+
* the new name onto the active-view ref so the quick-switch
|
|
2599
|
+
* dropdown and "active view" label update immediately.
|
|
2600
|
+
*
|
|
2601
|
+
* No-op (dev-logged) when remote saved views are disabled. Adapter
|
|
2602
|
+
* rejections are re-thrown so the caller can surface them in the
|
|
2603
|
+
* modal.
|
|
2604
|
+
*/
|
|
2605
|
+
updateViewOptions = async (view, options) => {
|
|
2606
|
+
const adapter = this.remoteLayouts;
|
|
2607
|
+
if (!adapter) {
|
|
2608
|
+
if (dev) {
|
|
2609
|
+
console.warn('updateViewOptions: no adapter - cannot update remotely');
|
|
2610
|
+
}
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
// Skip the network round-trip entirely when nothing actually
|
|
2614
|
+
// changed. Avoids a no-op PATCH that would still bump the
|
|
2615
|
+
// updated_at timestamp on the backend.
|
|
2616
|
+
const nameChanged = options.name !== undefined && options.name !== view.name;
|
|
2617
|
+
const visibilityChanged = options.visibility !== undefined && options.visibility !== view.visibility;
|
|
2618
|
+
if (!nameChanged && !visibilityChanged) {
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
const request = {
|
|
2622
|
+
id: view.id,
|
|
2623
|
+
...(nameChanged ? { name: options.name } : {}),
|
|
2624
|
+
...(visibilityChanged ? { visibility: options.visibility } : {}),
|
|
2625
|
+
};
|
|
2626
|
+
const updated = await adapter.updateView(request);
|
|
2627
|
+
this._viewState.setSavedViews(view.kind, this._viewState.savedViews(view.kind).map((v) => (v.id === updated.id ? updated : v)));
|
|
2628
|
+
// If the just-edited view is the active one for its kind, push
|
|
2629
|
+
// the new name onto the active-view ref so the dropdown label
|
|
2630
|
+
// reflects the rename without waiting for a refresh.
|
|
2631
|
+
const active = this._viewState.activeView(view.kind);
|
|
2632
|
+
if (active.kind === 'saved' && active.id === updated.id) {
|
|
2633
|
+
this._viewState.selectSavedView(view.kind, { id: updated.id, name: updated.name });
|
|
2634
|
+
if (view.kind === 'filter') {
|
|
2635
|
+
this._persistActiveFilterView();
|
|
2636
|
+
}
|
|
2637
|
+
else {
|
|
2638
|
+
this._persistActiveView();
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2501
2642
|
/**
|
|
2502
2643
|
* Fetch the saved views for this datatable through the injected
|
|
2503
2644
|
* `remoteLayouts` adapter and publish them into `_viewState`, split by
|
|
@@ -65,7 +65,7 @@ export declare class BuiltInRemoteRepository<TRow extends object> extends TableD
|
|
|
65
65
|
private _debounceTimer;
|
|
66
66
|
private _quickFilterFocusCount;
|
|
67
67
|
constructor(_options: BuiltInRemoteRepositoryOptions<TRow>);
|
|
68
|
-
readonly setFilterMode: (id: string, mode: FilterMode) => void;
|
|
68
|
+
readonly setFilterMode: (id: string, mode: FilterMode | undefined) => void;
|
|
69
69
|
readonly filterBy: (id: string, value: unknown) => void;
|
|
70
70
|
readonly resetFilters: () => void;
|
|
71
71
|
/** Jumps to the given 1-based page number, clamped at >= 1. */
|
|
@@ -121,7 +121,7 @@ export declare class CustomRemoteRepository<TRow extends object> extends TableDa
|
|
|
121
121
|
get filtering(): ColumnFiltersState;
|
|
122
122
|
private _abortController;
|
|
123
123
|
constructor(tableName: string, getUserId: () => string | undefined, _fetch: CustomRowSource<TRow>['fetch']);
|
|
124
|
-
readonly setFilterMode: (id: string, mode: FilterMode) => void;
|
|
124
|
+
readonly setFilterMode: (id: string, mode: FilterMode | undefined) => void;
|
|
125
125
|
readonly filterBy: (id: string, value: unknown) => void;
|
|
126
126
|
readonly resetFilters: () => void;
|
|
127
127
|
/** Jumps to the given 1-based page number, clamped at >= 1. */
|
|
@@ -91,7 +91,16 @@ export class BuiltInRemoteRepository extends TableDataRepository {
|
|
|
91
91
|
}
|
|
92
92
|
// ── IRemoteDataRepository surface ────────────────────────────────
|
|
93
93
|
setFilterMode = (id, mode) => {
|
|
94
|
-
|
|
94
|
+
if (mode === undefined) {
|
|
95
|
+
// Drop the entry entirely so the column falls back to its
|
|
96
|
+
// kind's default mode on the next render. Used by
|
|
97
|
+
// `TableContext._applyFilter` to clear stale modes when a
|
|
98
|
+
// saved filter view is applied.
|
|
99
|
+
this._filterModes.delete(id);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this._filterModes.set(id, mode);
|
|
103
|
+
}
|
|
95
104
|
this.scheduleReload();
|
|
96
105
|
};
|
|
97
106
|
filterBy = (id, value) => {
|
|
@@ -366,7 +375,16 @@ export class CustomRemoteRepository extends TableDataRepository {
|
|
|
366
375
|
this._fetch = _fetch;
|
|
367
376
|
}
|
|
368
377
|
setFilterMode = (id, mode) => {
|
|
369
|
-
|
|
378
|
+
if (mode === undefined) {
|
|
379
|
+
// Drop the entry entirely so the column falls back to its
|
|
380
|
+
// kind's default mode on the next render. Used by
|
|
381
|
+
// `TableContext._applyFilter` when restoring a saved filter
|
|
382
|
+
// view to clear modes left over from a prior view.
|
|
383
|
+
this._filterModes.delete(id);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
this._filterModes.set(id, mode);
|
|
387
|
+
}
|
|
370
388
|
void this.reload();
|
|
371
389
|
};
|
|
372
390
|
filterBy = (id, value) => {
|
|
@@ -37,7 +37,15 @@ export interface IDataRepository<T extends object> {
|
|
|
37
37
|
sorting: SortingState;
|
|
38
38
|
filtering: ColumnFiltersState;
|
|
39
39
|
filterModes: SvelteMap<string, FilterMode>;
|
|
40
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Set the filter mode for column `id`. Pass `undefined` to delete the
|
|
42
|
+
* entry entirely (the column then falls back to its kind's default
|
|
43
|
+
* mode when the next `FilterInput` / `QuickSearchCell` renders).
|
|
44
|
+
* Used by `_applyFilter` when restoring a saved filter view, so any
|
|
45
|
+
* mode from a previously-applied view is dropped before the new
|
|
46
|
+
* one's modes are applied.
|
|
47
|
+
*/
|
|
48
|
+
setFilterMode: (id: string, mode: FilterMode | undefined) => void;
|
|
41
49
|
filterBy: (id: string, value: unknown) => void;
|
|
42
50
|
dateFilter?: FilterFn<T, DateFilterValueType>;
|
|
43
51
|
numberFilter?: FilterFn<T, NumberFilterValueType>;
|
|
@@ -21,7 +21,7 @@ export interface ILocalDataRepository<T extends object> extends IDataRepository<
|
|
|
21
21
|
prevPage: () => void;
|
|
22
22
|
filtering: ColumnFiltersState;
|
|
23
23
|
filterModes: SvelteMap<string, FilterMode>;
|
|
24
|
-
setFilterMode: (id: string, mode: FilterMode) => void;
|
|
24
|
+
setFilterMode: (id: string, mode: FilterMode | undefined) => void;
|
|
25
25
|
filterBy: (id: string, value: unknown) => void;
|
|
26
26
|
dateFilter: FilterFn<T, DateFilterValueType>;
|
|
27
27
|
numberFilter: FilterFn<T, NumberFilterValueType>;
|
|
@@ -26,7 +26,7 @@ export interface IRemoteDataRepository<T extends object> extends IDataRepository
|
|
|
26
26
|
prevPage: () => void;
|
|
27
27
|
filtering: ColumnFiltersState;
|
|
28
28
|
filterModes: SvelteMap<string, FilterMode>;
|
|
29
|
-
setFilterMode: (id: string, mode: FilterMode) => void;
|
|
29
|
+
setFilterMode: (id: string, mode: FilterMode | undefined) => void;
|
|
30
30
|
filterBy: (id: string, value: unknown) => void;
|
|
31
31
|
resetFilters: () => void;
|
|
32
32
|
lastPage: number;
|
|
@@ -42,7 +42,7 @@ export declare abstract class TableDataRepository<T extends object, out FilterVa
|
|
|
42
42
|
*/
|
|
43
43
|
abstract setPageSize: (size: number | string) => void;
|
|
44
44
|
abstract filtering: ColumnFiltersState<FilterValueType>;
|
|
45
|
-
abstract setFilterMode: (id: string, mode: FilterMode) => void;
|
|
45
|
+
abstract setFilterMode: (id: string, mode: FilterMode | undefined) => void;
|
|
46
46
|
abstract filterBy: (id: string, value: unknown) => void;
|
|
47
47
|
abstract resetFilters: () => void;
|
|
48
48
|
abstract setPage: (page: number) => void;
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { DatatableViewBody } from './DatatableViewEnvelope.js';
|
|
2
2
|
import type { DatatableViewKind } from './DatatableViewKind.js';
|
|
3
|
+
/**
|
|
4
|
+
* Sharing visibility for a saved view. `'private'` is the default for
|
|
5
|
+
* v1: only the owner and explicit subscribers can see and apply it.
|
|
6
|
+
* `'public'` exposes the view to every user who has access to the
|
|
7
|
+
* datatable - they see it in the "Subscribe to Additional…" picker and
|
|
8
|
+
* can subscribe to it. scoria never interprets this token beyond
|
|
9
|
+
* passing it back through the adapter; the backend enforces the
|
|
10
|
+
* visibility semantics.
|
|
11
|
+
*/
|
|
12
|
+
export type DatatableViewVisibility = 'private' | 'public';
|
|
3
13
|
/**
|
|
4
14
|
* A saved datatable view - a named, persisted column layout or filter
|
|
5
15
|
* that round-trips through a `RemoteTableLayoutAdapter`. This is the
|
|
@@ -47,6 +57,15 @@ export interface DatatableView {
|
|
|
47
57
|
* UI - scoria falls back to `userId` when it is absent.
|
|
48
58
|
*/
|
|
49
59
|
subscribers?: ReadonlyArray<DatatableViewSubscriber>;
|
|
60
|
+
/**
|
|
61
|
+
* Sharing visibility. `'private'` (default) is only visible to the
|
|
62
|
+
* owner and explicit subscribers; `'public'` is offered to every
|
|
63
|
+
* user with datatable access through the "Subscribe to Additional…"
|
|
64
|
+
* picker. Optional on read for backwards compatibility - hosts that
|
|
65
|
+
* have not surfaced this column yet can omit it, and scoria falls
|
|
66
|
+
* back to private semantics in the UI.
|
|
67
|
+
*/
|
|
68
|
+
visibility?: DatatableViewVisibility;
|
|
50
69
|
}
|
|
51
70
|
/**
|
|
52
71
|
* One entry in a `DatatableView.subscribers` roster: an opaque user
|
|
@@ -81,6 +100,12 @@ export interface UpdateDatatableViewRequest {
|
|
|
81
100
|
name?: string;
|
|
82
101
|
/** New payload, if the layout/filter is being replaced. */
|
|
83
102
|
body?: DatatableViewBody;
|
|
103
|
+
/**
|
|
104
|
+
* New sharing visibility, if it is being changed. `'private'` keeps
|
|
105
|
+
* the view scoped to its owner + subscribers; `'public'` exposes it
|
|
106
|
+
* to every user with datatable access.
|
|
107
|
+
*/
|
|
108
|
+
visibility?: DatatableViewVisibility;
|
|
84
109
|
}
|
|
85
110
|
/**
|
|
86
111
|
* A subscriber relationship for a shared `DatatableView`. v1 sharing is
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import { TestContext } from '../../Contexts/TestContext.svelte.js';
|
|
5
|
-
import { getContext } from 'svelte';
|
|
6
|
-
|
|
7
|
-
const testContext = getContext<TestContext>(TestContext.identifier);
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<h1>counter 1: {testContext.value1.value}</h1>
|
|
11
|
-
<p>Ref wrapper 1 is incremented every time you click the button</p>
|
|
12
|
-
<h2>counter 2: {testContext.value2.value}</h2>
|
|
13
|
-
<p>ref wrapper 2 adds ref wrapper 1's value every time ref wrapper 1's value is divisible by 3</p>
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { TestContext } from '../../Contexts/TestContext.svelte.js';
|
|
5
|
+
import { getContext } from 'svelte';
|
|
6
|
+
|
|
7
|
+
const testContext = getContext<TestContext>(TestContext.identifier);
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<h1>counter 1: {testContext.value1.value}</h1>
|
|
11
|
+
<p>Ref wrapper 1 is incremented every time you click the button</p>
|
|
12
|
+
<h2>counter 2: {testContext.value2.value}</h2>
|
|
13
|
+
<p>ref wrapper 2 adds ref wrapper 1's value every time ref wrapper 1's value is divisible by 3</p>
|
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import { cssLength } from '../Helpers/Helpers.svelte.js';
|
|
5
|
-
import { InputVariant } from '../Types/Internal/InputVariant.js';
|
|
6
|
-
import { Size } from '../Types/Internal/Size.js';
|
|
7
|
-
import Icon from './Icon.svelte';
|
|
8
|
-
import type { TextAreaProps } from './TextAreaProps.js';
|
|
9
|
-
|
|
10
|
-
let {
|
|
11
|
-
value = $bindable(''),
|
|
12
|
-
labelTop,
|
|
13
|
-
labelRight,
|
|
14
|
-
placeholder,
|
|
15
|
-
maxWidth = false,
|
|
16
|
-
required = false,
|
|
17
|
-
variant = InputVariant.Default,
|
|
18
|
-
selectNone = false,
|
|
19
|
-
noresize = false,
|
|
20
|
-
uppercase = false,
|
|
21
|
-
disabled,
|
|
22
|
-
invalid = false,
|
|
23
|
-
showErrorColours = true,
|
|
24
|
-
optional = false,
|
|
25
|
-
leftIcon,
|
|
26
|
-
rightIcon,
|
|
27
|
-
onLeftIconClick,
|
|
28
|
-
onRightIconClick,
|
|
29
|
-
rows = 4,
|
|
30
|
-
|
|
31
|
-
textAreaRef = $bindable(),
|
|
32
|
-
autocomplete,
|
|
33
|
-
spellcheck,
|
|
34
|
-
onblur,
|
|
35
|
-
onfocus,
|
|
36
|
-
// No default `onkeydown` - see TextInput.svelte for the rationale.
|
|
37
|
-
// The legacy default (`passthrough`) blurred the textarea on every
|
|
38
|
-
// alphanumeric keystroke and made the field unusable.
|
|
39
|
-
onkeydown,
|
|
40
|
-
oninput,
|
|
41
|
-
}: TextAreaProps = $props();
|
|
42
|
-
|
|
43
|
-
const isInvalid = $derived(showErrorColours && ((required && value.trim() === '') || invalid));
|
|
44
|
-
</script>
|
|
45
|
-
|
|
46
|
-
<label
|
|
47
|
-
class:for-modal={variant === InputVariant.Modal}
|
|
48
|
-
class:max-width={maxWidth}
|
|
49
|
-
>
|
|
50
|
-
{#if labelTop && variant !== InputVariant.Modal}
|
|
51
|
-
<span
|
|
52
|
-
class="top-label"
|
|
53
|
-
class:for-default={variant === InputVariant.Default}
|
|
54
|
-
class:invalid={isInvalid}
|
|
55
|
-
>{labelTop}{#if optional}<i> - optional</i>{/if}</span
|
|
56
|
-
>
|
|
57
|
-
{#if variant === InputVariant.Default}
|
|
58
|
-
<span
|
|
59
|
-
aria-hidden="true"
|
|
60
|
-
class="border"
|
|
61
|
-
class:invalid={isInvalid}
|
|
62
|
-
>{labelTop}{#if optional}<i> - optional</i>{/if}</span
|
|
63
|
-
>
|
|
64
|
-
{/if}
|
|
65
|
-
{/if}
|
|
66
|
-
<div
|
|
67
|
-
class="icon-container"
|
|
68
|
-
class:for-modal={variant == InputVariant.Modal}
|
|
69
|
-
>
|
|
70
|
-
{#if leftIcon}
|
|
71
|
-
<Icon
|
|
72
|
-
name={leftIcon.name}
|
|
73
|
-
size={leftIcon.size ?? Size.Medium}
|
|
74
|
-
colour={leftIcon.colour}
|
|
75
|
-
float
|
|
76
|
-
left={cssLength('0.75rem')}
|
|
77
|
-
top={cssLength('50%')}
|
|
78
|
-
anchorX={cssLength('0')}
|
|
79
|
-
anchorY={cssLength('-50%')}
|
|
80
|
-
clickable={!!onLeftIconClick}
|
|
81
|
-
onclick={onLeftIconClick}
|
|
82
|
-
/>
|
|
83
|
-
{/if}
|
|
84
|
-
<textarea
|
|
85
|
-
class:noresize
|
|
86
|
-
class:uppercase
|
|
87
|
-
class:invalid={isInvalid}
|
|
88
|
-
class:select-none={selectNone}
|
|
89
|
-
class:left-pad={leftIcon != undefined}
|
|
90
|
-
class:right-pad={rightIcon != undefined}
|
|
91
|
-
class:for-modal={variant == InputVariant.Modal}
|
|
92
|
-
class:max-width={maxWidth}
|
|
93
|
-
autocomplete={autocomplete ?? 'off'}
|
|
94
|
-
spellcheck={spellcheck ?? true}
|
|
95
|
-
aria-invalid={isInvalid || undefined}
|
|
96
|
-
bind:value
|
|
97
|
-
{onblur}
|
|
98
|
-
{onfocus}
|
|
99
|
-
{onkeydown}
|
|
100
|
-
{oninput}
|
|
101
|
-
{rows}
|
|
102
|
-
bind:this={textAreaRef}
|
|
103
|
-
{placeholder}
|
|
104
|
-
{required}
|
|
105
|
-
{disabled}
|
|
106
|
-
></textarea>
|
|
107
|
-
{#if rightIcon}
|
|
108
|
-
<Icon
|
|
109
|
-
name={rightIcon.name}
|
|
110
|
-
size={rightIcon.size ?? Size.Medium}
|
|
111
|
-
colour={rightIcon.colour}
|
|
112
|
-
float
|
|
113
|
-
right={cssLength(variant == InputVariant.Modal ? '1rem' : '0.5rem')}
|
|
114
|
-
top={cssLength('50%')}
|
|
115
|
-
anchorX={cssLength('0')}
|
|
116
|
-
anchorY={cssLength('-50%')}
|
|
117
|
-
clickable={!!onRightIconClick}
|
|
118
|
-
onclick={onRightIconClick}
|
|
119
|
-
/>
|
|
120
|
-
{/if}
|
|
121
|
-
{#if labelRight}
|
|
122
|
-
<span
|
|
123
|
-
class="right-label"
|
|
124
|
-
class:for-modal={variant == InputVariant.Modal}>{labelRight}</span
|
|
125
|
-
>
|
|
126
|
-
{/if}
|
|
127
|
-
</div>
|
|
128
|
-
</label>
|
|
129
|
-
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { cssLength } from '../Helpers/Helpers.svelte.js';
|
|
5
|
+
import { InputVariant } from '../Types/Internal/InputVariant.js';
|
|
6
|
+
import { Size } from '../Types/Internal/Size.js';
|
|
7
|
+
import Icon from './Icon.svelte';
|
|
8
|
+
import type { TextAreaProps } from './TextAreaProps.js';
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
value = $bindable(''),
|
|
12
|
+
labelTop,
|
|
13
|
+
labelRight,
|
|
14
|
+
placeholder,
|
|
15
|
+
maxWidth = false,
|
|
16
|
+
required = false,
|
|
17
|
+
variant = InputVariant.Default,
|
|
18
|
+
selectNone = false,
|
|
19
|
+
noresize = false,
|
|
20
|
+
uppercase = false,
|
|
21
|
+
disabled,
|
|
22
|
+
invalid = false,
|
|
23
|
+
showErrorColours = true,
|
|
24
|
+
optional = false,
|
|
25
|
+
leftIcon,
|
|
26
|
+
rightIcon,
|
|
27
|
+
onLeftIconClick,
|
|
28
|
+
onRightIconClick,
|
|
29
|
+
rows = 4,
|
|
30
|
+
|
|
31
|
+
textAreaRef = $bindable(),
|
|
32
|
+
autocomplete,
|
|
33
|
+
spellcheck,
|
|
34
|
+
onblur,
|
|
35
|
+
onfocus,
|
|
36
|
+
// No default `onkeydown` - see TextInput.svelte for the rationale.
|
|
37
|
+
// The legacy default (`passthrough`) blurred the textarea on every
|
|
38
|
+
// alphanumeric keystroke and made the field unusable.
|
|
39
|
+
onkeydown,
|
|
40
|
+
oninput,
|
|
41
|
+
}: TextAreaProps = $props();
|
|
42
|
+
|
|
43
|
+
const isInvalid = $derived(showErrorColours && ((required && value.trim() === '') || invalid));
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<label
|
|
47
|
+
class:for-modal={variant === InputVariant.Modal}
|
|
48
|
+
class:max-width={maxWidth}
|
|
49
|
+
>
|
|
50
|
+
{#if labelTop && variant !== InputVariant.Modal}
|
|
51
|
+
<span
|
|
52
|
+
class="top-label"
|
|
53
|
+
class:for-default={variant === InputVariant.Default}
|
|
54
|
+
class:invalid={isInvalid}
|
|
55
|
+
>{labelTop}{#if optional}<i> - optional</i>{/if}</span
|
|
56
|
+
>
|
|
57
|
+
{#if variant === InputVariant.Default}
|
|
58
|
+
<span
|
|
59
|
+
aria-hidden="true"
|
|
60
|
+
class="border"
|
|
61
|
+
class:invalid={isInvalid}
|
|
62
|
+
>{labelTop}{#if optional}<i> - optional</i>{/if}</span
|
|
63
|
+
>
|
|
64
|
+
{/if}
|
|
65
|
+
{/if}
|
|
66
|
+
<div
|
|
67
|
+
class="icon-container"
|
|
68
|
+
class:for-modal={variant == InputVariant.Modal}
|
|
69
|
+
>
|
|
70
|
+
{#if leftIcon}
|
|
71
|
+
<Icon
|
|
72
|
+
name={leftIcon.name}
|
|
73
|
+
size={leftIcon.size ?? Size.Medium}
|
|
74
|
+
colour={leftIcon.colour}
|
|
75
|
+
float
|
|
76
|
+
left={cssLength('0.75rem')}
|
|
77
|
+
top={cssLength('50%')}
|
|
78
|
+
anchorX={cssLength('0')}
|
|
79
|
+
anchorY={cssLength('-50%')}
|
|
80
|
+
clickable={!!onLeftIconClick}
|
|
81
|
+
onclick={onLeftIconClick}
|
|
82
|
+
/>
|
|
83
|
+
{/if}
|
|
84
|
+
<textarea
|
|
85
|
+
class:noresize
|
|
86
|
+
class:uppercase
|
|
87
|
+
class:invalid={isInvalid}
|
|
88
|
+
class:select-none={selectNone}
|
|
89
|
+
class:left-pad={leftIcon != undefined}
|
|
90
|
+
class:right-pad={rightIcon != undefined}
|
|
91
|
+
class:for-modal={variant == InputVariant.Modal}
|
|
92
|
+
class:max-width={maxWidth}
|
|
93
|
+
autocomplete={autocomplete ?? 'off'}
|
|
94
|
+
spellcheck={spellcheck ?? true}
|
|
95
|
+
aria-invalid={isInvalid || undefined}
|
|
96
|
+
bind:value
|
|
97
|
+
{onblur}
|
|
98
|
+
{onfocus}
|
|
99
|
+
{onkeydown}
|
|
100
|
+
{oninput}
|
|
101
|
+
{rows}
|
|
102
|
+
bind:this={textAreaRef}
|
|
103
|
+
{placeholder}
|
|
104
|
+
{required}
|
|
105
|
+
{disabled}
|
|
106
|
+
></textarea>
|
|
107
|
+
{#if rightIcon}
|
|
108
|
+
<Icon
|
|
109
|
+
name={rightIcon.name}
|
|
110
|
+
size={rightIcon.size ?? Size.Medium}
|
|
111
|
+
colour={rightIcon.colour}
|
|
112
|
+
float
|
|
113
|
+
right={cssLength(variant == InputVariant.Modal ? '1rem' : '0.5rem')}
|
|
114
|
+
top={cssLength('50%')}
|
|
115
|
+
anchorX={cssLength('0')}
|
|
116
|
+
anchorY={cssLength('-50%')}
|
|
117
|
+
clickable={!!onRightIconClick}
|
|
118
|
+
onclick={onRightIconClick}
|
|
119
|
+
/>
|
|
120
|
+
{/if}
|
|
121
|
+
{#if labelRight}
|
|
122
|
+
<span
|
|
123
|
+
class="right-label"
|
|
124
|
+
class:for-modal={variant == InputVariant.Modal}>{labelRight}</span
|
|
125
|
+
>
|
|
126
|
+
{/if}
|
|
127
|
+
</div>
|
|
128
|
+
</label>
|
|
129
|
+
|
|
130
130
|
<style>textarea {
|
|
131
131
|
box-sizing: border-box;
|
|
132
132
|
height: 100%;
|
|
@@ -340,4 +340,4 @@ span.border.invalid {
|
|
|
340
340
|
.textarea-error-wrapper.for-modal {
|
|
341
341
|
position: relative;
|
|
342
342
|
height: 100%;
|
|
343
|
-
}</style>
|
|
343
|
+
}</style>
|