@lavalogic/scoria 0.38.7 → 0.38.9
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/dist/Components/InfoAlert.svelte +65 -0
- package/dist/Components/InfoAlert.svelte.d.ts +4 -0
- package/dist/Components/InfoAlertProps.d.ts +24 -0
- package/dist/Components/InfoAlertProps.js +1 -0
- package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +14 -1
- package/dist/Components/Table/Misc/ShareViewModal.svelte +378 -0
- package/dist/Components/Table/Misc/ShareViewModal.svelte.d.ts +26 -0
- package/dist/Components/Table/Misc/ShareViewModalProps.d.ts +24 -0
- package/dist/Components/Table/Misc/ShareViewModalProps.js +1 -0
- package/dist/Components/Table/Misc/TableConfigurationModal.svelte +66 -15
- package/dist/Components/Table/Types/Context/TableContext.svelte.d.ts +12 -0
- package/dist/Components/Table/Types/Context/TableContext.svelte.js +44 -2
- package/dist/Components/Table/Types/Persistence/RemoteTableLayoutAdapter.d.ts +24 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { Size } from '../Types/Internal/Size.js';
|
|
5
|
+
import Icon from './Icon.svelte';
|
|
6
|
+
import type { InfoAlertProps } from './InfoAlertProps.js';
|
|
7
|
+
|
|
8
|
+
const { children, iconName = 'circle-information' }: InfoAlertProps = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<!--
|
|
12
|
+
Small, inline informational banner. Mirrors the structure of the
|
|
13
|
+
`.error-banner` block already used inside the Table Configuration
|
|
14
|
+
modal - leading icon + body text on a tinted background - but on a
|
|
15
|
+
neutral blue palette so it reads as "helper text" rather than "error".
|
|
16
|
+
|
|
17
|
+
The body is a snippet so callers can inline links or emphasis without
|
|
18
|
+
the component prescribing a content shape.
|
|
19
|
+
-->
|
|
20
|
+
<div
|
|
21
|
+
class="info-alert"
|
|
22
|
+
role="status"
|
|
23
|
+
>
|
|
24
|
+
<div class="icon">
|
|
25
|
+
<Icon
|
|
26
|
+
name={iconName}
|
|
27
|
+
size={Size.Small}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="body">
|
|
31
|
+
{@render children()}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<style>.info-alert {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-flow: row nowrap;
|
|
38
|
+
align-items: flex-start;
|
|
39
|
+
gap: 0.5rem;
|
|
40
|
+
padding: 0.625rem 0.875rem;
|
|
41
|
+
border: solid 1px #cbd4da;
|
|
42
|
+
border-radius: 4px;
|
|
43
|
+
background-color: #f4f7f9;
|
|
44
|
+
color: #3a4952;
|
|
45
|
+
font-size: 1.125rem;
|
|
46
|
+
line-height: 1.4;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Nudge the icon down a hair so it sits visually centred against
|
|
50
|
+
the first line of body text rather than aligned to the very top
|
|
51
|
+
of the first character ascent. */
|
|
52
|
+
.icon {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
padding-top: 0.0625rem;
|
|
57
|
+
color: #647d8e;
|
|
58
|
+
--colour: #647d8e;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.body {
|
|
62
|
+
flex: 1 1 auto;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
color: #647d8e;
|
|
65
|
+
}</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { icons } from './Icons.js';
|
|
3
|
+
/**
|
|
4
|
+
* Props for `InfoAlert` - the small, inline informational banner used
|
|
5
|
+
* for non-blocking helper text and empty-state messages (e.g. "you
|
|
6
|
+
* have not saved any layouts yet…", "no filters match the search…").
|
|
7
|
+
*
|
|
8
|
+
* Visually it mirrors the existing `.error-banner` pattern used inside
|
|
9
|
+
* scoria's modals - an icon plus a single line / paragraph of body
|
|
10
|
+
* text on a tinted background - but with neutral / informational
|
|
11
|
+
* colours rather than error-red. It is deliberately a thin shell: any
|
|
12
|
+
* inline links or `<strong>` accents go inside the default snippet.
|
|
13
|
+
*/
|
|
14
|
+
export interface InfoAlertProps {
|
|
15
|
+
/** Body of the alert, rendered to the right of the leading icon. */
|
|
16
|
+
children: Snippet;
|
|
17
|
+
/**
|
|
18
|
+
* Optional icon name override. Defaults to `circle-information`,
|
|
19
|
+
* the standard neutral-info glyph; pass another name from the
|
|
20
|
+
* frozen `icons` registry to fit a more specific empty-state visual
|
|
21
|
+
* (e.g. `circle-exclamation` for a gentle warning).
|
|
22
|
+
*/
|
|
23
|
+
iconName?: keyof typeof icons;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
const { columnDef }: ColumnListRowProps<T> = $props();
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
/**
|
|
22
21
|
* Position of this column inside the sorted column list. Drives the
|
|
23
22
|
* disabled state of the "move up" / "move down" buttons and supplies
|
|
@@ -507,6 +506,14 @@ li.single-column:not(.anchor):last-of-type {
|
|
|
507
506
|
display: flex;
|
|
508
507
|
flex-shrink: 0;
|
|
509
508
|
}
|
|
509
|
+
.visibility-wrapper :global(div.inner-checkbox-container) {
|
|
510
|
+
border-width: 1px;
|
|
511
|
+
border-color: #cbd4da;
|
|
512
|
+
}
|
|
513
|
+
.visibility-wrapper :global(.outer-checkbox-container:hover div.inner-checkbox-container:not(.disabled)) {
|
|
514
|
+
border-width: 1px;
|
|
515
|
+
border-color: #adbbc5;
|
|
516
|
+
}
|
|
510
517
|
|
|
511
518
|
.pin-selector {
|
|
512
519
|
--input-height: 2.25rem;
|
|
@@ -519,6 +526,12 @@ li.single-column:not(.anchor):last-of-type {
|
|
|
519
526
|
.pin-selector.disabled {
|
|
520
527
|
opacity: 0.5;
|
|
521
528
|
}
|
|
529
|
+
.pin-selector :global(.radios > label) {
|
|
530
|
+
padding: 0;
|
|
531
|
+
}
|
|
532
|
+
.pin-selector :global(.radios > label:first-child) {
|
|
533
|
+
border-left: none;
|
|
534
|
+
}
|
|
522
535
|
|
|
523
536
|
.column-label {
|
|
524
537
|
width: 100%;
|
|
@@ -0,0 +1,378 @@
|
|
|
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 InfoAlert from '../../InfoAlert.svelte';
|
|
11
|
+
import MultiSelect from '../../MultiSelect.svelte';
|
|
12
|
+
import { cssLength } from '../../../Helpers/Helpers.svelte.js';
|
|
13
|
+
import { ColourSet } from '../../../scss/colours.js';
|
|
14
|
+
import { Size } from '../../../Types/Internal/Size.js';
|
|
15
|
+
import { Variant } from '../../../Types/Internal/Variant.js';
|
|
16
|
+
import { getContext, onMount } from 'svelte';
|
|
17
|
+
import { TableContext } from '../Types/Context/TableContext.svelte.js';
|
|
18
|
+
import type { Primitive } from '../Types/Context/TableInitOptions.js';
|
|
19
|
+
import type { DatatableViewUser } from '../Types/Persistence/RemoteTableLayoutAdapter.js';
|
|
20
|
+
import type { ShareViewModalProps } from './ShareViewModalProps.js';
|
|
21
|
+
|
|
22
|
+
const { view, onclose, onsaved }: ShareViewModalProps = $props();
|
|
23
|
+
|
|
24
|
+
const tableContext = getContext<TableContext<T, IdType>>(TableContext.identifier);
|
|
25
|
+
|
|
26
|
+
// All users the current user is allowed to share with. `null` until the
|
|
27
|
+
// boot-time `listUsers` fetch resolves; an array (possibly empty) once
|
|
28
|
+
// the call lands. Bound to the `MultiSelect` options below.
|
|
29
|
+
let allUsers = $state<ReadonlyArray<DatatableViewUser> | null>(null);
|
|
30
|
+
|
|
31
|
+
// User-visible error banner. Adapter rejections are routed here so the
|
|
32
|
+
// modal never throws into the host UI.
|
|
33
|
+
let errorMessage = $state<string | null>(null);
|
|
34
|
+
|
|
35
|
+
// In-flight flags for the two adapter call sites. `loadingUsers` blanks
|
|
36
|
+
// the picker while `listUsers` is in flight; `saving` disables the
|
|
37
|
+
// footer buttons while the subscribe / unsubscribe deltas land.
|
|
38
|
+
let loadingUsers = $state(false);
|
|
39
|
+
let saving = $state(false);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Current picker selection. Each entry is a `DatatableViewUser`. The
|
|
43
|
+
* initial value is seeded from `view.subscribers` (the existing
|
|
44
|
+
* roster) inside `onMount` so opening the modal shows "who is already
|
|
45
|
+
* shared with", and Save can diff against the originals.
|
|
46
|
+
*
|
|
47
|
+
* `displayName` falls back to the opaque `userId` when the subscriber
|
|
48
|
+
* record carries no label, matching the rest of the Configuration
|
|
49
|
+
* modal's fallback behaviour.
|
|
50
|
+
*/
|
|
51
|
+
let selected = $state<Array<DatatableViewUser>>([]);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Snapshot of the subscriber ids the view started with. The Save diff
|
|
55
|
+
* reads it to compute which subscribers were added (POST subscribe)
|
|
56
|
+
* vs removed (POST unsubscribe). Seeded in `onMount` from
|
|
57
|
+
* `view.subscribers` so the snapshot stays a true *initial* roster
|
|
58
|
+
* regardless of any concurrent edits to `view`.
|
|
59
|
+
*
|
|
60
|
+
* Typed as a plain `Set` (not `ReadonlySet`) only because it is
|
|
61
|
+
* reassigned once in `onMount`; reads downstream still treat it as
|
|
62
|
+
* read-only. Not reactive - the modal owns this snapshot for its
|
|
63
|
+
* lifetime and never re-derives it.
|
|
64
|
+
*/
|
|
65
|
+
let initialIds: Set<string> = new Set();
|
|
66
|
+
|
|
67
|
+
// Whether anything in the picker differs from the initial roster.
|
|
68
|
+
// Drives the Save button's disabled state so a no-op Save never hits
|
|
69
|
+
// the network.
|
|
70
|
+
const dirty = $derived.by(() => {
|
|
71
|
+
const currentIds = new Set(selected.map((s) => s.id));
|
|
72
|
+
if (currentIds.size !== initialIds.size) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
for (const id of currentIds) {
|
|
76
|
+
if (!initialIds.has(id)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
onMount(() => {
|
|
84
|
+
// Snapshot the initial subscriber roster once on mount so the
|
|
85
|
+
// Save diff has a stable starting point. Done inside `onMount`
|
|
86
|
+
// (rather than at `$state` init) so Svelte's reactivity lint does
|
|
87
|
+
// not flag the read as "captures the initial value of `view`" -
|
|
88
|
+
// the modal owns this snapshot for its lifetime intentionally.
|
|
89
|
+
const initial = (view.subscribers ?? []).map((s) => ({
|
|
90
|
+
id: s.userId,
|
|
91
|
+
displayName: s.displayName?.trim() ? s.displayName : s.userId,
|
|
92
|
+
}));
|
|
93
|
+
selected = initial;
|
|
94
|
+
initialIds = new Set(initial.map((u) => u.id));
|
|
95
|
+
|
|
96
|
+
const adapter = tableContext.remoteLayouts;
|
|
97
|
+
if (!adapter) {
|
|
98
|
+
errorMessage = 'Sharing is unavailable: no remote layouts adapter is configured.';
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
loadingUsers = true;
|
|
102
|
+
adapter
|
|
103
|
+
.listUsers()
|
|
104
|
+
.then((users) => {
|
|
105
|
+
// Exclude the view's owner from the picker - sharing a view
|
|
106
|
+
// with its own owner is nonsensical and the backend would
|
|
107
|
+
// reject the subscribe anyway.
|
|
108
|
+
allUsers = users.filter((u) => u.id !== view.ownerUserId);
|
|
109
|
+
})
|
|
110
|
+
.catch((e: unknown) => {
|
|
111
|
+
console.error('[ShareViewModal] listUsers failed:', e);
|
|
112
|
+
errorMessage =
|
|
113
|
+
e instanceof Error
|
|
114
|
+
? `Could not load the user list. (${e.message})`
|
|
115
|
+
: 'Could not load the user list.';
|
|
116
|
+
})
|
|
117
|
+
.finally(() => {
|
|
118
|
+
loadingUsers = false;
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/** Remove a user from the current selection. */
|
|
123
|
+
function removeFromSelection(userId: string): void {
|
|
124
|
+
selected = selected.filter((s) => s.id !== userId);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Apply the picker selection by issuing the required deltas. */
|
|
128
|
+
async function save(): Promise<void> {
|
|
129
|
+
const adapter = tableContext.remoteLayouts;
|
|
130
|
+
if (!adapter) {
|
|
131
|
+
errorMessage = 'Sharing is unavailable: no remote layouts adapter is configured.';
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!dirty) {
|
|
135
|
+
onclose();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const currentIds = new Set(selected.map((s) => s.id));
|
|
140
|
+
const toAdd: Array<string> = [];
|
|
141
|
+
const toRemove: Array<string> = [];
|
|
142
|
+
|
|
143
|
+
// Added: in the new selection but not the original roster.
|
|
144
|
+
for (const id of currentIds) {
|
|
145
|
+
if (!initialIds.has(id)) {
|
|
146
|
+
toAdd.push(id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Removed: in the original roster but not the new selection.
|
|
150
|
+
for (const id of initialIds) {
|
|
151
|
+
if (!currentIds.has(id)) {
|
|
152
|
+
toRemove.push(id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
errorMessage = null;
|
|
157
|
+
saving = true;
|
|
158
|
+
try {
|
|
159
|
+
// Run sequentially so a partial failure surfaces a useful error
|
|
160
|
+
// and leaves the already-applied deltas committed (an aborted
|
|
161
|
+
// `Promise.all` is much harder to reason about).
|
|
162
|
+
for (const userId of toAdd) {
|
|
163
|
+
await adapter.addSubscriber(view.id, userId);
|
|
164
|
+
}
|
|
165
|
+
for (const userId of toRemove) {
|
|
166
|
+
await adapter.removeSubscriber(view.id, userId);
|
|
167
|
+
}
|
|
168
|
+
if (onsaved) {
|
|
169
|
+
await onsaved();
|
|
170
|
+
}
|
|
171
|
+
onclose();
|
|
172
|
+
} catch (e: unknown) {
|
|
173
|
+
console.error('[ShareViewModal] save failed:', e);
|
|
174
|
+
errorMessage =
|
|
175
|
+
e instanceof Error
|
|
176
|
+
? `Could not update sharing. Some changes may have been applied. (${e.message})`
|
|
177
|
+
: 'Could not update sharing. Some changes may have been applied.';
|
|
178
|
+
} finally {
|
|
179
|
+
saving = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<DesktopModal
|
|
185
|
+
headerLabel={`Share "${view.name}"`}
|
|
186
|
+
headerSubtitle={`Choose who can access this ${view.kind === 'filter' ? 'filter' : 'layout'}.`}
|
|
187
|
+
onclose={() => {
|
|
188
|
+
if (!saving) {
|
|
189
|
+
onclose();
|
|
190
|
+
}
|
|
191
|
+
}}
|
|
192
|
+
width={cssLength('40vw')}
|
|
193
|
+
minWidth={cssLength('32rem')}
|
|
194
|
+
buttons={[
|
|
195
|
+
{
|
|
196
|
+
label: 'Cancel',
|
|
197
|
+
callback: () => {
|
|
198
|
+
if (!saving) {
|
|
199
|
+
onclose();
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
disabled: saving,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
label: saving ? 'Saving…' : 'Save',
|
|
206
|
+
callback: () => {
|
|
207
|
+
void save();
|
|
208
|
+
},
|
|
209
|
+
colourSet: ColourSet.Primary,
|
|
210
|
+
disabled: saving || !dirty || allUsers === null,
|
|
211
|
+
},
|
|
212
|
+
]}
|
|
213
|
+
>
|
|
214
|
+
<div class="share-body">
|
|
215
|
+
{#if errorMessage}
|
|
216
|
+
<div
|
|
217
|
+
class="error-banner"
|
|
218
|
+
role="alert"
|
|
219
|
+
>
|
|
220
|
+
<Icon
|
|
221
|
+
name="circle-exclamation"
|
|
222
|
+
size={Size.Small}
|
|
223
|
+
/>
|
|
224
|
+
<span>{errorMessage}</span>
|
|
225
|
+
</div>
|
|
226
|
+
{/if}
|
|
227
|
+
|
|
228
|
+
<div class="picker">
|
|
229
|
+
{#if loadingUsers && allUsers === null}
|
|
230
|
+
<InfoAlert>Loading users…</InfoAlert>
|
|
231
|
+
{:else if allUsers !== null}
|
|
232
|
+
<MultiSelect
|
|
233
|
+
label="Users"
|
|
234
|
+
name="User"
|
|
235
|
+
options={allUsers as Array<DatatableViewUser>}
|
|
236
|
+
labelProp="displayName"
|
|
237
|
+
valueProp="id"
|
|
238
|
+
valueAsObject={true}
|
|
239
|
+
selectedValues={selected}
|
|
240
|
+
onchange={(values) => {
|
|
241
|
+
selected = values;
|
|
242
|
+
}}
|
|
243
|
+
/>
|
|
244
|
+
{/if}
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{#if selected.length === 0}
|
|
248
|
+
<InfoAlert>
|
|
249
|
+
No users selected. Use the picker above to share this {view.kind === 'filter'
|
|
250
|
+
? 'filter'
|
|
251
|
+
: 'layout'} with one or more users.
|
|
252
|
+
</InfoAlert>
|
|
253
|
+
{:else}
|
|
254
|
+
<ul class="user-card-list">
|
|
255
|
+
{#each selected as user (user.id)}
|
|
256
|
+
<li class="user-card">
|
|
257
|
+
<div class="user-info">
|
|
258
|
+
<Icon
|
|
259
|
+
name="user-3"
|
|
260
|
+
size={Size.Medium}
|
|
261
|
+
/>
|
|
262
|
+
<span class="user-name">{user.displayName}</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="user-meta">
|
|
265
|
+
<!--
|
|
266
|
+
v1 sharing exposes a single role - subscriber - so
|
|
267
|
+
the "permission" surface is informational only. The
|
|
268
|
+
backend's `editor` / `systemOwned` flags are
|
|
269
|
+
reserved no-ops in v1 (see `DatatableView` DTO
|
|
270
|
+
JSDoc); scoria never sets them.
|
|
271
|
+
-->
|
|
272
|
+
<span class="permission-pill">Subscriber</span>
|
|
273
|
+
<Button
|
|
274
|
+
variant={Variant.Secondary}
|
|
275
|
+
size={Size.MediumSmall}
|
|
276
|
+
nowrap
|
|
277
|
+
iconLeft={{ name: 'circle-cross', size: Size.Small }}
|
|
278
|
+
disabled={saving}
|
|
279
|
+
onclick={() => {
|
|
280
|
+
removeFromSelection(user.id);
|
|
281
|
+
}}>Remove</Button
|
|
282
|
+
>
|
|
283
|
+
</div>
|
|
284
|
+
</li>
|
|
285
|
+
{/each}
|
|
286
|
+
</ul>
|
|
287
|
+
{/if}
|
|
288
|
+
</div>
|
|
289
|
+
</DesktopModal>
|
|
290
|
+
|
|
291
|
+
<style>.share-body {
|
|
292
|
+
display: flex;
|
|
293
|
+
flex-flow: column nowrap;
|
|
294
|
+
gap: 0.75rem;
|
|
295
|
+
padding: 1rem;
|
|
296
|
+
min-height: 25vh;
|
|
297
|
+
max-height: 60vh;
|
|
298
|
+
overflow-y: auto;
|
|
299
|
+
background-color: #ffffff;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.error-banner {
|
|
303
|
+
display: flex;
|
|
304
|
+
flex-flow: row nowrap;
|
|
305
|
+
align-items: center;
|
|
306
|
+
gap: 0.5rem;
|
|
307
|
+
padding: 0.5rem 0.75rem;
|
|
308
|
+
border: solid 1px #aa1414;
|
|
309
|
+
border-radius: 4px;
|
|
310
|
+
background-color: #f9e9e9;
|
|
311
|
+
color: #aa1414;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.picker {
|
|
315
|
+
display: flex;
|
|
316
|
+
flex-flow: column nowrap;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.muted {
|
|
320
|
+
color: #647d8e;
|
|
321
|
+
margin: 0;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.user-card-list {
|
|
325
|
+
list-style: none;
|
|
326
|
+
margin: 0;
|
|
327
|
+
padding: 0;
|
|
328
|
+
display: flex;
|
|
329
|
+
flex-flow: column nowrap;
|
|
330
|
+
gap: 0.5rem;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.user-card {
|
|
334
|
+
display: flex;
|
|
335
|
+
flex-flow: row nowrap;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: space-between;
|
|
338
|
+
gap: 0.75rem;
|
|
339
|
+
padding: 0.6rem 0.75rem;
|
|
340
|
+
border: solid 1px #cbd4da;
|
|
341
|
+
border-radius: 4px;
|
|
342
|
+
background-color: #ffffff;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.user-info {
|
|
346
|
+
display: flex;
|
|
347
|
+
flex-flow: row nowrap;
|
|
348
|
+
align-items: center;
|
|
349
|
+
gap: 0.5rem;
|
|
350
|
+
min-width: 0;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.user-name {
|
|
354
|
+
font-weight: 600;
|
|
355
|
+
overflow: hidden;
|
|
356
|
+
text-overflow: ellipsis;
|
|
357
|
+
white-space: nowrap;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.user-meta {
|
|
361
|
+
display: flex;
|
|
362
|
+
flex-flow: row nowrap;
|
|
363
|
+
align-items: center;
|
|
364
|
+
gap: 0.5rem;
|
|
365
|
+
flex-shrink: 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* v1 permissions are informational only - one pill per card showing
|
|
369
|
+
"Subscriber". The visual is a quiet tag so it does not compete with
|
|
370
|
+
the Remove action; the backend's `editor` / `systemOwned` flags are
|
|
371
|
+
reserved no-ops in v1. */
|
|
372
|
+
.permission-pill {
|
|
373
|
+
font-size: 0.875rem;
|
|
374
|
+
padding: 0.15rem 0.5rem;
|
|
375
|
+
border-radius: 999px;
|
|
376
|
+
background-color: #e9ecf1;
|
|
377
|
+
color: #3a4952;
|
|
378
|
+
}</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Primitive } from '../Types/Context/TableInitOptions.js';
|
|
2
|
+
import type { ShareViewModalProps } from './ShareViewModalProps.js';
|
|
3
|
+
declare function $$render<T extends object, IdType extends Primitive>(): {
|
|
4
|
+
props: ShareViewModalProps;
|
|
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 ShareViewModal: $$IsomorphicComponent;
|
|
25
|
+
type ShareViewModal<T extends object, IdType extends Primitive> = InstanceType<typeof ShareViewModal<T, IdType>>;
|
|
26
|
+
export default ShareViewModal;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DatatableView } from '../Types/Persistence/DatatableView.js';
|
|
2
|
+
/**
|
|
3
|
+
* Props for `ShareViewModal` - the FPM 403 round-4 share dialog opened
|
|
4
|
+
* from a saved view card's "Share" button. The modal renders a user
|
|
5
|
+
* picker (driven by `RemoteTableLayoutAdapter.listUsers`) plus a
|
|
6
|
+
* permission card per selected user; on Save it diffs the picker
|
|
7
|
+
* selection against the view's current `subscribers` and issues one
|
|
8
|
+
* `addSubscriber` / `removeSubscriber` call per change.
|
|
9
|
+
*/
|
|
10
|
+
export interface ShareViewModalProps {
|
|
11
|
+
/** The view being shared. The modal reads its current `subscribers`
|
|
12
|
+
* roster as the initial picker selection and writes deltas back via
|
|
13
|
+
* `addSubscriber` / `removeSubscriber`. */
|
|
14
|
+
view: DatatableView;
|
|
15
|
+
/** Called when the modal should close - both on Cancel and after a
|
|
16
|
+
* successful Save. The host (Table Configuration modal) is also
|
|
17
|
+
* expected to refresh its view list after a successful Save so the
|
|
18
|
+
* subscriber strip on the card reflects the change. */
|
|
19
|
+
onclose: () => void;
|
|
20
|
+
/** Called after a successful Save, before `onclose`. The host wires
|
|
21
|
+
* this to a saved-views refresh so the subscriber strip on the
|
|
22
|
+
* caller's card updates. Optional - omit to skip the refresh. */
|
|
23
|
+
onsaved?: () => void | Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import DesktopModal from '../../DesktopModal.svelte';
|
|
9
9
|
import HorizontalTabGroup from '../../HorizontalTabGroup.svelte';
|
|
10
10
|
import Icon from '../../Icon.svelte';
|
|
11
|
+
import InfoAlert from '../../InfoAlert.svelte';
|
|
11
12
|
import TextInput from '../../TextInput.svelte';
|
|
12
13
|
import VerticalTabGroup from '../../VerticalTabGroup.svelte';
|
|
13
14
|
import { cssLength } from '../../../Helpers/Helpers.svelte.js';
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
import type { Primitive } from '../Types/Context/TableInitOptions.js';
|
|
20
21
|
import type { DatatableView } from '../Types/Persistence/DatatableView.js';
|
|
21
22
|
import type { DatatableViewKind } from '../Types/Persistence/DatatableViewKind.js';
|
|
23
|
+
import ShareViewModal from './ShareViewModal.svelte';
|
|
22
24
|
import type { TableConfigurationModalProps } from './TableConfigurationModalProps.js';
|
|
23
25
|
|
|
24
26
|
const tableContext = getContext<TableContext<T, IdType>>(TableContext.identifier);
|
|
@@ -52,6 +54,12 @@
|
|
|
52
54
|
// My-tab shows "Loading…" rather than the "nothing saved yet" message.
|
|
53
55
|
let loadingViews = $state(false);
|
|
54
56
|
|
|
57
|
+
// The view whose Share modal is currently open. `null` when no share
|
|
58
|
+
// modal is rendered. Owner cards (rendered on the My-tab) wire their
|
|
59
|
+
// Share button to open `ShareViewModal` against the view they belong
|
|
60
|
+
// to; the modal closes itself on Cancel / Save.
|
|
61
|
+
let sharingView = $state<DatatableView | null>(null);
|
|
62
|
+
|
|
55
63
|
// Whether remote saved views are available at all. A table created
|
|
56
64
|
// without `datatableUuid` / `remoteLayouts` opts out of remote views;
|
|
57
65
|
// the modal then renders a graceful "unavailable" empty state and
|
|
@@ -329,12 +337,12 @@
|
|
|
329
337
|
|
|
330
338
|
{#if args.tab === 'mine'}
|
|
331
339
|
{#if loadingViews}
|
|
332
|
-
<
|
|
340
|
+
<InfoAlert>Loading saved {sectionNounPlural.toLowerCase()}…</InfoAlert>
|
|
333
341
|
{:else if myViews.length === 0}
|
|
334
|
-
<
|
|
342
|
+
<InfoAlert>
|
|
335
343
|
You have not saved any {sectionNounPlural.toLowerCase()} yet. Use the "Save a new {sectionNoun}"
|
|
336
344
|
tab to create one.
|
|
337
|
-
</
|
|
345
|
+
</InfoAlert>
|
|
338
346
|
{:else}
|
|
339
347
|
<ul class="card-list">
|
|
340
348
|
{#each myViews as view (view.id)}
|
|
@@ -356,7 +364,12 @@
|
|
|
356
364
|
nowrap
|
|
357
365
|
iconLeft={{ name: 'external-link', size: Size.Small }}
|
|
358
366
|
onclick={() => {
|
|
359
|
-
|
|
367
|
+
// Open the share dialog rather than switching to
|
|
368
|
+
// the "Shared" tab - subscribing OTHER users to
|
|
369
|
+
// this view is a distinct flow from "Subscribe
|
|
370
|
+
// to Additional" (which is the current user
|
|
371
|
+
// joining a public view).
|
|
372
|
+
sharingView = view;
|
|
360
373
|
}}>Share</Button
|
|
361
374
|
>
|
|
362
375
|
<Button
|
|
@@ -409,9 +422,9 @@
|
|
|
409
422
|
|
|
410
423
|
{#if publicViews !== null}
|
|
411
424
|
{#if publicViews.length === 0}
|
|
412
|
-
<
|
|
425
|
+
<InfoAlert>
|
|
413
426
|
There are no further {sectionNounPlural.toLowerCase()} available to subscribe to.
|
|
414
|
-
</
|
|
427
|
+
</InfoAlert>
|
|
415
428
|
{:else}
|
|
416
429
|
<ul class="card-list">
|
|
417
430
|
{#each publicViews as view (view.id)}
|
|
@@ -437,9 +450,9 @@
|
|
|
437
450
|
{/if}
|
|
438
451
|
|
|
439
452
|
{#if sharedViews.length === 0}
|
|
440
|
-
<
|
|
453
|
+
<InfoAlert>
|
|
441
454
|
No {sectionNounPlural.toLowerCase()} are currently shared with you.
|
|
442
|
-
</
|
|
455
|
+
</InfoAlert>
|
|
443
456
|
{:else}
|
|
444
457
|
<ul class="card-list">
|
|
445
458
|
{#each sharedViews as view (view.id)}
|
|
@@ -472,10 +485,10 @@
|
|
|
472
485
|
{/if}
|
|
473
486
|
{:else}
|
|
474
487
|
<div class="save-tab">
|
|
475
|
-
<
|
|
488
|
+
<InfoAlert>
|
|
476
489
|
Save the table's current {sectionNoun.toLowerCase()} as a reusable named
|
|
477
490
|
{sectionNoun.toLowerCase()}.
|
|
478
|
-
</
|
|
491
|
+
</InfoAlert>
|
|
479
492
|
<TextInput
|
|
480
493
|
placeholder={`${sectionNoun} name`}
|
|
481
494
|
bind:value={newViewName}
|
|
@@ -486,10 +499,16 @@
|
|
|
486
499
|
}
|
|
487
500
|
}}
|
|
488
501
|
/>
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
502
|
+
<!-- Wrap the button in a row so the flex column's default
|
|
503
|
+
`align-items: stretch` does not blow it up to the input's
|
|
504
|
+
width. The row itself can stretch; the button inside sits
|
|
505
|
+
at its natural content width like every other Button. -->
|
|
506
|
+
<div class="save-tab-actions">
|
|
507
|
+
<Button
|
|
508
|
+
disabled={!newViewName.trim()}
|
|
509
|
+
onclick={saveNewView}>Save Current</Button
|
|
510
|
+
>
|
|
511
|
+
</div>
|
|
493
512
|
</div>
|
|
494
513
|
{/if}
|
|
495
514
|
</div>
|
|
@@ -524,6 +543,25 @@
|
|
|
524
543
|
{/if}
|
|
525
544
|
</DesktopModal>
|
|
526
545
|
|
|
546
|
+
<!--
|
|
547
|
+
Share dialog stacked on top of the Configuration modal. Rendered as a
|
|
548
|
+
sibling (not nested in the modal body) so the second modal's overlay
|
|
549
|
+
covers the first - matching how every other layered modal in scoria
|
|
550
|
+
stacks. After a successful Save we refresh the saved-views list so the
|
|
551
|
+
owner's card's subscriber strip updates immediately, then close.
|
|
552
|
+
-->
|
|
553
|
+
{#if sharingView}
|
|
554
|
+
<ShareViewModal
|
|
555
|
+
view={sharingView}
|
|
556
|
+
onclose={() => {
|
|
557
|
+
sharingView = null;
|
|
558
|
+
}}
|
|
559
|
+
onsaved={async () => {
|
|
560
|
+
await tableContext.refreshSavedViews();
|
|
561
|
+
}}
|
|
562
|
+
/>
|
|
563
|
+
{/if}
|
|
564
|
+
|
|
527
565
|
<style>.empty-state {
|
|
528
566
|
display: flex;
|
|
529
567
|
flex-flow: column nowrap;
|
|
@@ -574,7 +612,9 @@
|
|
|
574
612
|
frame visually stable across every section/tab combination (short
|
|
575
613
|
tabs no longer shrink the dialog); the `max-height` + `overflow-y`
|
|
576
614
|
keep taller content scrolling within that fixed frame rather than
|
|
577
|
-
growing the dialog.
|
|
615
|
+
growing the dialog. The white background overrides the wrapping
|
|
616
|
+
`VerticalTabGroup .content` background (`$ui-blue-1`, a grey tint)
|
|
617
|
+
so the body reads as a clean content surface. */
|
|
578
618
|
.tab-body {
|
|
579
619
|
display: flex;
|
|
580
620
|
flex-flow: column nowrap;
|
|
@@ -583,6 +623,7 @@
|
|
|
583
623
|
min-height: 30vh;
|
|
584
624
|
max-height: 60vh;
|
|
585
625
|
overflow-y: auto;
|
|
626
|
+
background-color: #ffffff;
|
|
586
627
|
}
|
|
587
628
|
|
|
588
629
|
.muted {
|
|
@@ -659,4 +700,14 @@
|
|
|
659
700
|
flex-flow: column nowrap;
|
|
660
701
|
gap: 0.75rem;
|
|
661
702
|
max-width: 24rem;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/* Row wrapping the Save Current button. The wrapper stretches with
|
|
706
|
+
the surrounding flex column, but the button inside sits at its
|
|
707
|
+
natural content width (matching every other standard Button) and
|
|
708
|
+
left-aligns on the row. */
|
|
709
|
+
.save-tab-actions {
|
|
710
|
+
display: flex;
|
|
711
|
+
flex-flow: row nowrap;
|
|
712
|
+
justify-content: flex-start;
|
|
662
713
|
}</style>
|
|
@@ -409,6 +409,18 @@ export declare class TableContext<T extends object, RowIdType extends Primitive>
|
|
|
409
409
|
* factory layout regardless of which saved view was active.
|
|
410
410
|
*/
|
|
411
411
|
private _defaultLayoutSnapshot;
|
|
412
|
+
/**
|
|
413
|
+
* Coded-default column widths captured before any user interaction, by
|
|
414
|
+
* column id. Populated once during the boot chain (after
|
|
415
|
+
* `setupDefaultColumnDefs` and before the working copy / saved view
|
|
416
|
+
* hydration overlays anything). `_applyLayout` reads it to restore the
|
|
417
|
+
* factory widths of columns NOT mentioned in the applied layout's
|
|
418
|
+
* `sizing` map; without this, switching back to the Default view (whose
|
|
419
|
+
* `sizing` is empty) would leave previously-resized columns at the
|
|
420
|
+
* resized width because the apply loop only touches columns it has an
|
|
421
|
+
* explicit entry for.
|
|
422
|
+
*/
|
|
423
|
+
private _defaultColumnWidths;
|
|
412
424
|
/**
|
|
413
425
|
* Baseline `JSONTableFilter` the *filter* dirty comparison is
|
|
414
426
|
* measured against: the saved state of whatever filter view
|
|
@@ -500,6 +500,17 @@ export class TableContext {
|
|
|
500
500
|
// `resetToDefault` restores it.
|
|
501
501
|
this._defaultLayoutSnapshot = this._captureLayout();
|
|
502
502
|
this._viewBaseline = this._defaultLayoutSnapshot;
|
|
503
|
+
// Capture each column's coded-default width here, BEFORE
|
|
504
|
+
// any saved snapshot or user-resize can mutate `def.width`.
|
|
505
|
+
// `_applyLayout` consults this map to restore widths of
|
|
506
|
+
// columns absent from an applied layout's `sizing` (e.g.
|
|
507
|
+
// the Default view, whose `sizing` is empty) - otherwise
|
|
508
|
+
// a previously-resized column would stay at its resized
|
|
509
|
+
// width forever.
|
|
510
|
+
this._defaultColumnWidths.clear();
|
|
511
|
+
for (const def of this.columnDefs) {
|
|
512
|
+
this._defaultColumnWidths.set(def.id, def.width);
|
|
513
|
+
}
|
|
503
514
|
// Capture the Default-filter baseline: the empty / initial
|
|
504
515
|
// filter state at boot, BEFORE the localStorage working
|
|
505
516
|
// copy is overlaid. This is the "Default" the FPM 403
|
|
@@ -1015,6 +1026,19 @@ export class TableContext {
|
|
|
1015
1026
|
* factory layout regardless of which saved view was active.
|
|
1016
1027
|
*/
|
|
1017
1028
|
_defaultLayoutSnapshot = undefined;
|
|
1029
|
+
/**
|
|
1030
|
+
* Coded-default column widths captured before any user interaction, by
|
|
1031
|
+
* column id. Populated once during the boot chain (after
|
|
1032
|
+
* `setupDefaultColumnDefs` and before the working copy / saved view
|
|
1033
|
+
* hydration overlays anything). `_applyLayout` reads it to restore the
|
|
1034
|
+
* factory widths of columns NOT mentioned in the applied layout's
|
|
1035
|
+
* `sizing` map; without this, switching back to the Default view (whose
|
|
1036
|
+
* `sizing` is empty) would leave previously-resized columns at the
|
|
1037
|
+
* resized width because the apply loop only touches columns it has an
|
|
1038
|
+
* explicit entry for.
|
|
1039
|
+
*/
|
|
1040
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
1041
|
+
_defaultColumnWidths = new Map();
|
|
1018
1042
|
/**
|
|
1019
1043
|
* Baseline `JSONTableFilter` the *filter* dirty comparison is
|
|
1020
1044
|
* measured against: the saved state of whatever filter view
|
|
@@ -1986,8 +2010,26 @@ export class TableContext {
|
|
|
1986
2010
|
}
|
|
1987
2011
|
this.columnVisibility = visibility;
|
|
1988
2012
|
// Sizing: restore user-resized widths onto the matching defs. The
|
|
1989
|
-
// grid template reads `def.width` + `def.userResized`, so set both
|
|
1990
|
-
//
|
|
2013
|
+
// grid template reads `def.width` + `def.userResized`, so set both.
|
|
2014
|
+
//
|
|
2015
|
+
// Columns ABSENT from `layout.sizing` must be reset to their
|
|
2016
|
+
// coded-default width (with `userResized = false`) - otherwise a
|
|
2017
|
+
// previously-resized column stays at its resized width forever when
|
|
2018
|
+
// the user switches to a layout that does not mention it (most
|
|
2019
|
+
// notably the Default view, whose `sizing` is empty). Two passes:
|
|
2020
|
+
// reset everything to its coded default first, then overwrite with
|
|
2021
|
+
// the layout's explicit entries.
|
|
2022
|
+
const sizedIds = new Set(Object.keys(layout.sizing));
|
|
2023
|
+
for (const def of this.columnDefs) {
|
|
2024
|
+
if (sizedIds.has(def.id)) {
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
const original = this._defaultColumnWidths.get(def.id);
|
|
2028
|
+
if (original !== undefined) {
|
|
2029
|
+
def.width = original;
|
|
2030
|
+
}
|
|
2031
|
+
def.userResized = false;
|
|
2032
|
+
}
|
|
1991
2033
|
for (const [id, width] of Object.entries(layout.sizing)) {
|
|
1992
2034
|
const def = byId.get(id);
|
|
1993
2035
|
if (def) {
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { CreateDatatableViewRequest, DatatableView, UpdateDatatableViewRequest } from './DatatableView.js';
|
|
2
|
+
/**
|
|
3
|
+
* One entry in the user-picker roster returned by
|
|
4
|
+
* `RemoteTableLayoutAdapter.listUsers`. The opaque `id` is the same token
|
|
5
|
+
* space as `DatatableView.ownerUserId` / `DatatableViewSubscriber.userId`
|
|
6
|
+
* (so it can be passed straight to `addSubscriber` / `removeSubscriber`);
|
|
7
|
+
* `displayName` is a host-provided human label scoria renders verbatim.
|
|
8
|
+
*/
|
|
9
|
+
export interface DatatableViewUser {
|
|
10
|
+
/** Opaque user identifier (same token space as `ownerUserId`). */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Human-readable label shown in the share modal's user picker. */
|
|
13
|
+
displayName: string;
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
16
|
* Injected backend adapter for saved datatable layouts and filters.
|
|
4
17
|
*
|
|
@@ -44,4 +57,15 @@ export interface RemoteTableLayoutAdapter {
|
|
|
44
57
|
addSubscriber(viewId: string, userId: string): Promise<void>;
|
|
45
58
|
/** Remove a subscriber. */
|
|
46
59
|
removeSubscriber(viewId: string, userId: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Roster of users the current user could share a view with. Used by
|
|
62
|
+
* the Table Configuration modal's Share dialog to populate the user
|
|
63
|
+
* picker. The implementation decides who is "available" (typically
|
|
64
|
+
* everyone the current user can see); scoria treats the result as an
|
|
65
|
+
* opaque list. A backend "not found" / empty list is returned as an
|
|
66
|
+
* empty array - implementations must never return `null`.
|
|
67
|
+
*
|
|
68
|
+
* Added in the FPM 403 round-4 share flow.
|
|
69
|
+
*/
|
|
70
|
+
listUsers(): Promise<ReadonlyArray<DatatableViewUser>>;
|
|
47
71
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export type { HorizontalTabGroupProps } from './Components/HorizontalTabGroupPro
|
|
|
54
54
|
export { default as Icon } from './Components/Icon.svelte';
|
|
55
55
|
export { type IconProps } from './Components/IconProps.js';
|
|
56
56
|
export { icons } from './Components/Icons.js';
|
|
57
|
+
export { default as InfoAlert } from './Components/InfoAlert.svelte';
|
|
58
|
+
export type { InfoAlertProps } from './Components/InfoAlertProps.js';
|
|
57
59
|
export type { InputProps } from './Components/InputProps.js';
|
|
58
60
|
export { default as LoadingAnimation } from './Components/LoadingAnimation.svelte';
|
|
59
61
|
export type { LoadingAnimationProps } from './Components/LoadingAnimationProps.js';
|
|
@@ -113,7 +115,7 @@ export { type JSONTableFilter, TABLE_FILTER_SCHEMA_VERSION, isJSONTableFilter, }
|
|
|
113
115
|
export { type DatatableViewKind } from './Components/Table/Types/Persistence/DatatableViewKind.js';
|
|
114
116
|
export { type DatatableViewBody } from './Components/Table/Types/Persistence/DatatableViewEnvelope.js';
|
|
115
117
|
export type { CreateDatatableViewRequest, DatatableView, DatatableViewSubscriber, DatatableViewSubscription, UpdateDatatableViewRequest, } from './Components/Table/Types/Persistence/DatatableView.js';
|
|
116
|
-
export { type RemoteTableLayoutAdapter } from './Components/Table/Types/Persistence/RemoteTableLayoutAdapter.js';
|
|
118
|
+
export { type RemoteTableLayoutAdapter, type DatatableViewUser, } from './Components/Table/Types/Persistence/RemoteTableLayoutAdapter.js';
|
|
117
119
|
export { type ActiveViewRef, type JSONActiveView, TABLE_ACTIVE_VIEW_SCHEMA_VERSION, isJSONActiveView, } from './Components/Table/Types/Persistence/JSONActiveView.js';
|
|
118
120
|
export { type ValidationFn } from './Components/Table/Types/Columns/Definitions/ValidationFn.js';
|
|
119
121
|
export { type TableSettings } from './Components/Table/Types/Context/TableSettings.js';
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ export { default as HorizontalTabGroup } from './Components/HorizontalTabGroup.s
|
|
|
35
35
|
export { default as Icon } from './Components/Icon.svelte';
|
|
36
36
|
export {} from './Components/IconProps.js';
|
|
37
37
|
export { icons } from './Components/Icons.js';
|
|
38
|
+
export { default as InfoAlert } from './Components/InfoAlert.svelte';
|
|
38
39
|
export { default as LoadingAnimation } from './Components/LoadingAnimation.svelte';
|
|
39
40
|
export { default as LoadingModal } from './Components/LoadingModal.svelte';
|
|
40
41
|
export { default as LoadingOverlay } from './Components/LoadingOverlay.svelte';
|