@happyvertical/smrt-ui 0.55.3 → 0.55.4
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/AGENTS.md +1 -1
- package/README.md +45 -1
- package/dist/components/forms/RelationInput.svelte +378 -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/svelte/playground/InteractiveControlsPreview.svelte +22 -0
- package/dist/svelte/playground/InteractiveControlsPreview.svelte.d.ts.map +1 -1
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -17,7 +17,7 @@ SMRT has one shared set of UI primitives, split across two packages by concern:
|
|
|
17
17
|
`Tooltip`, `Skeleton`, `Tree`, `Pagination`, `DataTable`, `ContentList`, … — **plus the
|
|
18
18
|
Provider-free base FORM primitives** under `./forms` (`Form`, `Input`,
|
|
19
19
|
`Select`, `Textarea`, `Checkbox`, `Switch`, `RadioGroup`, `Slider`,
|
|
20
|
-
`RangeSlider`, `Combobox`, `MultiSelect`, `TagsInput`, `FilePicker`,
|
|
20
|
+
`RangeSlider`, `Combobox`, `RelationInput`, `MultiSelect`, `TagsInput`, `FilePicker`,
|
|
21
21
|
`FormGroup`), relocated here in #1589's
|
|
22
22
|
deferred-forms phase so domain packages can adopt them without pulling in the
|
|
23
23
|
smrt-svelte Provider or closing a build-graph cycle. These are dependency-free:
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ pnpm add @happyvertical/smrt-ui
|
|
|
32
32
|
| Area | Components |
|
|
33
33
|
| --- | --- |
|
|
34
34
|
| Fields | `Form`, `Field`/`FormGroup`, `Fieldset`, `InputGroup`, `ErrorSummary`, `FormActionBar` |
|
|
35
|
-
| Text and structured input | `Input`, `Textarea`, `Select`, `Combobox`, `Listbox`, `MultiSelect`, `TagsInput`, `SearchInput` |
|
|
35
|
+
| Text and structured input | `Input`, `Textarea`, `Select`, `Combobox`, `RelationInput`, `Listbox`, `MultiSelect`, `TagsInput`, `SearchInput` |
|
|
36
36
|
| Choices | `Checkbox`, `RadioGroup`/`Radio`, `Switch`, `Toggle`, `ToggleButton`, `SegmentedControl` |
|
|
37
37
|
| Values and files | `Slider`, `RangeSlider`, `DatePicker`, `TimePicker`, `FilePicker` |
|
|
38
38
|
| Capture | `CameraCapture`, `SignaturePad` |
|
|
@@ -130,6 +130,50 @@ controls with no name or an empty name are omitted. Disabled controls, including
|
|
|
130
130
|
those inside disabled fieldsets, are omitted by native `FormData`. Native form
|
|
131
131
|
reset restores the initial selection and its label, unless reset is canceled.
|
|
132
132
|
|
|
133
|
+
### RelationInput
|
|
134
|
+
|
|
135
|
+
`RelationInput` (`/forms`) moved here from `@happyvertical/smrt-svelte/forms` in #3637 (originally #3600); smrt-svelte keeps a deprecated re-export. Labels are props (`createLabel`, `clearLabel`, `loadingText`, ...); there is no message catalog.
|
|
136
|
+
|
|
137
|
+
Searchable single-select for relation (foreign-key) fields, a thin wrapper over
|
|
138
|
+
`Combobox`. It is presentation only: the caller supplies the data,
|
|
139
|
+
so the same lookup an assistant uses to turn "Acme" into a record id backs the
|
|
140
|
+
picker. Option shape is `RelationOption` — `{ id, label, detail? }`.
|
|
141
|
+
|
|
142
|
+
- **Props.** `name`, `label`, `value` (bindable id, `''` = none), `required`,
|
|
143
|
+
`disabled`, `error`, `placeholder`, `description`, `search(query)`, optional
|
|
144
|
+
`resolve(id)` and `onCreate(query)`, plus `debounceMs` (250), `interaction`,
|
|
145
|
+
`onchange` and overridable text (`createLabel`, `clearLabel`, `loadingText`,
|
|
146
|
+
`emptyText`, `errorText`, `resultsText`).
|
|
147
|
+
- **`search`** runs with `''` when the list opens and then debounced per
|
|
148
|
+
keystroke; a response for a superseded query is dropped. A rejected search
|
|
149
|
+
shows `errorText` and the next keystroke retries.
|
|
150
|
+
- **`resolve`** supplies the label of a `value` that has not been searched yet
|
|
151
|
+
(initial value, parent rebind, form reset). Without it such a value shows an
|
|
152
|
+
empty field, never the raw id. A `null` result also leaves the field empty.
|
|
153
|
+
- **`onCreate`** adds a "New ..." button; it receives the text most recently
|
|
154
|
+
searched for and may return the created `RelationOption`, which is selected.
|
|
155
|
+
- **Form integration** is through `Combobox`'s control registration (`Form`
|
|
156
|
+
provides the registry), not a `FieldDefinition`: agents see a `combobox`
|
|
157
|
+
control whose value is the id. The form posts the id under `name`.
|
|
158
|
+
- **Known limit.** An agent staging a value through the registered control can
|
|
159
|
+
only choose among the options last listed (the registry matches against
|
|
160
|
+
`options`; `allowCustom` is off), so it cannot write an id it has not
|
|
161
|
+
searched. It fails closed. Writing a resolved id needs an async hook on
|
|
162
|
+
`Combobox` and is tracked separately.
|
|
163
|
+
- **Stale options.** Typing clears the listed options until the new search
|
|
164
|
+
answers, so Enter or a click can never pick a record for an older query.
|
|
165
|
+
- **Clear button** shows only when not `required`, not `disabled`, and a value
|
|
166
|
+
is set; it returns focus to the field.
|
|
167
|
+
- **Accessibility** is the combobox pattern (`role=combobox`, `aria-expanded`,
|
|
168
|
+
`aria-activedescendant`, listbox/option, Arrow/Enter/Escape) plus a polite
|
|
169
|
+
`role=status` live region announcing searching / "N results" / no matches,
|
|
170
|
+
`aria-busy` while fetching, and `aria-invalid` + `aria-describedby` for
|
|
171
|
+
`error` and `description`.
|
|
172
|
+
- Options render `detail` under the label, so the option's accessible name
|
|
173
|
+
includes it. The `Combobox` additions that make this possible (`filter`,
|
|
174
|
+
`onquery`, `status`, `busy`, `invalid`, `describedby`, `optionContent`) are
|
|
175
|
+
generic and default to the previous behaviour.
|
|
176
|
+
|
|
133
177
|
### MultiSelect form submission
|
|
134
178
|
|
|
135
179
|
A named `MultiSelect` submits one hidden native input per selected option value,
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import Button from '../ui/Button.svelte';
|
|
4
|
+
import Combobox from './Combobox.svelte';
|
|
5
|
+
import { emitControlChange } from './control-dom.js';
|
|
6
|
+
import type { ControlInteractionOptions } from './control-interaction.js';
|
|
7
|
+
import {
|
|
8
|
+
recordControlUserEdit,
|
|
9
|
+
tryGetControlInteractionContext,
|
|
10
|
+
} from './control-interaction-context.js';
|
|
11
|
+
import type { RelationOption } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* RelationInput - searchable single-select for relation (foreign key) fields.
|
|
15
|
+
*
|
|
16
|
+
* Presentation only: the caller supplies `search` (and optionally `resolve`),
|
|
17
|
+
* so the same lookup an assistant uses to turn "Acme" into a record id backs
|
|
18
|
+
* the picker. Built on the smrt-ui `Combobox`, which supplies the ARIA 1.2
|
|
19
|
+
* combobox pattern (arrow keys, Enter, Escape, `aria-activedescendant`) and
|
|
20
|
+
* registers with the rich Form's control registry. The form posts the
|
|
21
|
+
* committed id under `name`, never the visible label.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```svelte
|
|
25
|
+
* <RelationInput
|
|
26
|
+
* name="customerId"
|
|
27
|
+
* label="Customer"
|
|
28
|
+
* bind:value={customerId}
|
|
29
|
+
* search={(q) => customers.search(q)}
|
|
30
|
+
* resolve={(id) => customers.byId(id)}
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export interface Props {
|
|
35
|
+
/** Field name; the selected id posts under it. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Visible label. */
|
|
38
|
+
label: string;
|
|
39
|
+
/** Selected record id (bindable). Empty string means no selection. */
|
|
40
|
+
value?: string;
|
|
41
|
+
/** Required field: hides the clear button and flags an empty value. */
|
|
42
|
+
required?: boolean;
|
|
43
|
+
/** Disabled state. */
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
/** Validation error message, shown below the field. */
|
|
46
|
+
error?: string;
|
|
47
|
+
/** Placeholder shown when nothing is selected. */
|
|
48
|
+
placeholder?: string;
|
|
49
|
+
/** Hint announced with the field. */
|
|
50
|
+
description?: string;
|
|
51
|
+
/** Look up records matching the text; called with '' when the list opens. */
|
|
52
|
+
search: (query: string) => Promise<RelationOption[]>;
|
|
53
|
+
/** Look up the current value's label when it has not been searched yet. */
|
|
54
|
+
resolve?: (id: string) => Promise<{ id: string; label: string } | null>;
|
|
55
|
+
/**
|
|
56
|
+
* "New ..." action. Receives the text most recently searched for. May
|
|
57
|
+
* return the created record, which is then selected.
|
|
58
|
+
*/
|
|
59
|
+
onCreate?: (
|
|
60
|
+
query: string,
|
|
61
|
+
) => void | RelationOption | Promise<void | RelationOption>;
|
|
62
|
+
/** Milliseconds to wait after typing before searching. */
|
|
63
|
+
debounceMs?: number;
|
|
64
|
+
/** Combobox interaction options (stable id, subject) for agents. */
|
|
65
|
+
interaction?: ControlInteractionOptions | false;
|
|
66
|
+
/** Text for the "New ..." button. */
|
|
67
|
+
createLabel?: string;
|
|
68
|
+
/** Accessible name for the clear button. */
|
|
69
|
+
clearLabel?: string;
|
|
70
|
+
/** Status text while searching. */
|
|
71
|
+
loadingText?: string;
|
|
72
|
+
/** Status text when nothing matches. */
|
|
73
|
+
emptyText?: string;
|
|
74
|
+
/** Status text when the search fails. */
|
|
75
|
+
errorText?: string;
|
|
76
|
+
/** Status text announcing how many results are listed. */
|
|
77
|
+
resultsText?: (count: number) => string;
|
|
78
|
+
/** Called when the selected id changes ('' when cleared). */
|
|
79
|
+
onchange?: (value: string) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let {
|
|
83
|
+
name,
|
|
84
|
+
label,
|
|
85
|
+
value = $bindable(''),
|
|
86
|
+
required = false,
|
|
87
|
+
disabled = false,
|
|
88
|
+
error,
|
|
89
|
+
placeholder,
|
|
90
|
+
description,
|
|
91
|
+
search,
|
|
92
|
+
resolve,
|
|
93
|
+
onCreate,
|
|
94
|
+
debounceMs = 250,
|
|
95
|
+
interaction,
|
|
96
|
+
createLabel,
|
|
97
|
+
clearLabel,
|
|
98
|
+
loadingText = 'Searching…',
|
|
99
|
+
emptyText = 'No matches',
|
|
100
|
+
errorText = 'Search failed. Try again.',
|
|
101
|
+
resultsText = (count: number) =>
|
|
102
|
+
`${count} ${count === 1 ? 'result' : 'results'} available`,
|
|
103
|
+
onchange,
|
|
104
|
+
}: Props = $props();
|
|
105
|
+
|
|
106
|
+
const instanceId = $props.id();
|
|
107
|
+
const errorId = `${instanceId}-error`;
|
|
108
|
+
const hintId = `${instanceId}-hint`;
|
|
109
|
+
const interactionContext = tryGetControlInteractionContext();
|
|
110
|
+
|
|
111
|
+
let rootEl = $state<HTMLDivElement | null>(null);
|
|
112
|
+
let results = $state<RelationOption[]>([]);
|
|
113
|
+
let loading = $state(false);
|
|
114
|
+
let failed = $state(false);
|
|
115
|
+
let selectedLabel = $state('');
|
|
116
|
+
let creating = $state(false);
|
|
117
|
+
|
|
118
|
+
// Plain bookkeeping, deliberately not reactive.
|
|
119
|
+
let labelledId = '';
|
|
120
|
+
let resolvingId = '';
|
|
121
|
+
let searchSeq = 0;
|
|
122
|
+
let lastQuery = '';
|
|
123
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
124
|
+
|
|
125
|
+
const options = $derived(
|
|
126
|
+
results.map((item) => ({ value: item.id, label: item.label })),
|
|
127
|
+
);
|
|
128
|
+
const detailById = $derived(
|
|
129
|
+
new Map(results.map((item) => [item.id, item.detail])),
|
|
130
|
+
);
|
|
131
|
+
const status = $derived(
|
|
132
|
+
loading
|
|
133
|
+
? loadingText
|
|
134
|
+
: failed
|
|
135
|
+
? errorText
|
|
136
|
+
: results.length === 0
|
|
137
|
+
? emptyText
|
|
138
|
+
: resultsText(results.length),
|
|
139
|
+
);
|
|
140
|
+
const describedby = $derived(
|
|
141
|
+
[error ? errorId : '', description ? hintId : ''].filter(Boolean).join(' ') ||
|
|
142
|
+
undefined,
|
|
143
|
+
);
|
|
144
|
+
const controlId = $derived(
|
|
145
|
+
interaction === false ? undefined : (interaction?.id ?? name),
|
|
146
|
+
);
|
|
147
|
+
const canClear = $derived(!required && !disabled && value !== '');
|
|
148
|
+
const newLabel = $derived(createLabel ?? `New ${label.toLowerCase()}`);
|
|
149
|
+
|
|
150
|
+
async function runSearch(query: string) {
|
|
151
|
+
const seq = ++searchSeq;
|
|
152
|
+
loading = true;
|
|
153
|
+
failed = false;
|
|
154
|
+
try {
|
|
155
|
+
const found = await search(query);
|
|
156
|
+
if (seq !== searchSeq) return;
|
|
157
|
+
results = found;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (seq !== searchSeq) return;
|
|
160
|
+
results = [];
|
|
161
|
+
failed = true;
|
|
162
|
+
console.warn('[smrt-ui] RelationInput search failed', {
|
|
163
|
+
error: String(err),
|
|
164
|
+
});
|
|
165
|
+
} finally {
|
|
166
|
+
if (seq === searchSeq) loading = false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function handleQuery(query: string) {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
lastQuery = query;
|
|
173
|
+
if (query === '') {
|
|
174
|
+
// Opening the list: no typing to wait for.
|
|
175
|
+
void runSearch('');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Show "Searching" straight away and drop any in-flight older request. The
|
|
179
|
+
// listed options answered an older query, so none stay selectable (Enter or
|
|
180
|
+
// a click would otherwise pick a record that no longer matches the text).
|
|
181
|
+
searchSeq++;
|
|
182
|
+
loading = true;
|
|
183
|
+
failed = false;
|
|
184
|
+
results = [];
|
|
185
|
+
timer = setTimeout(() => void runSearch(query), debounceMs);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function handleValueChange(next: string) {
|
|
189
|
+
lastQuery = '';
|
|
190
|
+
const hit = results.find((item) => item.id === next);
|
|
191
|
+
if (hit) {
|
|
192
|
+
selectedLabel = hit.label;
|
|
193
|
+
labelledId = next;
|
|
194
|
+
}
|
|
195
|
+
onchange?.(next);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function focusInput() {
|
|
199
|
+
rootEl?.querySelector<HTMLInputElement>('input[role="combobox"]')?.focus();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function clear() {
|
|
203
|
+
if (!canClear) return;
|
|
204
|
+
value = '';
|
|
205
|
+
selectedLabel = '';
|
|
206
|
+
labelledId = '';
|
|
207
|
+
lastQuery = '';
|
|
208
|
+
onchange?.('');
|
|
209
|
+
recordControlUserEdit(
|
|
210
|
+
interactionContext,
|
|
211
|
+
controlId,
|
|
212
|
+
interaction === false ? undefined : interaction?.subject,
|
|
213
|
+
);
|
|
214
|
+
if (rootEl) emitControlChange(rootEl);
|
|
215
|
+
focusInput();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function create() {
|
|
219
|
+
if (!onCreate || creating || disabled) return;
|
|
220
|
+
// The field has already lost focus (and reset its text) by now, so use the
|
|
221
|
+
// text most recently searched for.
|
|
222
|
+
const typed = lastQuery;
|
|
223
|
+
creating = true;
|
|
224
|
+
try {
|
|
225
|
+
const made = await onCreate(typed);
|
|
226
|
+
if (made && typeof made === 'object') {
|
|
227
|
+
results = [made, ...results.filter((item) => item.id !== made.id)];
|
|
228
|
+
selectedLabel = made.label;
|
|
229
|
+
labelledId = made.id;
|
|
230
|
+
value = made.id;
|
|
231
|
+
onchange?.(made.id);
|
|
232
|
+
recordControlUserEdit(
|
|
233
|
+
interactionContext,
|
|
234
|
+
controlId,
|
|
235
|
+
interaction === false ? undefined : interaction?.subject,
|
|
236
|
+
);
|
|
237
|
+
if (rootEl) emitControlChange(rootEl);
|
|
238
|
+
}
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.warn('[smrt-ui] RelationInput onCreate failed', {
|
|
241
|
+
error: String(err),
|
|
242
|
+
});
|
|
243
|
+
} finally {
|
|
244
|
+
creating = false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Keep the visible label in step with `value` however it changed (initial
|
|
249
|
+
// value, parent rebind, form reset): known from a search, else resolved.
|
|
250
|
+
$effect(() => {
|
|
251
|
+
const id = value;
|
|
252
|
+
const known = results.find((item) => item.id === id);
|
|
253
|
+
untrack(() => {
|
|
254
|
+
if (!id) {
|
|
255
|
+
selectedLabel = '';
|
|
256
|
+
labelledId = '';
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (id === labelledId) return;
|
|
260
|
+
if (known) {
|
|
261
|
+
selectedLabel = known.label;
|
|
262
|
+
labelledId = id;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// A different id than the one labelled: never keep the previous label.
|
|
266
|
+
selectedLabel = '';
|
|
267
|
+
labelledId = '';
|
|
268
|
+
if (!resolve || resolvingId === id) return;
|
|
269
|
+
resolvingId = id;
|
|
270
|
+
resolve(id)
|
|
271
|
+
.then((record) => {
|
|
272
|
+
if (value === id && record) {
|
|
273
|
+
selectedLabel = record.label;
|
|
274
|
+
labelledId = id;
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
.catch((err) => {
|
|
278
|
+
console.warn('[smrt-ui] RelationInput resolve failed', {
|
|
279
|
+
error: String(err),
|
|
280
|
+
});
|
|
281
|
+
})
|
|
282
|
+
.finally(() => {
|
|
283
|
+
if (resolvingId === id) resolvingId = '';
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
$effect(() => () => clearTimeout(timer));
|
|
289
|
+
</script>
|
|
290
|
+
|
|
291
|
+
<div class="relation-input" bind:this={rootEl}>
|
|
292
|
+
<div class="field">
|
|
293
|
+
<Combobox
|
|
294
|
+
{name}
|
|
295
|
+
{label}
|
|
296
|
+
{options}
|
|
297
|
+
{placeholder}
|
|
298
|
+
{disabled}
|
|
299
|
+
{required}
|
|
300
|
+
{interaction}
|
|
301
|
+
{status}
|
|
302
|
+
{describedby}
|
|
303
|
+
bind:value
|
|
304
|
+
valueLabel={selectedLabel}
|
|
305
|
+
filter={false}
|
|
306
|
+
busy={loading}
|
|
307
|
+
invalid={!!error}
|
|
308
|
+
onquery={handleQuery}
|
|
309
|
+
onvaluechange={handleValueChange}
|
|
310
|
+
>
|
|
311
|
+
{#snippet optionContent(option)}
|
|
312
|
+
<span class="option-label">{option.label}</span>
|
|
313
|
+
{#if detailById.get(String(option.value))}
|
|
314
|
+
<span class="option-detail">{detailById.get(String(option.value))}</span>
|
|
315
|
+
{/if}
|
|
316
|
+
{/snippet}
|
|
317
|
+
</Combobox>
|
|
318
|
+
{#if canClear}
|
|
319
|
+
<Button
|
|
320
|
+
variant="ghost"
|
|
321
|
+
size="sm"
|
|
322
|
+
class="clear"
|
|
323
|
+
aria-label={clearLabel ?? `Clear ${label}`}
|
|
324
|
+
onclick={clear}
|
|
325
|
+
>
|
|
326
|
+
×
|
|
327
|
+
</Button>
|
|
328
|
+
{/if}
|
|
329
|
+
</div>
|
|
330
|
+
{#if description}<p id={hintId} class="hint">{description}</p>{/if}
|
|
331
|
+
{#if onCreate}
|
|
332
|
+
<Button
|
|
333
|
+
variant="ghost"
|
|
334
|
+
size="sm"
|
|
335
|
+
class="create"
|
|
336
|
+
disabled={disabled || creating}
|
|
337
|
+
onclick={create}
|
|
338
|
+
>
|
|
339
|
+
+ {newLabel}
|
|
340
|
+
</Button>
|
|
341
|
+
{/if}
|
|
342
|
+
{#if error}<p id={errorId} class="error" role="alert">{error}</p>{/if}
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<style>
|
|
346
|
+
.relation-input {
|
|
347
|
+
display: grid;
|
|
348
|
+
gap: var(--smrt-spacing-1, 4px);
|
|
349
|
+
min-width: 0;
|
|
350
|
+
}
|
|
351
|
+
.field {
|
|
352
|
+
position: relative;
|
|
353
|
+
}
|
|
354
|
+
.field :global(.clear) {
|
|
355
|
+
position: absolute;
|
|
356
|
+
right: var(--smrt-spacing-1, 4px);
|
|
357
|
+
bottom: var(--smrt-spacing-1, 4px);
|
|
358
|
+
}
|
|
359
|
+
.option-label,
|
|
360
|
+
.option-detail {
|
|
361
|
+
display: block;
|
|
362
|
+
}
|
|
363
|
+
.option-detail {
|
|
364
|
+
font-size: var(--smrt-typography-body-small-size, 0.75rem);
|
|
365
|
+
color: var(--smrt-color-on-surface-variant);
|
|
366
|
+
}
|
|
367
|
+
.hint,
|
|
368
|
+
.error {
|
|
369
|
+
margin: 0;
|
|
370
|
+
font-size: var(--smrt-typography-body-small-size, 0.75rem);
|
|
371
|
+
}
|
|
372
|
+
.hint {
|
|
373
|
+
color: var(--smrt-color-on-surface-variant);
|
|
374
|
+
}
|
|
375
|
+
.error {
|
|
376
|
+
color: var(--smrt-color-error);
|
|
377
|
+
}
|
|
378
|
+
</style>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ControlInteractionOptions } from './control-interaction.js';
|
|
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":"AAOA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAK1E,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;AAyPD,QAAA,MAAM,aAAa,gDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -47,6 +47,7 @@ export { default as ProvinceSelect } from './ProvinceSelect.svelte';
|
|
|
47
47
|
export { default as Radio } from './Radio.svelte';
|
|
48
48
|
export { default as RadioGroup } from './RadioGroup.svelte';
|
|
49
49
|
export { default as RangeSlider } from './RangeSlider.svelte';
|
|
50
|
+
export { default as RelationInput } from './RelationInput.svelte';
|
|
50
51
|
export { playReadyBeep, primeReadyBeep, type ReadyBeepOptions, } from './ready-beep.js';
|
|
51
52
|
export { default as SearchInput } from './SearchInput.svelte';
|
|
52
53
|
export { default as SegmentedControl } from './SegmentedControl.svelte';
|
|
@@ -62,6 +63,6 @@ export { default as Textarea } from './Textarea.svelte';
|
|
|
62
63
|
export { default as TimePicker } from './TimePicker.svelte';
|
|
63
64
|
export { default as Toggle } from './Toggle.svelte';
|
|
64
65
|
export { default as ToggleButton } from './ToggleButton.svelte';
|
|
65
|
-
export type { CameraCaptureLabels, CameraCaptureState, CaptureResult, FormError, RangeSliderValue, SegmentedControlOption, SignaturePadLabels, SignaturePadState, } from './types.js';
|
|
66
|
+
export type { CameraCaptureLabels, CameraCaptureState, CaptureResult, FormError, RangeSliderValue, RelationOption, SegmentedControlOption, SignaturePadLabels, SignaturePadState, } from './types.js';
|
|
66
67
|
export { type ControlRegistrationDescriptor, useControlRegistration, } from './use-control-registration.svelte.js';
|
|
67
68
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/forms/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/forms/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,7 @@ export { default as ProvinceSelect } from './ProvinceSelect.svelte';
|
|
|
48
48
|
export { default as Radio } from './Radio.svelte';
|
|
49
49
|
export { default as RadioGroup } from './RadioGroup.svelte';
|
|
50
50
|
export { default as RangeSlider } from './RangeSlider.svelte';
|
|
51
|
+
export { default as RelationInput } from './RelationInput.svelte';
|
|
51
52
|
export { playReadyBeep, primeReadyBeep, } from './ready-beep.js';
|
|
52
53
|
export { default as SearchInput } from './SearchInput.svelte';
|
|
53
54
|
export { default as SegmentedControl } from './SegmentedControl.svelte';
|
|
@@ -52,4 +52,13 @@ export interface SignaturePadLabels {
|
|
|
52
52
|
useSignature: string;
|
|
53
53
|
committed: string;
|
|
54
54
|
}
|
|
55
|
+
/** A searchable relation target for the RelationInput component */
|
|
56
|
+
export interface RelationOption {
|
|
57
|
+
/** Record id; this is the value the form posts. */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Human-readable name shown in the field and the list. */
|
|
60
|
+
label: string;
|
|
61
|
+
/** Secondary text shown under the label in the list. */
|
|
62
|
+
detail?: string;
|
|
63
|
+
}
|
|
55
64
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,IAAI,EAAE,IAAI,CAAC;IACX,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAC1B,KAAK,GACL,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,mBAAmB,GACnB,WAAW,GACX,aAAa,GACb,OAAO,GACP,UAAU,CAAC;AAEf,oFAAoF;AACpF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEjE,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,IAAI,EAAE,IAAI,CAAC;IACX,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAC1B,KAAK,GACL,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,mBAAmB,GACnB,WAAW,GACX,aAAa,GACb,OAAO,GACP,UAAU,CAAC;AAEf,oFAAoF;AACpF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEjE,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;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"}
|
|
@@ -13,6 +13,7 @@ import MultiSelect from '../../components/forms/MultiSelect.svelte';
|
|
|
13
13
|
import Radio from '../../components/forms/Radio.svelte';
|
|
14
14
|
import RadioGroup from '../../components/forms/RadioGroup.svelte';
|
|
15
15
|
import RangeSlider from '../../components/forms/RangeSlider.svelte';
|
|
16
|
+
import RelationInput from '../../components/forms/RelationInput.svelte';
|
|
16
17
|
import SegmentedControl from '../../components/forms/SegmentedControl.svelte';
|
|
17
18
|
import Slider from '../../components/forms/Slider.svelte';
|
|
18
19
|
import Switch from '../../components/forms/Switch.svelte';
|
|
@@ -27,6 +28,26 @@ const countries = [
|
|
|
27
28
|
{ value: 'us', label: 'United States' },
|
|
28
29
|
{ value: 'mx', label: 'Mexico' },
|
|
29
30
|
];
|
|
31
|
+
// RelationInput demo: a caller-supplied lookup standing in for a REST search.
|
|
32
|
+
const demoCustomers = [
|
|
33
|
+
{ id: 'cus_001', label: 'Acme Corp', detail: 'acme@example.com' },
|
|
34
|
+
{ id: 'cus_002', label: 'Globex', detail: 'billing@globex.example' },
|
|
35
|
+
{ id: 'cus_003', label: 'Initech', detail: 'ap@initech.example' },
|
|
36
|
+
{ id: 'cus_004', label: 'Umbrella Ltd' },
|
|
37
|
+
];
|
|
38
|
+
async function searchCustomers(query: string) {
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
40
|
+
const q = query.toLowerCase();
|
|
41
|
+
return demoCustomers.filter(
|
|
42
|
+
(c) =>
|
|
43
|
+
c.label.toLowerCase().includes(q) ||
|
|
44
|
+
(c.detail ?? '').toLowerCase().includes(q),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
async function resolveCustomer(id: string) {
|
|
48
|
+
return demoCustomers.find((c) => c.id === id) ?? null;
|
|
49
|
+
}
|
|
50
|
+
let customerId = $state('');
|
|
30
51
|
let accepted = $state(true);
|
|
31
52
|
let notifications = $state(true);
|
|
32
53
|
let pinned = $state(false);
|
|
@@ -83,6 +104,7 @@ const identity = (controlId: string) => ({
|
|
|
83
104
|
|
|
84
105
|
<section><h5>Structured input</h5><div class="grid">
|
|
85
106
|
<Combobox name="country" label="Country" options={countries} bind:value={country} />
|
|
107
|
+
<RelationInput name="customerId" label="Customer" description="Search by name or email" search={searchCustomers} resolve={resolveCustomer} bind:value={customerId} />
|
|
86
108
|
<MultiSelect name="channels" label="Channels" options={[{ value: 'email', label: 'Email' }, { value: 'sms', label: 'SMS' }, { value: 'push', label: 'Push' }]} bind:values={channels} />
|
|
87
109
|
<TagsInput name="topics" label="Topics" bind:values={tags} />
|
|
88
110
|
<DatePicker name="date" label="Publish date" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InteractiveControlsPreview.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/playground/InteractiveControlsPreview.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"InteractiveControlsPreview.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/playground/InteractiveControlsPreview.svelte.ts"],"names":[],"mappings":"AA+IA,QAAA,MAAM,0BAA0B,2DAAwC,CAAC;AACzE,KAAK,0BAA0B,GAAG,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAChF,eAAe,0BAA0B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-ui",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.4",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Domain-agnostic Svelte 5 UI runtime for SMRT: primitives, i18n client, theme system, and module UI registry",
|
|
6
6
|
"type": "module",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"access": "public"
|
|
140
140
|
},
|
|
141
141
|
"dependencies": {
|
|
142
|
-
"@happyvertical/smrt-types": "0.55.
|
|
142
|
+
"@happyvertical/smrt-types": "0.55.4",
|
|
143
143
|
"esm-env": "^1.2.2"
|
|
144
144
|
},
|
|
145
145
|
"peerDependencies": {
|