@lavalogic/scoria 0.37.56 → 0.38.1

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 (50) hide show
  1. package/dist/Components/Table/ARCHITECTURE.md +22 -22
  2. package/dist/Components/Table/Misc/ColumnPanel.svelte +17 -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/FilterPanel.svelte +25 -113
  8. package/dist/Components/Table/Misc/TableConfigurationModal.svelte +626 -0
  9. package/dist/Components/Table/Misc/TableConfigurationModal.svelte.d.ts +26 -0
  10. package/dist/Components/Table/Misc/TableConfigurationModalProps.d.ts +13 -0
  11. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +15 -0
  12. package/dist/Components/Table/Misc/TableSidebar.svelte +45 -2
  13. package/dist/Components/Table/Misc/TableViewDropdown.svelte +97 -0
  14. package/dist/Components/Table/Misc/TableViewDropdown.svelte.d.ts +35 -0
  15. package/dist/Components/Table/SubApis.svelte.js +0 -41
  16. package/dist/Components/Table/Types/Columns/JSONTableLayout.d.ts +5 -5
  17. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.d.ts +5 -5
  18. package/dist/Components/Table/Types/Context/ColumnLayoutState.svelte.js +5 -5
  19. package/dist/Components/Table/Types/Context/PreferencesState.svelte.d.ts +106 -147
  20. package/dist/Components/Table/Types/Context/PreferencesState.svelte.js +156 -203
  21. package/dist/Components/Table/Types/Context/TableContext.svelte.d.ts +323 -44
  22. package/dist/Components/Table/Types/Context/TableContext.svelte.js +687 -187
  23. package/dist/Components/Table/Types/Context/TableInitOptions.d.ts +9 -0
  24. package/dist/Components/Table/Types/Context/ViewState.svelte.d.ts +152 -0
  25. package/dist/Components/Table/Types/Context/ViewState.svelte.js +223 -0
  26. package/dist/Components/Table/Types/ExpandedPanel.d.ts +9 -5
  27. package/dist/Components/Table/Types/ExpandedPanel.js +9 -5
  28. package/dist/Components/Table/Types/Persistence/DatatableView.d.ts +95 -0
  29. package/dist/Components/Table/Types/Persistence/DatatableViewEnvelope.d.ts +21 -0
  30. package/dist/Components/Table/Types/Persistence/DatatableViewEnvelope.js +1 -0
  31. package/dist/Components/Table/Types/Persistence/DatatableViewKind.d.ts +10 -0
  32. package/dist/Components/Table/Types/Persistence/DatatableViewKind.js +1 -0
  33. package/dist/Components/Table/Types/Persistence/JSONActiveView.d.ts +58 -0
  34. package/dist/Components/Table/Types/Persistence/JSONActiveView.js +41 -0
  35. package/dist/Components/Table/Types/Persistence/JSONTableFilter.d.ts +53 -0
  36. package/dist/Components/Table/Types/Persistence/JSONTableFilter.js +42 -0
  37. package/dist/Components/Table/Types/Persistence/RemoteTableLayoutAdapter.d.ts +47 -0
  38. package/dist/Components/Table/Types/Persistence/RemoteTableLayoutAdapter.js +1 -0
  39. package/dist/Components/Table/Types/Public/CreateTableOptions.d.ts +10 -0
  40. package/dist/Components/Table/Types/Public/TablePersistence.d.ts +1 -4
  41. package/dist/Components/Table/Types/Public/TableSubApis.d.ts +0 -18
  42. package/dist/Components/Table/Types/Public/index.d.ts +1 -1
  43. package/dist/Components/Table/createTable.svelte.js +2 -0
  44. package/dist/index.d.ts +8 -1
  45. package/dist/index.js +9 -0
  46. package/package.json +1 -1
  47. package/dist/Components/Table/Types/Columns/Definitions/ColumnDefSet.d.ts +0 -20
  48. package/dist/Components/Table/Types/Columns/Definitions/JSONColumnDefSet.d.ts +0 -25
  49. /package/dist/Components/Table/{Types/Columns/Definitions/ColumnDefSet.js → Misc/TableConfigurationModalProps.js} +0 -0
  50. /package/dist/Components/Table/Types/{Columns/Definitions/JSONColumnDefSet.js → Persistence/DatatableView.js} +0 -0
@@ -16,14 +16,14 @@ Everything in the public surface is re-exported from
16
16
 
17
17
  ```ts
18
18
  import {
19
- createTable,
20
- localRows,
21
- remoteRows,
22
- customRows,
23
- Table,
24
- type CreateTableOptions,
25
- type DisplayModalProps,
26
- // …per-kind option types: TextColumnOptions, NumberColumnOptions, …
19
+ createTable,
20
+ localRows,
21
+ remoteRows,
22
+ customRows,
23
+ Table,
24
+ type CreateTableOptions,
25
+ type DisplayModalProps,
26
+ // …per-kind option types: TextColumnOptions, NumberColumnOptions, …
27
27
  } from '@lavalogic/scoria';
28
28
  ```
29
29
 
@@ -96,16 +96,16 @@ Table/
96
96
  `TableContext` is the root state object. It owns the data repo and seven
97
97
  sub-contexts:
98
98
 
99
- | Sub-context | Owns |
100
- | ----------------------- | ------------------------------------------------------------------- |
101
- | `_sorting` | Sort comparators + the `sortedData` derived chain |
102
- | `_filtering` | Filter-fn relays + the `filteredData` derived chain + quick-filter toggle |
103
- | `_columnLayout` | Column widths / pinning / visibility / id-index maps |
104
- | `_selection` | Selected & highlighted maps, key cache, page-wide select helpers |
105
- | `_preferences` | Saved column-presets + localStorage round-trip |
106
- | `_focus` | Focus-coordinate state + multi-cell selection |
107
- | `_virtualisation` | Row virtualisation window |
108
- | `_queryValues` | Typeahead-echo cache for QuerySelect filter inputs |
99
+ | Sub-context | Owns |
100
+ | ----------------- | ------------------------------------------------------------------------- |
101
+ | `_sorting` | Sort comparators + the `sortedData` derived chain |
102
+ | `_filtering` | Filter-fn relays + the `filteredData` derived chain + quick-filter toggle |
103
+ | `_columnLayout` | Column widths / pinning / visibility / id-index maps |
104
+ | `_selection` | Selected & highlighted maps, key cache, page-wide select helpers |
105
+ | `_preferences` | Table settings + layout / filter / view localStorage round-trip |
106
+ | `_focus` | Focus-coordinate state + multi-cell selection |
107
+ | `_virtualisation` | Row virtualisation window |
108
+ | `_queryValues` | Typeahead-echo cache for QuerySelect filter inputs |
109
109
 
110
110
  Each sub-context is a class under `Types/Context/<Name>State.svelte.ts`
111
111
  that takes a `deps` object of thunk-returning fields the parent reads
@@ -261,10 +261,10 @@ for the canonical pattern.
261
261
 
262
262
  ## Testing model
263
263
 
264
- | File pattern | Project | What it covers |
265
- | --------------------------------- | --------- | ----------------------------------------------- |
266
- | `*.test.ts` (no `.svelte.`) | `server` | Pure-node unit tests (helpers, type contracts). Runs via `pnpm test`. |
267
- | `*.svelte.test.ts` | `client` | Tests using `$state` / `$effect` from Svelte 5. Browser mode via Vitest + Playwright. |
264
+ | File pattern | Project | What it covers |
265
+ | --------------------------- | -------- | ------------------------------------------------------------------------------------- |
266
+ | `*.test.ts` (no `.svelte.`) | `server` | Pure-node unit tests (helpers, type contracts). Runs via `pnpm test`. |
267
+ | `*.svelte.test.ts` | `client` | Tests using `$state` / `$effect` from Svelte 5. Browser mode via Vitest + Playwright. |
268
268
 
269
269
  The browser-mode runner has a Windows-specific chromium launch issue;
270
270
  set `SCORIA_CHROMIUM_EXECUTABLE` to the headless-shell binary to work
@@ -1,31 +1,12 @@
1
1
  <svelte:options runes />
2
2
 
3
- <script
4
- lang="ts"
5
- generics="T extends object, IdType extends Primitive"
6
- >
7
- import Button from '../../Button.svelte';
8
- import SingleSelect from '../../SingleSelect.svelte';
3
+ <script lang="ts">
9
4
  import ColumnList from '../Body/Rows/ColumnList.svelte';
10
5
  import TextInput from '../../TextInput.svelte';
11
- import type { SelectOption } from '../../../Types/Internal/SelectOption.js';
12
6
  import { Size } from '../../../Types/Internal/Size.js';
13
- import { Variant } from '../../../Types/Internal/Variant.js';
14
- import { getContext } from 'svelte';
15
- import { TableContext } from '../Types/Context/TableContext.svelte.js';
16
- import type { Primitive } from '../Types/Context/TableInitOptions.js';
7
+ import TableViewDropdown from './TableViewDropdown.svelte';
17
8
 
18
- const tableContext = getContext<TableContext<T, IdType>>(TableContext.identifier);
19
-
20
- const labelledPresets: Array<SelectOption<string>> = $derived(
21
- tableContext.columnPresets.map((pr) => ({ label: pr.name, value: pr.name }))
22
- );
23
-
24
- const selectedValue = $derived(
25
- labelledPresets.find((it) => it.value === tableContext.selectedPreset?.name) ?? null
26
- );
27
9
  let searchValue = $state('');
28
- let pendingPresetName = $state('');
29
10
  </script>
30
11
 
31
12
  <section class="side-panel-section">
@@ -34,22 +15,10 @@
34
15
  </header>
35
16
 
36
17
  <div class="toolbar">
37
- <SingleSelect
38
- label="Selected Preset"
39
- name="Preset"
40
- options={labelledPresets}
41
- labelProp="label"
42
- valueProp="value"
43
- value={selectedValue}
44
- valueAsObject
45
- onchange={(newSelection: SelectOption<string> | null) => {
46
- const preset =
47
- newSelection && tableContext.columnPresets.find((p) => p.name === newSelection.value);
48
- if (preset) {
49
- tableContext.setSelectedPreset(preset);
50
- }
51
- }}
52
- />
18
+ <!-- Quick-switch dropdown for saved column layouts. Sits above the
19
+ search box; saving a layout is done from the Table
20
+ Configuration modal's "Save a new Layout" tab. -->
21
+ <TableViewDropdown kind="layout" />
53
22
  <TextInput
54
23
  bind:value={searchValue}
55
24
  leftIcon={{ name: 'search', size: Size.Medium }}
@@ -65,30 +34,6 @@
65
34
  <div class="column-list-wrapper">
66
35
  <ColumnList {searchValue} />
67
36
  </div>
68
-
69
- <form
70
- class="side-panel-footer"
71
- onsubmit={(e) => {
72
- e.preventDefault();
73
- const trimmed = pendingPresetName.trim();
74
- if (!trimmed) {
75
- return;
76
- }
77
- tableContext.saveColumnPreset(trimmed);
78
- pendingPresetName = '';
79
- }}
80
- >
81
- <TextInput
82
- placeholder="Preset name"
83
- bind:value={pendingPresetName}
84
- />
85
- <Button
86
- type="submit"
87
- variant={Variant.Secondary}
88
- nowrap
89
- size={Size.MediumSmall}>Save Layout</Button
90
- >
91
- </form>
92
37
  </section>
93
38
 
94
39
  <style>.side-panel-section {
@@ -143,6 +88,10 @@
143
88
  box-sizing: border-box;
144
89
  }
145
90
 
91
+ /* Top toolbar (saved-layout dropdown + Search input) sits below the
92
+ header but above the scrollable list. It is intentionally not
93
+ inside `.side-panel-body` because the list takes the remaining
94
+ vertical space and scrolls on its own. */
146
95
  .toolbar {
147
96
  flex-shrink: 0;
148
97
  padding: 1rem 1.25rem;
@@ -160,6 +109,13 @@
160
109
  display: flex;
161
110
  flex-flow: column nowrap;
162
111
  background-color: #ffffff;
112
+ /* Horizontal padding aligned with FilterPanel's `.side-panel-body`
113
+ so the column-list cards leave the same gutter before the panel's
114
+ right border as the filter chips do, and the SidePanel's
115
+ `border-right` stays visible between the content and the toolbar
116
+ icons. Vertical padding is 0 because `.column-list` provides
117
+ its own 0.5rem padding (which doubles up to a 1.5rem gap from
118
+ the toolbar above and the footer below). */
163
119
  padding: 0 1.25rem;
164
120
  box-sizing: border-box;
165
121
  }</style>
@@ -1,25 +1,3 @@
1
- import type { Primitive } from '../Types/Context/TableInitOptions.js';
2
- declare function $$render<T extends object, IdType extends Primitive>(): {
3
- props: Record<string, never>;
4
- exports: {};
5
- bindings: "";
6
- slots: {};
7
- events: {};
8
- };
9
- declare class __sveltets_Render<T extends object, IdType extends Primitive> {
10
- props(): ReturnType<typeof $$render<T, IdType>>['props'];
11
- events(): ReturnType<typeof $$render<T, IdType>>['events'];
12
- slots(): ReturnType<typeof $$render<T, IdType>>['slots'];
13
- bindings(): "";
14
- exports(): {};
15
- }
16
- interface $$IsomorphicComponent {
17
- new <T extends object, IdType extends Primitive>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T, IdType>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T, IdType>['props']>, ReturnType<__sveltets_Render<T, IdType>['events']>, ReturnType<__sveltets_Render<T, IdType>['slots']>> & {
18
- $$bindings?: ReturnType<__sveltets_Render<T, IdType>['bindings']>;
19
- } & ReturnType<__sveltets_Render<T, IdType>['exports']>;
20
- <T extends object, IdType extends Primitive>(internal: unknown, props: {}): ReturnType<__sveltets_Render<T, IdType>['exports']>;
21
- z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
22
- }
23
- declare const ColumnPanel: $$IsomorphicComponent;
24
- type ColumnPanel<T extends object, IdType extends Primitive> = InstanceType<typeof ColumnPanel<T, IdType>>;
1
+ declare const ColumnPanel: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ColumnPanel = ReturnType<typeof ColumnPanel>;
25
3
  export default ColumnPanel;
@@ -1,56 +1,15 @@
1
1
  <svelte:options runes />
2
2
 
3
- <script
4
- lang="ts"
5
- generics="T extends object, IdType extends Primitive"
6
- >
7
- import Button from '../../Button.svelte';
3
+ <script lang="ts">
8
4
  import DesktopModal from '../../DesktopModal.svelte';
9
- import SingleSelect from '../../SingleSelect.svelte';
10
5
  import ColumnList from '../Body/Rows/ColumnList.svelte';
11
6
  import TextInput from '../../TextInput.svelte';
12
- import type { SelectOption } from '../../../Types/Internal/SelectOption.js';
13
7
  import { Size } from '../../../Types/Internal/Size.js';
14
- import { Variant } from '../../../Types/Internal/Variant.js';
15
- import { getContext } from 'svelte';
16
- import { TableContext } from '../Types/Context/TableContext.svelte.js';
17
- import type { Primitive } from '../Types/Context/TableInitOptions.js';
18
8
  import type { ColumnPanelModalProps } from './ColumnPanelModalProps.js';
19
9
 
20
- const tableContext = getContext<TableContext<T, IdType>>(TableContext.identifier);
21
-
22
10
  const { onclose }: ColumnPanelModalProps = $props();
23
- const labelledPresets: Array<SelectOption<string>> = $derived(
24
- tableContext.columnPresets.map((pr) => ({ label: pr.name, value: pr.name }))
25
- );
26
- let searchValue = $state('');
27
- let pendingPresetName = $state('');
28
-
29
- const selectedValue = $derived(
30
- labelledPresets.find((it) => it.value === tableContext.selectedPreset?.name) ?? null
31
- );
32
-
33
- function trySavePreset(e: KeyboardEvent) {
34
- if (e.key === 'Enter') {
35
- e.preventDefault();
36
- const trimmed = pendingPresetName.trim();
37
- if (!trimmed) {
38
- return;
39
- }
40
- tableContext.saveColumnPreset(trimmed);
41
- pendingPresetName = '';
42
- }
43
- }
44
11
 
45
- function savePreset(e: MouseEvent) {
46
- e.preventDefault();
47
- const trimmed = pendingPresetName.trim();
48
- if (!trimmed) {
49
- return;
50
- }
51
- tableContext.saveColumnPreset(trimmed);
52
- pendingPresetName = '';
53
- }
12
+ let searchValue = $state('');
54
13
  </script>
55
14
 
56
15
  <DesktopModal
@@ -59,25 +18,7 @@
59
18
  buttons={[{ label: 'Close', callback: () => onclose?.() }]}
60
19
  >
61
20
  <div class="flex">
62
- <div class="column-panel top presets-and-filter">
63
- <div class="select">
64
- <SingleSelect
65
- label="Selected Preset"
66
- name="Preset"
67
- options={labelledPresets}
68
- labelProp="label"
69
- valueProp="value"
70
- value={selectedValue}
71
- valueAsObject
72
- onchange={(newSelection: SelectOption<string> | null) => {
73
- const preset =
74
- newSelection && tableContext.columnPresets.find((p) => p.name === newSelection.value);
75
- if (preset) {
76
- tableContext.setSelectedPreset(preset);
77
- }
78
- }}
79
- />
80
- </div>
21
+ <div class="column-panel top">
81
22
  <TextInput
82
23
  bind:value={searchValue}
83
24
  leftIcon={{ name: 'search', size: Size.Medium }}
@@ -92,20 +33,6 @@
92
33
  <div class="scrollable">
93
34
  <ColumnList {searchValue} />
94
35
  </div>
95
- <div class="column-panel bottom save-preset-wrapper">
96
- <TextInput
97
- placeholder="Preset name"
98
- bind:value={pendingPresetName}
99
- onkeydown={trySavePreset}
100
- />
101
- <Button
102
- type="submit"
103
- variant={Variant.Secondary}
104
- nowrap
105
- size={Size.MediumSmall}
106
- onclick={savePreset}>Save Layout</Button
107
- >
108
- </div>
109
36
  </div>
110
37
  </DesktopModal>
111
38
 
@@ -128,25 +55,9 @@
128
55
  }
129
56
  .column-panel.top {
130
57
  border-bottom: solid 2px #cbd4da;
131
- }
132
- .column-panel.bottom {
133
- border-top: solid 2px #cbd4da;
134
- }
135
- .column-panel.presets-and-filter {
136
58
  display: flex;
137
59
  flex-flow: column;
138
60
  row-gap: 0.5rem;
139
61
  padding: 0.5rem;
140
62
  flex-basis: min-content;
141
- }
142
- .column-panel.save-preset-wrapper {
143
- display: flex;
144
- flex-flow: row nowrap;
145
- justify-content: space-between;
146
- gap: 0.5rem;
147
- padding: 0.5rem;
148
- flex-basis: min-content;
149
- }
150
- .column-panel .select {
151
- width: 100%;
152
63
  }</style>
@@ -1,26 +1,4 @@
1
- import type { Primitive } from '../Types/Context/TableInitOptions.js';
2
1
  import type { ColumnPanelModalProps } from './ColumnPanelModalProps.js';
3
- declare function $$render<T extends object, IdType extends Primitive>(): {
4
- props: ColumnPanelModalProps;
5
- exports: {};
6
- bindings: "";
7
- slots: {};
8
- events: {};
9
- };
10
- declare class __sveltets_Render<T extends object, IdType extends Primitive> {
11
- props(): ReturnType<typeof $$render<T, IdType>>['props'];
12
- events(): ReturnType<typeof $$render<T, IdType>>['events'];
13
- slots(): ReturnType<typeof $$render<T, IdType>>['slots'];
14
- bindings(): "";
15
- exports(): {};
16
- }
17
- interface $$IsomorphicComponent {
18
- new <T extends object, IdType extends Primitive>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T, IdType>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T, IdType>['props']>, ReturnType<__sveltets_Render<T, IdType>['events']>, ReturnType<__sveltets_Render<T, IdType>['slots']>> & {
19
- $$bindings?: ReturnType<__sveltets_Render<T, IdType>['bindings']>;
20
- } & ReturnType<__sveltets_Render<T, IdType>['exports']>;
21
- <T extends object, IdType extends Primitive>(internal: unknown, props: ReturnType<__sveltets_Render<T, IdType>['props']> & {}): ReturnType<__sveltets_Render<T, IdType>['exports']>;
22
- z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
23
- }
24
- declare const ColumnPanelModal: $$IsomorphicComponent;
25
- type ColumnPanelModal<T extends object, IdType extends Primitive> = InstanceType<typeof ColumnPanelModal<T, IdType>>;
2
+ declare const ColumnPanelModal: import("svelte").Component<ColumnPanelModalProps, {}, "">;
3
+ type ColumnPanelModal = ReturnType<typeof ColumnPanelModal>;
26
4
  export default ColumnPanelModal;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Props for `ColumnPanelModal` (the touch variant of the column-presets and
3
- * column-list management panel).
2
+ * Props for `ColumnPanelModal` (the touch variant of the column-list
3
+ * visibility / reorder management panel).
4
4
  */
5
5
  export interface ColumnPanelModalProps {
6
6
  /** Called when the modal requests close. */
@@ -5,17 +5,16 @@
5
5
  generics="T extends object, IdType extends Primitive"
6
6
  >
7
7
  import Action from '../../Action.svelte';
8
- import Button from '../../Button.svelte';
9
8
  import TextInput from '../../TextInput.svelte';
10
9
  import { Colour, ColourSet } from '../../../scss/colours.js';
11
10
  import { Size } from '../../../Types/Internal/Size.js';
12
- import { Variant } from '../../../Types/Internal/Variant.js';
13
11
  import { SvelteSet } from 'svelte/reactivity';
14
12
  import { getContext } from 'svelte';
15
13
  import type { Primitive } from '../Types/Context/TableInitOptions.js';
16
14
  import type { ColumnDef } from '../Types/Columns/Definitions/ColumnDef.svelte.js';
17
15
  import { TableContext } from '../Types/Context/TableContext.svelte.js';
18
16
  import FilterInput from './FilterInput.svelte';
17
+ import TableViewDropdown from './TableViewDropdown.svelte';
19
18
 
20
19
  const tableContext = getContext<TableContext<T, IdType>>(TableContext.identifier);
21
20
 
@@ -31,11 +30,6 @@
31
30
  * string is the "no filter" state. */
32
31
  let quickFindValue: string = $state('');
33
32
 
34
- /** Whether the Save Filter overflow menu is open. The chevron
35
- * toggles it; clicking anywhere else (via the window-level
36
- * listener below) closes it. */
37
- let saveMenuOpen: boolean = $state(false);
38
-
39
33
  /** Names of currently-collapsed groups. A name absent from this
40
34
  * set is rendered expanded. Reactive via `SvelteSet` so the panel
41
35
  * re-renders on toggle without manually re-assigning. */
@@ -106,39 +100,6 @@
106
100
  }
107
101
  }
108
102
 
109
- function resetFilters(): void {
110
- tableContext.paginationRepo?.resetFilters();
111
- }
112
-
113
- /**
114
- * Placeholder for the future Save-Filter / preset workflow. For
115
- * now this just closes the overflow menu; consumers wire the real
116
- * persistence layer in a follow-up workorder. The chevron menu is
117
- * still useful today as a home for the Reset action and as the
118
- * insertion point for "Load Filter" / "Manage Filters" entries
119
- * once those land.
120
- */
121
- function saveFilter(): void {
122
- saveMenuOpen = false;
123
- if (typeof window !== 'undefined') {
124
- window.dispatchEvent(
125
- new CustomEvent('scoria:save-filter', {
126
- detail: { tableName: tableContext.tableName },
127
- })
128
- );
129
- }
130
- }
131
-
132
- function handleWindowClick(e: MouseEvent): void {
133
- if (!saveMenuOpen) {
134
- return;
135
- }
136
- const target = e.target as HTMLElement | null;
137
- if (target?.closest('.save-filter-cluster') == null) {
138
- saveMenuOpen = false;
139
- }
140
- }
141
-
142
103
  /**
143
104
  * No-op keydown handler used to override `TextInput`'s default
144
105
  * `passthrough` keydown (defined in `Helpers.svelte.ts`), which
@@ -155,14 +116,16 @@
155
116
  }
156
117
  </script>
157
118
 
158
- <svelte:window onclick={handleWindowClick} />
159
-
160
119
  <section class="side-panel-section">
161
120
  <header class="side-panel-header">
162
121
  <h2>Advanced Filter</h2>
163
122
  </header>
164
123
 
165
124
  <div class="toolbar">
125
+ <!-- Quick-switch dropdown for saved filter sets. Sits above the
126
+ search box; saving a filter is done from the Table
127
+ Configuration modal's "Save a new Filter" tab. -->
128
+ <TableViewDropdown kind="filter" />
166
129
  <div class="quick-find">
167
130
  <TextInput
168
131
  labelTop="Quick Find a Filter"
@@ -172,36 +135,6 @@
172
135
  onkeydown={noopKeydown}
173
136
  />
174
137
  </div>
175
-
176
- <div class="save-filter-cluster">
177
- <Button
178
- variant={Variant.Secondary}
179
- nowrap
180
- disabled
181
- size={Size.MediumSmall}
182
- onclick={saveFilter}>Save Filter</Button
183
- >
184
- <Action
185
- label="More save options"
186
- colourSet={ColourSet.Auxiliary}
187
- icon={{ name: 'chevron-down', size: Size.Medium }}
188
- onclick={() => {
189
- saveMenuOpen = !saveMenuOpen;
190
- }}
191
- />
192
- {#if saveMenuOpen}
193
- <div class="save-menu">
194
- <button
195
- type="button"
196
- class="save-menu-item"
197
- onclick={() => {
198
- saveMenuOpen = false;
199
- resetFilters();
200
- }}>Reset all filters</button
201
- >
202
- </div>
203
- {/if}
204
- </div>
205
138
  </div>
206
139
 
207
140
  <div class="side-panel-body">
@@ -306,59 +239,24 @@
306
239
  box-sizing: border-box;
307
240
  }
308
241
 
242
+ /* Top toolbar (saved-filter dropdown + Quick Find) sits below the
243
+ header but above the scrollable body. Kept outside
244
+ `.side-panel-body` so it stays pinned while the groups list
245
+ scrolls underneath. The two rows stack vertically: the
246
+ quick-switch dropdown on top, the search field below it. */
309
247
  .toolbar {
310
248
  flex-shrink: 0;
311
249
  display: flex;
312
- flex-flow: row nowrap;
313
- align-items: center;
250
+ flex-flow: column nowrap;
314
251
  gap: 0.75rem;
315
252
  padding: 1rem 1.25rem;
316
253
  background-color: #ffffff;
317
254
  border-bottom: solid 1px #cbd4da;
318
255
  }
319
256
  .toolbar .quick-find {
320
- flex: 1;
321
257
  min-width: 0;
322
258
  --input-height: 3rem;
323
259
  }
324
- .toolbar .save-filter-cluster {
325
- position: relative;
326
- display: flex;
327
- flex-flow: row nowrap;
328
- align-items: center;
329
- gap: 0.25rem;
330
- }
331
- .toolbar .save-filter-cluster .save-menu {
332
- position: absolute;
333
- top: 100%;
334
- right: 0;
335
- margin-top: 0.25rem;
336
- background-color: #ffffff;
337
- border: solid 1px #cbd4da;
338
- border-radius: 4px;
339
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
340
- min-width: 12rem;
341
- z-index: 5;
342
- display: flex;
343
- flex-flow: column nowrap;
344
- padding: 0.25rem 0;
345
- }
346
- .toolbar .save-filter-cluster .save-menu-item {
347
- background: transparent;
348
- border: none;
349
- text-align: left;
350
- padding: 0.5rem 0.75rem;
351
- cursor: pointer;
352
- font: inherit;
353
- color: #3a4952;
354
- }
355
- .toolbar .save-filter-cluster .save-menu-item:hover {
356
- background-color: #f4f7f9;
357
- }
358
- .toolbar .save-filter-cluster .save-menu-item:focus-visible {
359
- outline: 2px solid #259fc7;
360
- outline-offset: -2px;
361
- }
362
260
 
363
261
  p.empty {
364
262
  color: #647d8e;
@@ -372,6 +270,11 @@ p.empty {
372
270
  gap: 1rem;
373
271
  }
374
272
 
273
+ /* `overflow: visible` (no longer `overflow: hidden`) so a filter
274
+ row's select dropdown can extend below the group's bottom edge
275
+ when there are not enough rows in the group to push the dropdown
276
+ past it. The header / body each round their own outer corners
277
+ instead so the rounded-card look survives without clipping. */
375
278
  .group {
376
279
  border: solid 1px #cbd4da;
377
280
  border-radius: 4px;
@@ -387,6 +290,11 @@ p.empty {
387
290
  padding: 0.5rem 0.75rem;
388
291
  background-color: #f4f7f9;
389
292
  border-bottom: solid 1px #cbd4da;
293
+ /* Round the header's top corners directly so the tinted strip
294
+ follows the parent's rounded outline. Without this rule
295
+ (combined with the `.group` `overflow: visible` change) the
296
+ header's blue band would leak past the parent's rounded
297
+ corners. */
390
298
  border-top-left-radius: 4px;
391
299
  border-top-right-radius: 4px;
392
300
  }
@@ -399,10 +307,14 @@ p.empty {
399
307
 
400
308
  .group.collapsed > .group-header {
401
309
  border-bottom: none;
310
+ /* When collapsed the header is the only visible row, so it
311
+ has to round all four corners to follow the parent outline. */
402
312
  border-bottom-left-radius: 4px;
403
313
  border-bottom-right-radius: 4px;
404
314
  }
405
315
 
316
+ /* Round the bottom of the body so its visual edge follows the
317
+ parent's rounded outline once `overflow: hidden` is gone. */
406
318
  .group:not(.collapsed) > .group-body {
407
319
  border-bottom-left-radius: 4px;
408
320
  border-bottom-right-radius: 4px;