@happyvertical/smrt-svelte 0.55.0 → 0.55.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -6
- package/agents/components.md +44 -1
- package/agents/workspace.md +77 -2
- package/dist/components/app/AppShell.svelte +122 -24
- package/dist/components/app/AppShell.svelte.d.ts +25 -2
- package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
- package/dist/components/app/DockToggles.svelte +103 -0
- package/dist/components/app/DockToggles.svelte.d.ts +8 -0
- package/dist/components/app/DockToggles.svelte.d.ts.map +1 -0
- package/dist/components/app/dock-toggle.d.ts +14 -0
- package/dist/components/app/dock-toggle.d.ts.map +1 -0
- package/dist/components/app/dock-toggle.js +1 -0
- package/dist/components/app/index.d.ts +2 -0
- package/dist/components/app/index.d.ts.map +1 -1
- package/dist/components/app/index.js +1 -0
- package/dist/components/board/Board.svelte +92 -394
- package/dist/components/board/Board.svelte.d.ts.map +1 -1
- package/dist/components/forms/RelationInput.svelte +379 -0
- package/dist/components/forms/RelationInput.svelte.d.ts +75 -0
- package/dist/components/forms/RelationInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +2 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +1 -0
- package/dist/components/forms/types.d.ts +9 -0
- package/dist/components/forms/types.d.ts.map +1 -1
- package/dist/components/sortable/Sortable.svelte +359 -0
- package/dist/components/sortable/Sortable.svelte.d.ts +26 -0
- package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -0
- package/dist/components/sortable/controller.svelte.d.ts +152 -0
- package/dist/components/sortable/controller.svelte.d.ts.map +1 -0
- package/dist/components/sortable/controller.svelte.js +453 -0
- package/dist/components/sortable/index.d.ts +5 -0
- package/dist/components/sortable/index.d.ts.map +1 -0
- package/dist/components/sortable/index.js +2 -0
- package/dist/components/sortable/types.d.ts +80 -0
- package/dist/components/sortable/types.d.ts.map +1 -0
- package/dist/components/sortable/types.js +1 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +62 -3
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +5 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +2 -1
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +196 -0
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts +17 -0
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +69 -3
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/dock.d.ts +37 -0
- package/dist/components/workspace/admin-shell/dock.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/dock.js +25 -0
- package/dist/components/workspace/admin-shell/layout-context.d.ts +7 -0
- package/dist/components/workspace/admin-shell/layout-context.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout-context.js +17 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts +69 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.js +91 -0
- package/dist/components/workspace/admin-shell/layout.d.ts +120 -0
- package/dist/components/workspace/admin-shell/layout.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout.js +402 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +6 -4
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/settings.js +14 -4
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +59 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +146 -2
- package/dist/components/workspace/admin-shell/types.d.ts +52 -0
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/index.d.ts +6 -1
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +5 -0
- package/dist/i18n/server.d.ts +1 -0
- package/dist/i18n/server.d.ts.map +1 -1
- package/dist/i18n/server.js +1 -0
- package/dist/i18n/strings.sortable.d.ts +13 -0
- package/dist/i18n/strings.sortable.d.ts.map +1 -0
- package/dist/i18n/strings.sortable.js +13 -0
- package/dist/i18n/strings.workspace.d.ts +13 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +13 -0
- package/package.json +26 -15
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from '@happyvertical/smrt-ui';
|
|
3
|
+
import {
|
|
4
|
+
Combobox,
|
|
5
|
+
type ControlInteractionOptions,
|
|
6
|
+
emitControlChange,
|
|
7
|
+
recordControlUserEdit,
|
|
8
|
+
tryGetControlInteractionContext,
|
|
9
|
+
} from '@happyvertical/smrt-ui/forms';
|
|
10
|
+
import { untrack } from 'svelte';
|
|
11
|
+
import { logger } from '../../internal/logger.js';
|
|
12
|
+
import type { RelationOption } from './types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* RelationInput - searchable single-select for relation (foreign key) fields.
|
|
16
|
+
*
|
|
17
|
+
* Presentation only: the caller supplies `search` (and optionally `resolve`),
|
|
18
|
+
* so the same lookup an assistant uses to turn "Acme" into a record id backs
|
|
19
|
+
* the picker. Built on the smrt-ui `Combobox`, which supplies the ARIA 1.2
|
|
20
|
+
* combobox pattern (arrow keys, Enter, Escape, `aria-activedescendant`) and
|
|
21
|
+
* registers with the rich Form's control registry. The form posts the
|
|
22
|
+
* committed id under `name`, never the visible label.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```svelte
|
|
26
|
+
* <RelationInput
|
|
27
|
+
* name="customerId"
|
|
28
|
+
* label="Customer"
|
|
29
|
+
* bind:value={customerId}
|
|
30
|
+
* search={(q) => customers.search(q)}
|
|
31
|
+
* resolve={(id) => customers.byId(id)}
|
|
32
|
+
* />
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export interface Props {
|
|
36
|
+
/** Field name; the selected id posts under it. */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Visible label. */
|
|
39
|
+
label: string;
|
|
40
|
+
/** Selected record id (bindable). Empty string means no selection. */
|
|
41
|
+
value?: string;
|
|
42
|
+
/** Required field: hides the clear button and flags an empty value. */
|
|
43
|
+
required?: boolean;
|
|
44
|
+
/** Disabled state. */
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
/** Validation error message, shown below the field. */
|
|
47
|
+
error?: string;
|
|
48
|
+
/** Placeholder shown when nothing is selected. */
|
|
49
|
+
placeholder?: string;
|
|
50
|
+
/** Hint announced with the field. */
|
|
51
|
+
description?: string;
|
|
52
|
+
/** Look up records matching the text; called with '' when the list opens. */
|
|
53
|
+
search: (query: string) => Promise<RelationOption[]>;
|
|
54
|
+
/** Look up the current value's label when it has not been searched yet. */
|
|
55
|
+
resolve?: (id: string) => Promise<{ id: string; label: string } | null>;
|
|
56
|
+
/**
|
|
57
|
+
* "New ..." action. Receives the text most recently searched for. May
|
|
58
|
+
* return the created record, which is then selected.
|
|
59
|
+
*/
|
|
60
|
+
onCreate?: (
|
|
61
|
+
query: string,
|
|
62
|
+
) => void | RelationOption | Promise<void | RelationOption>;
|
|
63
|
+
/** Milliseconds to wait after typing before searching. */
|
|
64
|
+
debounceMs?: number;
|
|
65
|
+
/** Combobox interaction options (stable id, subject) for agents. */
|
|
66
|
+
interaction?: ControlInteractionOptions | false;
|
|
67
|
+
/** Text for the "New ..." button. */
|
|
68
|
+
createLabel?: string;
|
|
69
|
+
/** Accessible name for the clear button. */
|
|
70
|
+
clearLabel?: string;
|
|
71
|
+
/** Status text while searching. */
|
|
72
|
+
loadingText?: string;
|
|
73
|
+
/** Status text when nothing matches. */
|
|
74
|
+
emptyText?: string;
|
|
75
|
+
/** Status text when the search fails. */
|
|
76
|
+
errorText?: string;
|
|
77
|
+
/** Status text announcing how many results are listed. */
|
|
78
|
+
resultsText?: (count: number) => string;
|
|
79
|
+
/** Called when the selected id changes ('' when cleared). */
|
|
80
|
+
onchange?: (value: string) => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let {
|
|
84
|
+
name,
|
|
85
|
+
label,
|
|
86
|
+
value = $bindable(''),
|
|
87
|
+
required = false,
|
|
88
|
+
disabled = false,
|
|
89
|
+
error,
|
|
90
|
+
placeholder,
|
|
91
|
+
description,
|
|
92
|
+
search,
|
|
93
|
+
resolve,
|
|
94
|
+
onCreate,
|
|
95
|
+
debounceMs = 250,
|
|
96
|
+
interaction,
|
|
97
|
+
createLabel,
|
|
98
|
+
clearLabel,
|
|
99
|
+
loadingText = 'Searching…',
|
|
100
|
+
emptyText = 'No matches',
|
|
101
|
+
errorText = 'Search failed. Try again.',
|
|
102
|
+
resultsText = (count: number) =>
|
|
103
|
+
`${count} ${count === 1 ? 'result' : 'results'} available`,
|
|
104
|
+
onchange,
|
|
105
|
+
}: Props = $props();
|
|
106
|
+
|
|
107
|
+
const instanceId = $props.id();
|
|
108
|
+
const errorId = `${instanceId}-error`;
|
|
109
|
+
const hintId = `${instanceId}-hint`;
|
|
110
|
+
const interactionContext = tryGetControlInteractionContext();
|
|
111
|
+
|
|
112
|
+
let rootEl = $state<HTMLDivElement | null>(null);
|
|
113
|
+
let results = $state<RelationOption[]>([]);
|
|
114
|
+
let loading = $state(false);
|
|
115
|
+
let failed = $state(false);
|
|
116
|
+
let selectedLabel = $state('');
|
|
117
|
+
let creating = $state(false);
|
|
118
|
+
|
|
119
|
+
// Plain bookkeeping, deliberately not reactive.
|
|
120
|
+
let labelledId = '';
|
|
121
|
+
let resolvingId = '';
|
|
122
|
+
let searchSeq = 0;
|
|
123
|
+
let lastQuery = '';
|
|
124
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
125
|
+
|
|
126
|
+
const options = $derived(
|
|
127
|
+
results.map((item) => ({ value: item.id, label: item.label })),
|
|
128
|
+
);
|
|
129
|
+
const detailById = $derived(
|
|
130
|
+
new Map(results.map((item) => [item.id, item.detail])),
|
|
131
|
+
);
|
|
132
|
+
const status = $derived(
|
|
133
|
+
loading
|
|
134
|
+
? loadingText
|
|
135
|
+
: failed
|
|
136
|
+
? errorText
|
|
137
|
+
: results.length === 0
|
|
138
|
+
? emptyText
|
|
139
|
+
: resultsText(results.length),
|
|
140
|
+
);
|
|
141
|
+
const describedby = $derived(
|
|
142
|
+
[error ? errorId : '', description ? hintId : ''].filter(Boolean).join(' ') ||
|
|
143
|
+
undefined,
|
|
144
|
+
);
|
|
145
|
+
const controlId = $derived(
|
|
146
|
+
interaction === false ? undefined : (interaction?.id ?? name),
|
|
147
|
+
);
|
|
148
|
+
const canClear = $derived(!required && !disabled && value !== '');
|
|
149
|
+
const newLabel = $derived(createLabel ?? `New ${label.toLowerCase()}`);
|
|
150
|
+
|
|
151
|
+
async function runSearch(query: string) {
|
|
152
|
+
const seq = ++searchSeq;
|
|
153
|
+
loading = true;
|
|
154
|
+
failed = false;
|
|
155
|
+
try {
|
|
156
|
+
const found = await search(query);
|
|
157
|
+
if (seq !== searchSeq) return;
|
|
158
|
+
results = found;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (seq !== searchSeq) return;
|
|
161
|
+
results = [];
|
|
162
|
+
failed = true;
|
|
163
|
+
logger.warn('[smrt-svelte] RelationInput search failed', {
|
|
164
|
+
error: String(err),
|
|
165
|
+
});
|
|
166
|
+
} finally {
|
|
167
|
+
if (seq === searchSeq) loading = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function handleQuery(query: string) {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
lastQuery = query;
|
|
174
|
+
if (query === '') {
|
|
175
|
+
// Opening the list: no typing to wait for.
|
|
176
|
+
void runSearch('');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Show "Searching" straight away and drop any in-flight older request. The
|
|
180
|
+
// listed options answered an older query, so none stay selectable (Enter or
|
|
181
|
+
// a click would otherwise pick a record that no longer matches the text).
|
|
182
|
+
searchSeq++;
|
|
183
|
+
loading = true;
|
|
184
|
+
failed = false;
|
|
185
|
+
results = [];
|
|
186
|
+
timer = setTimeout(() => void runSearch(query), debounceMs);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function handleValueChange(next: string) {
|
|
190
|
+
lastQuery = '';
|
|
191
|
+
const hit = results.find((item) => item.id === next);
|
|
192
|
+
if (hit) {
|
|
193
|
+
selectedLabel = hit.label;
|
|
194
|
+
labelledId = next;
|
|
195
|
+
}
|
|
196
|
+
onchange?.(next);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function focusInput() {
|
|
200
|
+
rootEl?.querySelector<HTMLInputElement>('input[role="combobox"]')?.focus();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function clear() {
|
|
204
|
+
if (!canClear) return;
|
|
205
|
+
value = '';
|
|
206
|
+
selectedLabel = '';
|
|
207
|
+
labelledId = '';
|
|
208
|
+
lastQuery = '';
|
|
209
|
+
onchange?.('');
|
|
210
|
+
recordControlUserEdit(
|
|
211
|
+
interactionContext,
|
|
212
|
+
controlId,
|
|
213
|
+
interaction === false ? undefined : interaction?.subject,
|
|
214
|
+
);
|
|
215
|
+
if (rootEl) emitControlChange(rootEl);
|
|
216
|
+
focusInput();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function create() {
|
|
220
|
+
if (!onCreate || creating || disabled) return;
|
|
221
|
+
// The field has already lost focus (and reset its text) by now, so use the
|
|
222
|
+
// text most recently searched for.
|
|
223
|
+
const typed = lastQuery;
|
|
224
|
+
creating = true;
|
|
225
|
+
try {
|
|
226
|
+
const made = await onCreate(typed);
|
|
227
|
+
if (made && typeof made === 'object') {
|
|
228
|
+
results = [made, ...results.filter((item) => item.id !== made.id)];
|
|
229
|
+
selectedLabel = made.label;
|
|
230
|
+
labelledId = made.id;
|
|
231
|
+
value = made.id;
|
|
232
|
+
onchange?.(made.id);
|
|
233
|
+
recordControlUserEdit(
|
|
234
|
+
interactionContext,
|
|
235
|
+
controlId,
|
|
236
|
+
interaction === false ? undefined : interaction?.subject,
|
|
237
|
+
);
|
|
238
|
+
if (rootEl) emitControlChange(rootEl);
|
|
239
|
+
}
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logger.warn('[smrt-svelte] RelationInput onCreate failed', {
|
|
242
|
+
error: String(err),
|
|
243
|
+
});
|
|
244
|
+
} finally {
|
|
245
|
+
creating = false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Keep the visible label in step with `value` however it changed (initial
|
|
250
|
+
// value, parent rebind, form reset): known from a search, else resolved.
|
|
251
|
+
$effect(() => {
|
|
252
|
+
const id = value;
|
|
253
|
+
const known = results.find((item) => item.id === id);
|
|
254
|
+
untrack(() => {
|
|
255
|
+
if (!id) {
|
|
256
|
+
selectedLabel = '';
|
|
257
|
+
labelledId = '';
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (id === labelledId) return;
|
|
261
|
+
if (known) {
|
|
262
|
+
selectedLabel = known.label;
|
|
263
|
+
labelledId = id;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// A different id than the one labelled: never keep the previous label.
|
|
267
|
+
selectedLabel = '';
|
|
268
|
+
labelledId = '';
|
|
269
|
+
if (!resolve || resolvingId === id) return;
|
|
270
|
+
resolvingId = id;
|
|
271
|
+
resolve(id)
|
|
272
|
+
.then((record) => {
|
|
273
|
+
if (value === id && record) {
|
|
274
|
+
selectedLabel = record.label;
|
|
275
|
+
labelledId = id;
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
.catch((err) => {
|
|
279
|
+
logger.warn('[smrt-svelte] RelationInput resolve failed', {
|
|
280
|
+
error: String(err),
|
|
281
|
+
});
|
|
282
|
+
})
|
|
283
|
+
.finally(() => {
|
|
284
|
+
if (resolvingId === id) resolvingId = '';
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
$effect(() => () => clearTimeout(timer));
|
|
290
|
+
</script>
|
|
291
|
+
|
|
292
|
+
<div class="relation-input" bind:this={rootEl}>
|
|
293
|
+
<div class="field">
|
|
294
|
+
<Combobox
|
|
295
|
+
{name}
|
|
296
|
+
{label}
|
|
297
|
+
{options}
|
|
298
|
+
{placeholder}
|
|
299
|
+
{disabled}
|
|
300
|
+
{required}
|
|
301
|
+
{interaction}
|
|
302
|
+
{status}
|
|
303
|
+
{describedby}
|
|
304
|
+
bind:value
|
|
305
|
+
valueLabel={selectedLabel}
|
|
306
|
+
filter={false}
|
|
307
|
+
busy={loading}
|
|
308
|
+
invalid={!!error}
|
|
309
|
+
onquery={handleQuery}
|
|
310
|
+
onvaluechange={handleValueChange}
|
|
311
|
+
>
|
|
312
|
+
{#snippet optionContent(option)}
|
|
313
|
+
<span class="option-label">{option.label}</span>
|
|
314
|
+
{#if detailById.get(String(option.value))}
|
|
315
|
+
<span class="option-detail">{detailById.get(String(option.value))}</span>
|
|
316
|
+
{/if}
|
|
317
|
+
{/snippet}
|
|
318
|
+
</Combobox>
|
|
319
|
+
{#if canClear}
|
|
320
|
+
<Button
|
|
321
|
+
variant="ghost"
|
|
322
|
+
size="sm"
|
|
323
|
+
class="clear"
|
|
324
|
+
aria-label={clearLabel ?? `Clear ${label}`}
|
|
325
|
+
onclick={clear}
|
|
326
|
+
>
|
|
327
|
+
×
|
|
328
|
+
</Button>
|
|
329
|
+
{/if}
|
|
330
|
+
</div>
|
|
331
|
+
{#if description}<p id={hintId} class="hint">{description}</p>{/if}
|
|
332
|
+
{#if onCreate}
|
|
333
|
+
<Button
|
|
334
|
+
variant="ghost"
|
|
335
|
+
size="sm"
|
|
336
|
+
class="create"
|
|
337
|
+
disabled={disabled || creating}
|
|
338
|
+
onclick={create}
|
|
339
|
+
>
|
|
340
|
+
+ {newLabel}
|
|
341
|
+
</Button>
|
|
342
|
+
{/if}
|
|
343
|
+
{#if error}<p id={errorId} class="error" role="alert">{error}</p>{/if}
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
<style>
|
|
347
|
+
.relation-input {
|
|
348
|
+
display: grid;
|
|
349
|
+
gap: var(--smrt-spacing-1, 4px);
|
|
350
|
+
min-width: 0;
|
|
351
|
+
}
|
|
352
|
+
.field {
|
|
353
|
+
position: relative;
|
|
354
|
+
}
|
|
355
|
+
.field :global(.clear) {
|
|
356
|
+
position: absolute;
|
|
357
|
+
right: var(--smrt-spacing-1, 4px);
|
|
358
|
+
bottom: var(--smrt-spacing-1, 4px);
|
|
359
|
+
}
|
|
360
|
+
.option-label,
|
|
361
|
+
.option-detail {
|
|
362
|
+
display: block;
|
|
363
|
+
}
|
|
364
|
+
.option-detail {
|
|
365
|
+
font-size: var(--smrt-typography-body-small-size, 0.75rem);
|
|
366
|
+
color: var(--smrt-color-on-surface-variant);
|
|
367
|
+
}
|
|
368
|
+
.hint,
|
|
369
|
+
.error {
|
|
370
|
+
margin: 0;
|
|
371
|
+
font-size: var(--smrt-typography-body-small-size, 0.75rem);
|
|
372
|
+
}
|
|
373
|
+
.hint {
|
|
374
|
+
color: var(--smrt-color-on-surface-variant);
|
|
375
|
+
}
|
|
376
|
+
.error {
|
|
377
|
+
color: var(--smrt-color-error);
|
|
378
|
+
}
|
|
379
|
+
</style>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type ControlInteractionOptions } from '@happyvertical/smrt-ui/forms';
|
|
2
|
+
import type { RelationOption } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* RelationInput - searchable single-select for relation (foreign key) fields.
|
|
5
|
+
*
|
|
6
|
+
* Presentation only: the caller supplies `search` (and optionally `resolve`),
|
|
7
|
+
* so the same lookup an assistant uses to turn "Acme" into a record id backs
|
|
8
|
+
* the picker. Built on the smrt-ui `Combobox`, which supplies the ARIA 1.2
|
|
9
|
+
* combobox pattern (arrow keys, Enter, Escape, `aria-activedescendant`) and
|
|
10
|
+
* registers with the rich Form's control registry. The form posts the
|
|
11
|
+
* committed id under `name`, never the visible label.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```svelte
|
|
15
|
+
* <RelationInput
|
|
16
|
+
* name="customerId"
|
|
17
|
+
* label="Customer"
|
|
18
|
+
* bind:value={customerId}
|
|
19
|
+
* search={(q) => customers.search(q)}
|
|
20
|
+
* resolve={(id) => customers.byId(id)}
|
|
21
|
+
* />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface Props {
|
|
25
|
+
/** Field name; the selected id posts under it. */
|
|
26
|
+
name: string;
|
|
27
|
+
/** Visible label. */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Selected record id (bindable). Empty string means no selection. */
|
|
30
|
+
value?: string;
|
|
31
|
+
/** Required field: hides the clear button and flags an empty value. */
|
|
32
|
+
required?: boolean;
|
|
33
|
+
/** Disabled state. */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/** Validation error message, shown below the field. */
|
|
36
|
+
error?: string;
|
|
37
|
+
/** Placeholder shown when nothing is selected. */
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
/** Hint announced with the field. */
|
|
40
|
+
description?: string;
|
|
41
|
+
/** Look up records matching the text; called with '' when the list opens. */
|
|
42
|
+
search: (query: string) => Promise<RelationOption[]>;
|
|
43
|
+
/** Look up the current value's label when it has not been searched yet. */
|
|
44
|
+
resolve?: (id: string) => Promise<{
|
|
45
|
+
id: string;
|
|
46
|
+
label: string;
|
|
47
|
+
} | null>;
|
|
48
|
+
/**
|
|
49
|
+
* "New ..." action. Receives the text most recently searched for. May
|
|
50
|
+
* return the created record, which is then selected.
|
|
51
|
+
*/
|
|
52
|
+
onCreate?: (query: string) => void | RelationOption | Promise<void | RelationOption>;
|
|
53
|
+
/** Milliseconds to wait after typing before searching. */
|
|
54
|
+
debounceMs?: number;
|
|
55
|
+
/** Combobox interaction options (stable id, subject) for agents. */
|
|
56
|
+
interaction?: ControlInteractionOptions | false;
|
|
57
|
+
/** Text for the "New ..." button. */
|
|
58
|
+
createLabel?: string;
|
|
59
|
+
/** Accessible name for the clear button. */
|
|
60
|
+
clearLabel?: string;
|
|
61
|
+
/** Status text while searching. */
|
|
62
|
+
loadingText?: string;
|
|
63
|
+
/** Status text when nothing matches. */
|
|
64
|
+
emptyText?: string;
|
|
65
|
+
/** Status text when the search fails. */
|
|
66
|
+
errorText?: string;
|
|
67
|
+
/** Status text announcing how many results are listed. */
|
|
68
|
+
resultsText?: (count: number) => string;
|
|
69
|
+
/** Called when the selected id changes ('' when cleared). */
|
|
70
|
+
onchange?: (value: string) => void;
|
|
71
|
+
}
|
|
72
|
+
declare const RelationInput: import("svelte").Component<Props, {}, "value">;
|
|
73
|
+
type RelationInput = ReturnType<typeof RelationInput>;
|
|
74
|
+
export default RelationInput;
|
|
75
|
+
//# sourceMappingURL=RelationInput.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RelationInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/RelationInput.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,yBAAyB,EAI/B,MAAM,8BAA8B,CAAC;AAGtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,KAAK;IACpB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACrD,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACxE;;;OAGG;IACH,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,KACV,IAAI,GAAG,cAAc,GAAG,OAAO,CAAC,IAAI,GAAG,cAAc,CAAC,CAAC;IAC5D,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAuPD,QAAA,MAAM,aAAa,gDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -21,9 +21,10 @@ export { default as MeasurementInput } from './MeasurementInput.svelte';
|
|
|
21
21
|
export { default as MoneyInput } from './MoneyInput.svelte';
|
|
22
22
|
export { default as NumberInput } from './NumberInput.svelte';
|
|
23
23
|
export { default as PhoneInput } from './PhoneInput.svelte';
|
|
24
|
+
export { default as RelationInput } from './RelationInput.svelte';
|
|
24
25
|
export { default as SearchInput } from './SearchInput.svelte';
|
|
25
26
|
export { default as SelectInput } from './SelectInput.svelte';
|
|
26
27
|
export { default as TextareaInput } from './TextareaInput.svelte';
|
|
27
28
|
export { default as TextInput } from './TextInput.svelte';
|
|
28
|
-
export type { AddressValue, DateRangeValue, LLMModelId, MeasurementUnit, MeasurementValue, SelectOption, STTAdapterType, } from './types.js';
|
|
29
|
+
export type { AddressValue, DateRangeValue, LLMModelId, MeasurementUnit, MeasurementValue, RelationOption, SelectOption, STTAdapterType, } from './types.js';
|
|
29
30
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/forms/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,SAAS,EACT,KAAK,EACL,MAAM,EACN,QAAQ,EACR,MAAM,GACP,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG1D,YAAY,EACV,YAAY,EACZ,cAAc,EACd,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/forms/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,SAAS,EACT,KAAK,EACL,MAAM,EACN,QAAQ,EACR,MAAM,GACP,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG1D,YAAY,EACV,YAAY,EACZ,cAAc,EACd,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC"}
|
|
@@ -23,6 +23,7 @@ export { default as MeasurementInput } from './MeasurementInput.svelte';
|
|
|
23
23
|
export { default as MoneyInput } from './MoneyInput.svelte';
|
|
24
24
|
export { default as NumberInput } from './NumberInput.svelte';
|
|
25
25
|
export { default as PhoneInput } from './PhoneInput.svelte';
|
|
26
|
+
export { default as RelationInput } from './RelationInput.svelte';
|
|
26
27
|
export { default as SearchInput } from './SearchInput.svelte';
|
|
27
28
|
export { default as SelectInput } from './SelectInput.svelte';
|
|
28
29
|
export { default as TextareaInput } from './TextareaInput.svelte';
|
|
@@ -10,6 +10,15 @@ export interface SelectOption {
|
|
|
10
10
|
value: string;
|
|
11
11
|
label: string;
|
|
12
12
|
}
|
|
13
|
+
/** A searchable relation target for the RelationInput component */
|
|
14
|
+
export interface RelationOption {
|
|
15
|
+
/** Record id; this is the value the form posts. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Human-readable name shown in the field and the list. */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Secondary text shown under the label in the list. */
|
|
20
|
+
detail?: string;
|
|
21
|
+
}
|
|
13
22
|
/** Address value for SMRTAddress component */
|
|
14
23
|
export interface AddressValue {
|
|
15
24
|
street: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kCAAkC;AAClC,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,cAAc,GAAG,aAAa,CAAC;AAE/E,wCAAwC;AACxC,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,cAAc,GACd,cAAc,GACd,cAAc,GACd,cAAc,CAAC;AAEnB,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,6BAA6B;AAC7B,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAErE,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,eAAe,CAAC;CACvB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kCAAkC;AAClC,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,cAAc,GAAG,aAAa,CAAC;AAE/E,wCAAwC;AACxC,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,cAAc,GACd,cAAc,GACd,cAAc,GACd,cAAc,CAAC;AAEnB,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,6BAA6B;AAC7B,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAErE,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,eAAe,CAAC;CACvB"}
|