@lavalogic/scoria 0.37.56 → 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
@@ -0,0 +1,644 @@
1
+ <svelte:options runes />
2
+
3
+ <script
4
+ lang="ts"
5
+ generics="T extends object, IdType extends Primitive"
6
+ >
7
+ import Button from '../../Button.svelte';
8
+ import DesktopModal from '../../DesktopModal.svelte';
9
+ import Icon from '../../Icon.svelte';
10
+ import TextInput from '../../TextInput.svelte';
11
+ import { Size } from '../../../Types/Internal/Size.js';
12
+ import { Variant } from '../../../Types/Internal/Variant.js';
13
+ import { getContext } from 'svelte';
14
+ import { TableContext } from '../Types/Context/TableContext.svelte.js';
15
+ import type { Primitive } from '../Types/Context/TableInitOptions.js';
16
+ import type { DatatableView } from '../Types/Persistence/DatatableView.js';
17
+ import type { DatatableViewKind } from '../Types/Persistence/DatatableViewKind.js';
18
+ import type { TableConfigurationModalProps } from './TableConfigurationModalProps.js';
19
+
20
+ const tableContext = getContext<TableContext<T, IdType>>(TableContext.identifier);
21
+
22
+ const { onclose }: TableConfigurationModalProps = $props();
23
+
24
+ // ── Left-nav section + right-pane tab selection ──────────────────────
25
+ // `section` picks the kind (layout vs filter); `tab` picks which of the
26
+ // three horizontal panes is visible for that section. Both are plain
27
+ // `$state` since they are pure local UI selection.
28
+ let section = $state<DatatableViewKind>('layout');
29
+ type ConfigTab = 'mine' | 'shared' | 'save';
30
+ let tab = $state<ConfigTab>('mine');
31
+
32
+ // Name field for the "Save a new ..." tab.
33
+ let newViewName = $state('');
34
+
35
+ // Public-views discovery state for the "Subscribe to Additional" flow.
36
+ // `null` = not yet loaded; an array = the last successful fetch.
37
+ let publicViews = $state<ReadonlyArray<DatatableView> | null>(null);
38
+ let loadingPublicViews = $state(false);
39
+
40
+ // User-visible error banner. Every adapter call is wrapped so a
41
+ // rejection lands here instead of throwing into the UI. scoria cannot
42
+ // assume a host-provided toast context exists, so the modal surfaces
43
+ // errors inline in its own body.
44
+ let errorMessage = $state<string | null>(null);
45
+
46
+ // Whether remote saved views are available at all. A table created
47
+ // without `datatableUuid` / `remoteLayouts` opts out of remote views;
48
+ // the modal then renders a graceful "unavailable" empty state and
49
+ // makes zero adapter calls.
50
+ const remoteAvailable = $derived(
51
+ tableContext.datatableUuid !== undefined && tableContext.remoteLayouts !== undefined
52
+ );
53
+
54
+ // The opaque current-user id, used to scope "mine" vs "shared" and to
55
+ // drive subscribe / unsubscribe. `undefined` when the host supplied
56
+ // `() => undefined` - sharing controls then degrade gracefully.
57
+ const currentUserId = $derived(tableContext.getUserId());
58
+
59
+ // All saved views of the active section, split by ownership. A view the
60
+ // current user owns is "mine"; anything else they can see is "shared"
61
+ // (a subscription). When no user id is available every view falls into
62
+ // "mine" so the user can still manage what they have.
63
+ const sectionViews = $derived(tableContext._viewState.savedViews(section));
64
+ const myViews = $derived(
65
+ sectionViews.filter((view) => currentUserId === undefined || view.ownerUserId === currentUserId)
66
+ );
67
+ const sharedViews = $derived(
68
+ sectionViews.filter((view) => currentUserId !== undefined && view.ownerUserId !== currentUserId)
69
+ );
70
+
71
+ // Human labels for the active section, so the three tabs and headings
72
+ // read "My Layouts" / "My Filters" etc. without branching everywhere.
73
+ const sectionNoun = $derived(section === 'filter' ? 'Filter' : 'Layout');
74
+ const sectionNounPlural = $derived(section === 'filter' ? 'Filters' : 'Layouts');
75
+
76
+ /**
77
+ * Run an adapter-backed action, clearing any prior error first and
78
+ * routing a rejection to the inline error banner. Keeps every async
79
+ * branch below a one-liner and guarantees no rejection escapes to the
80
+ * UI (the modal never throws).
81
+ */
82
+ async function guard(action: () => Promise<void>, failureMessage: string): Promise<void> {
83
+ errorMessage = null;
84
+ try {
85
+ await action();
86
+ } catch {
87
+ errorMessage = failureMessage;
88
+ }
89
+ }
90
+
91
+ /** Apply a saved view to the live table. Pure local call - no await. */
92
+ function applyView(view: DatatableView): void {
93
+ errorMessage = null;
94
+ tableContext.applyDatatableView(view);
95
+ }
96
+
97
+ /** Delete a view the current user owns, then refresh the lists. */
98
+ function deleteView(view: DatatableView): void {
99
+ const adapter = tableContext.remoteLayouts;
100
+ if (!adapter) {
101
+ return;
102
+ }
103
+ void guard(async () => {
104
+ await adapter.deleteView(view.id);
105
+ await tableContext.refreshSavedViews();
106
+ }, `Could not delete "${view.name}". Please try again.`);
107
+ }
108
+
109
+ /** Save the live state of the active section as a brand-new view. */
110
+ function saveNewView(): void {
111
+ const trimmed = newViewName.trim();
112
+ if (!trimmed) {
113
+ return;
114
+ }
115
+ void guard(async () => {
116
+ await tableContext.saveCurrentAsNewView(trimmed, section);
117
+ newViewName = '';
118
+ }, `Could not save the new ${sectionNoun.toLowerCase()}. Please try again.`);
119
+ }
120
+
121
+ /** Remove the current user as a subscriber of a shared view. */
122
+ function unsubscribe(view: DatatableView): void {
123
+ const adapter = tableContext.remoteLayouts;
124
+ if (!adapter || currentUserId === undefined) {
125
+ return;
126
+ }
127
+ void guard(async () => {
128
+ await adapter.removeSubscriber(view.id, currentUserId);
129
+ await tableContext.refreshSavedViews();
130
+ }, `Could not unsubscribe from "${view.name}". Please try again.`);
131
+ }
132
+
133
+ /** Remove a named subscriber from a view the current user owns. */
134
+ function removeSubscriber(view: DatatableView, userId: string): void {
135
+ const adapter = tableContext.remoteLayouts;
136
+ if (!adapter) {
137
+ return;
138
+ }
139
+ void guard(async () => {
140
+ await adapter.removeSubscriber(view.id, userId);
141
+ await tableContext.refreshSavedViews();
142
+ }, 'Could not remove the subscriber. Please try again.');
143
+ }
144
+
145
+ /** Fetch the public views the user could subscribe to. */
146
+ function loadPublicViews(): void {
147
+ const adapter = tableContext.remoteLayouts;
148
+ const datatableUuid = tableContext.datatableUuid;
149
+ if (!adapter || datatableUuid === undefined) {
150
+ return;
151
+ }
152
+ loadingPublicViews = true;
153
+ void guard(async () => {
154
+ const all = await adapter.listPublicViews(datatableUuid);
155
+ publicViews = all.filter((view) => view.kind === section);
156
+ }, 'Could not load views available to subscribe to. Please try again.').finally(() => {
157
+ loadingPublicViews = false;
158
+ });
159
+ }
160
+
161
+ /** Subscribe the current user to a public view, then refresh. */
162
+ function subscribe(view: DatatableView): void {
163
+ const adapter = tableContext.remoteLayouts;
164
+ if (!adapter || currentUserId === undefined) {
165
+ return;
166
+ }
167
+ void guard(async () => {
168
+ await adapter.addSubscriber(view.id, currentUserId);
169
+ await tableContext.refreshSavedViews();
170
+ loadPublicViews();
171
+ }, `Could not subscribe to "${view.name}". Please try again.`);
172
+ }
173
+
174
+ /** Switch the active left-nav section, resetting the tab + scratch UI. */
175
+ function selectSection(next: DatatableViewKind): void {
176
+ section = next;
177
+ tab = 'mine';
178
+ newViewName = '';
179
+ publicViews = null;
180
+ errorMessage = null;
181
+ }
182
+
183
+ /** Switch the active right-pane tab, resetting tab-local scratch UI. */
184
+ function selectTab(next: ConfigTab): void {
185
+ tab = next;
186
+ errorMessage = null;
187
+ if (next !== 'save') {
188
+ newViewName = '';
189
+ }
190
+ if (next !== 'shared') {
191
+ publicViews = null;
192
+ }
193
+ }
194
+ </script>
195
+
196
+ <DesktopModal
197
+ headerLabel="Table Configuration"
198
+ headerSubtitle={tableContext.datatableUuid
199
+ ? `Table Identifier: ${tableContext.datatableUuid}`
200
+ : undefined}
201
+ onclose={() => onclose?.()}
202
+ buttons={[{ label: 'Close', callback: () => onclose?.() }]}
203
+ >
204
+ {#if !remoteAvailable}
205
+ <div class="empty-state">
206
+ <Icon
207
+ name="circle-information"
208
+ size={Size.Large}
209
+ />
210
+ <p>Saved layouts and filters are unavailable for this table.</p>
211
+ </div>
212
+ {:else}
213
+ <div class="config">
214
+ <!-- Left vertical nav: pick the kind being configured. -->
215
+ <nav class="side-nav">
216
+ <button
217
+ type="button"
218
+ class="nav-item"
219
+ class:active={section === 'layout'}
220
+ onclick={() => {
221
+ selectSection('layout');
222
+ }}
223
+ >
224
+ Table Layouts
225
+ </button>
226
+ <button
227
+ type="button"
228
+ class="nav-item"
229
+ class:active={section === 'filter'}
230
+ onclick={() => {
231
+ selectSection('filter');
232
+ }}
233
+ >
234
+ Table Filters
235
+ </button>
236
+ </nav>
237
+
238
+ <div class="pane">
239
+ <!-- Three horizontal tabs for the selected section. -->
240
+ <div
241
+ class="tabs"
242
+ role="tablist"
243
+ >
244
+ <button
245
+ type="button"
246
+ role="tab"
247
+ aria-selected={tab === 'mine'}
248
+ class="tab"
249
+ class:active={tab === 'mine'}
250
+ onclick={() => {
251
+ selectTab('mine');
252
+ }}
253
+ >
254
+ My {sectionNounPlural}
255
+ </button>
256
+ <button
257
+ type="button"
258
+ role="tab"
259
+ aria-selected={tab === 'shared'}
260
+ class="tab"
261
+ class:active={tab === 'shared'}
262
+ onclick={() => {
263
+ selectTab('shared');
264
+ }}
265
+ >
266
+ <!-- DESIGN-REVIEW: the mockups were internally inconsistent
267
+ ("Subscribed to Filters" on one, "Shared Filters" on
268
+ another). Resolved to "Shared" consistently for both
269
+ the layout and filter sections. -->
270
+ Shared {sectionNounPlural}
271
+ </button>
272
+ <button
273
+ type="button"
274
+ role="tab"
275
+ aria-selected={tab === 'save'}
276
+ class="tab"
277
+ class:active={tab === 'save'}
278
+ onclick={() => {
279
+ selectTab('save');
280
+ }}
281
+ >
282
+ Save a new {sectionNoun}
283
+ </button>
284
+ </div>
285
+
286
+ {#if errorMessage}
287
+ <div
288
+ class="error-banner"
289
+ role="alert"
290
+ >
291
+ <Icon
292
+ name="circle-exclamation"
293
+ size={Size.Small}
294
+ />
295
+ <span>{errorMessage}</span>
296
+ </div>
297
+ {/if}
298
+
299
+ <div class="tab-body">
300
+ {#if tab === 'mine'}
301
+ {#if myViews.length === 0}
302
+ <p class="muted">
303
+ You have not saved any {sectionNounPlural.toLowerCase()} yet. Use the "Save a new {sectionNoun}"
304
+ tab to create one.
305
+ </p>
306
+ {:else}
307
+ <ul class="card-list">
308
+ {#each myViews as view (view.id)}
309
+ <li class="view-card">
310
+ <div class="view-card-head">
311
+ <span class="view-name">{view.name}</span>
312
+ <div class="view-actions">
313
+ <Button
314
+ variant={Variant.Secondary}
315
+ size={Size.MediumSmall}
316
+ nowrap
317
+ onclick={() => {
318
+ applyView(view);
319
+ }}>Apply</Button
320
+ >
321
+ <Button
322
+ variant={Variant.Secondary}
323
+ size={Size.MediumSmall}
324
+ nowrap
325
+ iconLeft={{ name: 'external-link', size: Size.Small }}
326
+ onclick={() => {
327
+ selectTab('shared');
328
+ }}>Share</Button
329
+ >
330
+ <Button
331
+ variant={Variant.Secondary}
332
+ size={Size.MediumSmall}
333
+ nowrap
334
+ iconLeft={{ name: 'circle-cross', size: Size.Small }}
335
+ onclick={() => {
336
+ deleteView(view);
337
+ }}>Delete</Button
338
+ >
339
+ </div>
340
+ </div>
341
+ {#if view.subscribers && view.subscribers.length > 0}
342
+ <ul class="subscriber-list">
343
+ {#each view.subscribers as subscriber (subscriber.userId)}
344
+ <li class="subscriber">
345
+ <Icon
346
+ name="user-3"
347
+ size={Size.Small}
348
+ />
349
+ <span>{subscriber.displayName ?? subscriber.userId}</span>
350
+ <Button
351
+ variant={Variant.Secondary}
352
+ size={Size.Tiny}
353
+ nowrap
354
+ onclick={() => {
355
+ removeSubscriber(view, subscriber.userId);
356
+ }}>Remove</Button
357
+ >
358
+ </li>
359
+ {/each}
360
+ </ul>
361
+ {/if}
362
+ </li>
363
+ {/each}
364
+ </ul>
365
+ {/if}
366
+ {:else if tab === 'shared'}
367
+ <div class="shared-head">
368
+ <Button
369
+ variant={Variant.Secondary}
370
+ size={Size.MediumSmall}
371
+ nowrap
372
+ disabled={loadingPublicViews}
373
+ iconLeft={{ name: 'circle-plus', size: Size.Small }}
374
+ onclick={loadPublicViews}>Subscribe to Additional...</Button
375
+ >
376
+ </div>
377
+
378
+ {#if publicViews !== null}
379
+ {#if publicViews.length === 0}
380
+ <p class="muted">
381
+ There are no further {sectionNounPlural.toLowerCase()} available to subscribe to.
382
+ </p>
383
+ {:else}
384
+ <ul class="card-list">
385
+ {#each publicViews as view (view.id)}
386
+ <li class="view-card">
387
+ <div class="view-card-head">
388
+ <span class="view-name">{view.name}</span>
389
+ <div class="view-actions">
390
+ <Button
391
+ variant={Variant.Secondary}
392
+ size={Size.MediumSmall}
393
+ nowrap
394
+ disabled={currentUserId === undefined}
395
+ onclick={() => {
396
+ subscribe(view);
397
+ }}>Subscribe</Button
398
+ >
399
+ </div>
400
+ </div>
401
+ </li>
402
+ {/each}
403
+ </ul>
404
+ {/if}
405
+ {/if}
406
+
407
+ {#if sharedViews.length === 0}
408
+ <p class="muted">
409
+ No {sectionNounPlural.toLowerCase()} are currently shared with you.
410
+ </p>
411
+ {:else}
412
+ <ul class="card-list">
413
+ {#each sharedViews as view (view.id)}
414
+ <li class="view-card">
415
+ <div class="view-card-head">
416
+ <span class="view-name">{view.name}</span>
417
+ <div class="view-actions">
418
+ <Button
419
+ variant={Variant.Secondary}
420
+ size={Size.MediumSmall}
421
+ nowrap
422
+ onclick={() => {
423
+ applyView(view);
424
+ }}>Apply</Button
425
+ >
426
+ <Button
427
+ variant={Variant.Secondary}
428
+ size={Size.MediumSmall}
429
+ nowrap
430
+ disabled={currentUserId === undefined}
431
+ onclick={() => {
432
+ unsubscribe(view);
433
+ }}>Unsubscribe</Button
434
+ >
435
+ </div>
436
+ </div>
437
+ </li>
438
+ {/each}
439
+ </ul>
440
+ {/if}
441
+ {:else}
442
+ <div class="save-tab">
443
+ <p class="muted">
444
+ Save the table's current {sectionNoun.toLowerCase()} as a reusable named
445
+ {sectionNoun.toLowerCase()}.
446
+ </p>
447
+ <TextInput
448
+ placeholder={`${sectionNoun} name`}
449
+ bind:value={newViewName}
450
+ onkeydown={(e: KeyboardEvent) => {
451
+ if (e.key === 'Enter') {
452
+ e.preventDefault();
453
+ saveNewView();
454
+ }
455
+ }}
456
+ />
457
+ <Button
458
+ type="submit"
459
+ variant={Variant.Primary}
460
+ size={Size.MediumSmall}
461
+ nowrap
462
+ disabled={!newViewName.trim()}
463
+ onclick={saveNewView}>Save Current</Button
464
+ >
465
+ </div>
466
+ {/if}
467
+ </div>
468
+ </div>
469
+ </div>
470
+ {/if}
471
+ </DesktopModal>
472
+
473
+ <style>.empty-state {
474
+ display: flex;
475
+ flex-flow: column nowrap;
476
+ align-items: center;
477
+ justify-content: center;
478
+ gap: 0.75rem;
479
+ padding: 3rem 1rem;
480
+ text-align: center;
481
+ color: #3a4952;
482
+ }
483
+
484
+ .config {
485
+ display: flex;
486
+ flex-flow: row nowrap;
487
+ gap: 1rem;
488
+ flex: 1;
489
+ min-height: 0;
490
+ width: 100%;
491
+ }
492
+
493
+ .side-nav {
494
+ display: flex;
495
+ flex-flow: column nowrap;
496
+ gap: 0.25rem;
497
+ flex-shrink: 0;
498
+ border-right: solid 2px #cbd4da;
499
+ padding-right: 1rem;
500
+ }
501
+ .side-nav .nav-item {
502
+ text-align: left;
503
+ white-space: nowrap;
504
+ padding: 0.6rem 0.9rem;
505
+ border: solid 1px #cbd4da;
506
+ border-radius: 4px;
507
+ background-color: #ffffff;
508
+ color: #3a4952;
509
+ font-weight: 600;
510
+ cursor: pointer;
511
+ }
512
+ .side-nav .nav-item:hover {
513
+ background-color: #e9ecf1;
514
+ }
515
+ .side-nav .nav-item.active {
516
+ background-color: #cbd4da;
517
+ color: #455661;
518
+ }
519
+
520
+ .pane {
521
+ display: flex;
522
+ flex-flow: column nowrap;
523
+ gap: 0.75rem;
524
+ flex: 1;
525
+ min-width: 0;
526
+ }
527
+
528
+ .tabs {
529
+ display: flex;
530
+ flex-flow: row nowrap;
531
+ gap: 0.25rem;
532
+ border-bottom: solid 2px #cbd4da;
533
+ }
534
+ .tabs .tab {
535
+ padding: 0.5rem 0.9rem;
536
+ border: none;
537
+ border-bottom: solid 3px transparent;
538
+ background-color: transparent;
539
+ color: #3a4952;
540
+ font-weight: 600;
541
+ white-space: nowrap;
542
+ cursor: pointer;
543
+ }
544
+ .tabs .tab:hover {
545
+ color: #259fc7;
546
+ }
547
+ .tabs .tab.active {
548
+ border-bottom-color: #259fc7;
549
+ color: #259fc7;
550
+ }
551
+
552
+ .error-banner {
553
+ display: flex;
554
+ flex-flow: row nowrap;
555
+ align-items: center;
556
+ gap: 0.5rem;
557
+ padding: 0.5rem 0.75rem;
558
+ border: solid 1px #aa1414;
559
+ border-radius: 4px;
560
+ background-color: #f9e9e9;
561
+ color: #aa1414;
562
+ }
563
+
564
+ .tab-body {
565
+ flex: 1;
566
+ min-height: 0;
567
+ overflow-y: auto;
568
+ }
569
+
570
+ .muted {
571
+ color: #647d8e;
572
+ }
573
+
574
+ .card-list {
575
+ list-style: none;
576
+ margin: 0;
577
+ padding: 0;
578
+ display: flex;
579
+ flex-flow: column nowrap;
580
+ gap: 0.5rem;
581
+ }
582
+
583
+ .view-card {
584
+ border: solid 1px #cbd4da;
585
+ border-radius: 4px;
586
+ padding: 0.6rem 0.75rem;
587
+ background-color: #ffffff;
588
+ display: flex;
589
+ flex-flow: column nowrap;
590
+ gap: 0.5rem;
591
+ }
592
+
593
+ .view-card-head {
594
+ display: flex;
595
+ flex-flow: row nowrap;
596
+ align-items: center;
597
+ justify-content: space-between;
598
+ gap: 0.75rem;
599
+ }
600
+
601
+ .view-name {
602
+ font-weight: 600;
603
+ overflow: hidden;
604
+ text-overflow: ellipsis;
605
+ white-space: nowrap;
606
+ }
607
+
608
+ .view-actions {
609
+ display: flex;
610
+ flex-flow: row nowrap;
611
+ gap: 0.375rem;
612
+ flex-shrink: 0;
613
+ }
614
+
615
+ .subscriber-list {
616
+ list-style: none;
617
+ margin: 0;
618
+ padding: 0.25rem 0 0;
619
+ display: flex;
620
+ flex-flow: column nowrap;
621
+ gap: 0.25rem;
622
+ border-top: solid 1px #dae0e5;
623
+ }
624
+
625
+ .subscriber {
626
+ display: flex;
627
+ flex-flow: row nowrap;
628
+ align-items: center;
629
+ gap: 0.5rem;
630
+ font-size: 0.875rem;
631
+ }
632
+
633
+ .shared-head {
634
+ display: flex;
635
+ flex-flow: row nowrap;
636
+ margin-bottom: 0.5rem;
637
+ }
638
+
639
+ .save-tab {
640
+ display: flex;
641
+ flex-flow: column nowrap;
642
+ gap: 0.75rem;
643
+ max-width: 24rem;
644
+ }</style>
@@ -0,0 +1,26 @@
1
+ import type { Primitive } from '../Types/Context/TableInitOptions.js';
2
+ import type { TableConfigurationModalProps } from './TableConfigurationModalProps.js';
3
+ declare function $$render<T extends object, IdType extends Primitive>(): {
4
+ props: TableConfigurationModalProps;
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 TableConfigurationModal: $$IsomorphicComponent;
25
+ type TableConfigurationModal<T extends object, IdType extends Primitive> = InstanceType<typeof TableConfigurationModal<T, IdType>>;
26
+ export default TableConfigurationModal;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Props for `TableConfigurationModal` - the FPM 403 "Table Configuration"
3
+ * modal that manages saved datatable layouts and filters (the new view
4
+ * system that supersedes the old named-preset box).
5
+ *
6
+ * The modal is driven entirely by the `TableContext` it reads from
7
+ * Svelte context; it has no data props of its own. It only needs a way
8
+ * to tell its host the panel should close.
9
+ */
10
+ export interface TableConfigurationModalProps {
11
+ /** Called when the modal requests close (footer button / backdrop). */
12
+ onclose?: () => void;
13
+ }