@marianmeres/stuic 3.168.0 → 3.170.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/dist/README.md +1 -1
- package/dist/components/Input/FieldOptions.svelte +89 -54
- package/dist/components/Input/FieldOptions.svelte.d.ts +13 -0
- package/dist/components/Input/README.md +144 -31
- package/dist/components/Input/_internal/FieldLikeChips.svelte +286 -0
- package/dist/components/Input/_internal/FieldLikeChips.svelte.d.ts +65 -0
- package/dist/components/Input/field-options-i18n-sk.d.ts +21 -0
- package/dist/components/Input/field-options-i18n-sk.js +61 -0
- package/dist/components/Input/field-options-i18n.d.ts +82 -0
- package/dist/components/Input/field-options-i18n.js +90 -0
- package/dist/components/Input/index.css +58 -0
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.js +2 -0
- package/dist/components/Pill/Pill.svelte +8 -2
- package/dist/components/Pill/Pill.svelte.d.ts +2 -0
- package/dist/components/Pill/README.md +35 -22
- package/dist/components/Pill/index.css +57 -18
- package/dist/components/Timeline/README.md +204 -0
- package/dist/components/Timeline/Timeline.svelte +246 -0
- package/dist/components/Timeline/Timeline.svelte.d.ts +87 -0
- package/dist/components/Timeline/index.css +311 -0
- package/dist/components/Timeline/index.d.ts +1 -0
- package/dist/components/Timeline/index.js +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/domains/components.md +39 -1
- package/docs/maybe-todo.md +37 -13
- package/docs/upgrading.md +6 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
src/lib/
|
|
26
|
-
├── components/ #
|
|
26
|
+
├── components/ # 74 component directories
|
|
27
27
|
├── actions/ # 15 Svelte actions (use: directives)
|
|
28
28
|
├── attachments/ # Svelte attachments ({@attach} — preferred for new DOM helpers)
|
|
29
29
|
├── utils/ # 45 utility modules
|
|
@@ -129,7 +129,7 @@ Global tokens that control cross-component visual properties. Defined in `src/li
|
|
|
129
129
|
|
|
130
130
|
### Domain Docs
|
|
131
131
|
|
|
132
|
-
- [Components](./docs/domains/components.md) —
|
|
132
|
+
- [Components](./docs/domains/components.md) — 74 component directories, Props pattern, snippets
|
|
133
133
|
- [Theming](./docs/domains/theming.md) — CSS tokens, dark mode, themes
|
|
134
134
|
- [CSS presets](./docs/domains/css-presets.md) — ratio-locked frame (letterbox), safe-area, scrollbar
|
|
135
135
|
- [Actions](./docs/domains/actions.md) — 15 Svelte directives
|
package/dist/README.md
CHANGED
|
@@ -43,7 +43,7 @@ npm install @marianmeres/stuic
|
|
|
43
43
|
- **FieldRadios** - Radio button group
|
|
44
44
|
- **FieldFile** - File upload input
|
|
45
45
|
- **FieldAssets** - Multi-file upload with preview
|
|
46
|
-
- **FieldOptions** - Modal-based multi-select picker
|
|
46
|
+
- **FieldOptions** - Modal-based multi-select picker (optional inline `chips` display)
|
|
47
47
|
- **FieldKeyValues** - Key-value pairs editor with JSON serialization
|
|
48
48
|
- **FieldSwitch** - Toggle switch within a form
|
|
49
49
|
- **Fieldset** - Group of form fields with legend
|
|
@@ -20,12 +20,11 @@
|
|
|
20
20
|
type ValidationResult,
|
|
21
21
|
} from "../../actions/validate.svelte.js";
|
|
22
22
|
import type { TranslateFn } from "../../types.js";
|
|
23
|
+
import type { IntentColorKey } from "../../utils/design-tokens.js";
|
|
23
24
|
import { getId } from "../../utils/get-id.js";
|
|
24
|
-
import { isPlainObject } from "../../utils/is-plain-object.js";
|
|
25
25
|
import { maybeJsonParse } from "../../utils/maybe-json-parse.js";
|
|
26
26
|
import { waitForNextRepaint } from "../../utils/paint.js";
|
|
27
27
|
import { qsa } from "../../utils/qsa.js";
|
|
28
|
-
import { replaceMap } from "../../utils/replace-map.js";
|
|
29
28
|
import { strHash } from "../../utils/str-hash.js";
|
|
30
29
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
31
30
|
import Button from "../Button/Button.svelte";
|
|
@@ -36,8 +35,10 @@
|
|
|
36
35
|
import X from "../X/X.svelte";
|
|
37
36
|
import InputWrap from "./_internal/InputWrap.svelte";
|
|
38
37
|
import FieldLikeButton from "./FieldLikeButton.svelte";
|
|
38
|
+
import FieldLikeChips, { type FieldLikeChip } from "./_internal/FieldLikeChips.svelte";
|
|
39
39
|
import ListItemButton from "../ListItemButton/ListItemButton.svelte";
|
|
40
40
|
import type { InputWrapClassProps } from "./types.js";
|
|
41
|
+
import { t_default } from "./field-options-i18n.js";
|
|
41
42
|
|
|
42
43
|
export interface Option {
|
|
43
44
|
label: string;
|
|
@@ -90,6 +91,18 @@
|
|
|
90
91
|
* serialized to `value` on submit. No-op for single-select. Default `false`.
|
|
91
92
|
*/
|
|
92
93
|
ordered?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Opt-in: render the current selection as inline, individually removable chips
|
|
96
|
+
* (`Pill`s) instead of the comma-joined text button. Picking still happens in the
|
|
97
|
+
* modal (opened by the trailing button, or by clicking the empty part of the row);
|
|
98
|
+
* removing a chip writes `value` immediately, without the modal. `renderValue` is
|
|
99
|
+
* ignored in this mode (labels come from `renderOptionLabel`). Default `false`.
|
|
100
|
+
*/
|
|
101
|
+
chips?: boolean;
|
|
102
|
+
/** Classes for each chip (`chips` mode only) */
|
|
103
|
+
classChip?: string;
|
|
104
|
+
/** Pill intent of the chips (`chips` mode only) */
|
|
105
|
+
chipIntent?: IntentColorKey;
|
|
93
106
|
showIconsCheckbox?: boolean;
|
|
94
107
|
showIconsRadio?: boolean;
|
|
95
108
|
searchPlaceholder?: string;
|
|
@@ -97,56 +110,6 @@
|
|
|
97
110
|
itemIdPropName?: string;
|
|
98
111
|
onChange?: (value: string) => void;
|
|
99
112
|
}
|
|
100
|
-
|
|
101
|
-
// i18n ready
|
|
102
|
-
function t_default(
|
|
103
|
-
k: string,
|
|
104
|
-
values: false | null | undefined | Record<string, string | number> = null,
|
|
105
|
-
fallback: string | boolean = "",
|
|
106
|
-
i18nSpanWrap: boolean = true
|
|
107
|
-
) {
|
|
108
|
-
const m: Record<string, string> = {
|
|
109
|
-
field_req_att: "This field requires attention. Please review and try again.",
|
|
110
|
-
cardinality_of: "of max",
|
|
111
|
-
cardinality_selected: "selected",
|
|
112
|
-
submit: "Submit",
|
|
113
|
-
select_all: "Select all",
|
|
114
|
-
clear_all: "Clear selected",
|
|
115
|
-
clear: "Clear",
|
|
116
|
-
search_placeholder: "Type to search...",
|
|
117
|
-
search_submit_placeholder: "Type to search and submit...",
|
|
118
|
-
cardinality_full: "Max selection reached",
|
|
119
|
-
select_from_list: "Please select from the list only",
|
|
120
|
-
x_close: "Clear input or close [esc]",
|
|
121
|
-
close: "Close [esc]",
|
|
122
|
-
unknown_allowed: "Select or type and submit",
|
|
123
|
-
unknown_not_allowed: "Select from the list",
|
|
124
|
-
no_results: "No results found.",
|
|
125
|
-
add_new: 'Add "{{value}}"...',
|
|
126
|
-
click_add_new: "You must add the value to continue",
|
|
127
|
-
//
|
|
128
|
-
pick_tab: "Pick",
|
|
129
|
-
arrange_tab: "Arrange ({{value}})",
|
|
130
|
-
arrange_help: "Reorder the selected items. Use the buttons to move them.",
|
|
131
|
-
sort_az: "Sort A–Z",
|
|
132
|
-
reverse: "Reverse",
|
|
133
|
-
shuffle: "Shuffle",
|
|
134
|
-
move_up: "Move up",
|
|
135
|
-
move_down: "Move down",
|
|
136
|
-
move_to_top: "Move to top",
|
|
137
|
-
move_to_bottom: "Move to bottom",
|
|
138
|
-
remove_item: "Remove",
|
|
139
|
-
moved_up: "Moved {{value}} up",
|
|
140
|
-
moved_down: "Moved {{value}} down",
|
|
141
|
-
removed_item: "Removed {{value}}",
|
|
142
|
-
sorted_az: "Sorted A to Z",
|
|
143
|
-
reversed: "Order reversed",
|
|
144
|
-
shuffled: "Order shuffled",
|
|
145
|
-
};
|
|
146
|
-
let out = m[k] ?? fallback ?? k;
|
|
147
|
-
|
|
148
|
-
return isPlainObject(values) ? replaceMap(out, values as any) : out;
|
|
149
|
-
}
|
|
150
113
|
</script>
|
|
151
114
|
|
|
152
115
|
<script lang="ts">
|
|
@@ -214,6 +177,9 @@
|
|
|
214
177
|
renderOptionGroup = (s: string) => `${s}`.replaceAll("_", " "),
|
|
215
178
|
allowUnknown = false,
|
|
216
179
|
ordered = false,
|
|
180
|
+
chips = false,
|
|
181
|
+
classChip,
|
|
182
|
+
chipIntent,
|
|
217
183
|
showIconsCheckbox = true,
|
|
218
184
|
showIconsRadio = false,
|
|
219
185
|
searchPlaceholder,
|
|
@@ -229,8 +195,9 @@
|
|
|
229
195
|
modal = modalDialog;
|
|
230
196
|
});
|
|
231
197
|
|
|
232
|
-
// Imperative API delegates to the inner FieldLikeButton
|
|
233
|
-
|
|
198
|
+
// Imperative API delegates to the inner trigger (FieldLikeButton, or FieldLikeChips in
|
|
199
|
+
// `chips` mode) — both expose the same methods.
|
|
200
|
+
let triggerRef: FieldLikeButton | FieldLikeChips | undefined = $state();
|
|
234
201
|
|
|
235
202
|
/** Trigger validation now. Renders the inline message if invalid. */
|
|
236
203
|
export function validate(): ValidationResult | undefined {
|
|
@@ -604,6 +571,38 @@
|
|
|
604
571
|
});
|
|
605
572
|
}
|
|
606
573
|
|
|
574
|
+
// --- chips (inline display of the selection) ---
|
|
575
|
+
|
|
576
|
+
// straight from `value`, in value order — so with `ordered` the arranged order is what
|
|
577
|
+
// the user sees; labels go through the same renderer as the modal's options
|
|
578
|
+
let chipItems: FieldLikeChip[] = $derived.by(() => {
|
|
579
|
+
if (!chips) return [];
|
|
580
|
+
const parsed = maybeJsonParse(value || "[]");
|
|
581
|
+
if (!Array.isArray(parsed)) return [];
|
|
582
|
+
return parsed
|
|
583
|
+
.filter((item) => item != null)
|
|
584
|
+
.map((item: Item, i: number) => {
|
|
585
|
+
const label = _renderOptionLabel(item);
|
|
586
|
+
return {
|
|
587
|
+
// index-suffixed so a (malformed) duplicate id can't break the keyed each
|
|
588
|
+
key: `${item[itemIdPropName] ?? ""}__${i}`,
|
|
589
|
+
label,
|
|
590
|
+
removeLabel: t("chips_remove", { value: label }),
|
|
591
|
+
};
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// removing a chip is an immediate, modal-less edit of `value` + the same change
|
|
596
|
+
// notification the modal submit does (so validation re-runs)
|
|
597
|
+
function removeChip(_chip: FieldLikeChip, index: number) {
|
|
598
|
+
const parsed = maybeJsonParse(value || "[]");
|
|
599
|
+
if (!Array.isArray(parsed)) return;
|
|
600
|
+
const items = parsed.filter((item) => item != null);
|
|
601
|
+
value = JSON.stringify(items.filter((_, i) => i !== index));
|
|
602
|
+
_dispatch_change_to_owner();
|
|
603
|
+
onChange?.(value);
|
|
604
|
+
}
|
|
605
|
+
|
|
607
606
|
// "outer" submit - will set the outer bound value (always string) and close modal...
|
|
608
607
|
// further process is left on the consumer
|
|
609
608
|
function submit() {
|
|
@@ -712,6 +711,42 @@
|
|
|
712
711
|
<div>
|
|
713
712
|
{#if trigger}
|
|
714
713
|
{@render trigger({ value, modal: modalDialog })}
|
|
714
|
+
{:else if chips}
|
|
715
|
+
<FieldLikeChips
|
|
716
|
+
bind:this={triggerRef}
|
|
717
|
+
bind:value
|
|
718
|
+
bind:input={parentHiddenInputEl}
|
|
719
|
+
chips={chipItems}
|
|
720
|
+
{name}
|
|
721
|
+
class={classProp}
|
|
722
|
+
{label}
|
|
723
|
+
{description}
|
|
724
|
+
{labelLeft}
|
|
725
|
+
{labelAfter}
|
|
726
|
+
{below}
|
|
727
|
+
{labelLeftWidth}
|
|
728
|
+
{labelLeftBreakpoint}
|
|
729
|
+
{classLabel}
|
|
730
|
+
{classLabelBox}
|
|
731
|
+
{classInputBox}
|
|
732
|
+
{classInputBoxWrap}
|
|
733
|
+
{classInputBoxWrapInvalid}
|
|
734
|
+
{classDescBox}
|
|
735
|
+
{classDescBoxToggle}
|
|
736
|
+
{classBelowBox}
|
|
737
|
+
{classValidationBox}
|
|
738
|
+
{style}
|
|
739
|
+
validate={wrappedValidate}
|
|
740
|
+
{required}
|
|
741
|
+
{disabled}
|
|
742
|
+
{tabindex}
|
|
743
|
+
{classChip}
|
|
744
|
+
{chipIntent}
|
|
745
|
+
placeholder={t("chips_placeholder")}
|
|
746
|
+
openLabel={t("chips_open")}
|
|
747
|
+
onOpen={() => modalDialog?.open()}
|
|
748
|
+
onRemove={removeChip}
|
|
749
|
+
/>
|
|
715
750
|
{:else}
|
|
716
751
|
<FieldLikeButton
|
|
717
752
|
bind:this={triggerRef}
|
|
@@ -2,6 +2,7 @@ import { type Item } from "@marianmeres/item-collection";
|
|
|
2
2
|
import { type Snippet } from "svelte";
|
|
3
3
|
import { type ValidateOptions, type ValidationResult } from "../../actions/validate.svelte.js";
|
|
4
4
|
import type { TranslateFn } from "../../types.js";
|
|
5
|
+
import type { IntentColorKey } from "../../utils/design-tokens.js";
|
|
5
6
|
import { ModalDialog } from "../ModalDialog/index.js";
|
|
6
7
|
import { NotificationsStack } from "../Notifications/index.js";
|
|
7
8
|
import type { THC } from "../Thc/Thc.svelte";
|
|
@@ -62,6 +63,18 @@ export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
|
62
63
|
* serialized to `value` on submit. No-op for single-select. Default `false`.
|
|
63
64
|
*/
|
|
64
65
|
ordered?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Opt-in: render the current selection as inline, individually removable chips
|
|
68
|
+
* (`Pill`s) instead of the comma-joined text button. Picking still happens in the
|
|
69
|
+
* modal (opened by the trailing button, or by clicking the empty part of the row);
|
|
70
|
+
* removing a chip writes `value` immediately, without the modal. `renderValue` is
|
|
71
|
+
* ignored in this mode (labels come from `renderOptionLabel`). Default `false`.
|
|
72
|
+
*/
|
|
73
|
+
chips?: boolean;
|
|
74
|
+
/** Classes for each chip (`chips` mode only) */
|
|
75
|
+
classChip?: string;
|
|
76
|
+
/** Pill intent of the chips (`chips` mode only) */
|
|
77
|
+
chipIntent?: IntentColorKey;
|
|
65
78
|
showIconsCheckbox?: boolean;
|
|
66
79
|
showIconsRadio?: boolean;
|
|
67
80
|
searchPlaceholder?: string;
|
|
@@ -408,48 +408,54 @@ Components use data attributes for CSS styling:
|
|
|
408
408
|
|
|
409
409
|
## FieldOptions
|
|
410
410
|
|
|
411
|
-
A modal-based multi-select/single-select component with search functionality, typeahead support, and option grouping.
|
|
411
|
+
A modal-based multi-select/single-select component with search functionality, typeahead support, and option grouping. The closed field can show the selection as inline removable chips (`chips`).
|
|
412
412
|
|
|
413
413
|
### Props
|
|
414
414
|
|
|
415
|
-
| Prop | Type | Default | Description
|
|
416
|
-
| ------------------- | ---------------------------------------------------------- | ---------- |
|
|
417
|
-
| `value` | `string` | `"[]"` | JSON array of selected items (bindable)
|
|
418
|
-
| `name` | `string` | - | Form field name
|
|
419
|
-
| `getOptions` | `(q: string, current: Item[]) => Promise<{found: Item[]}>` | - | Async function to fetch options
|
|
420
|
-
| `cardinality` | `number` | `Infinity` | Max selections (-1 for unlimited)
|
|
421
|
-
| `allowUnknown` | `boolean` | `false` | Allow typing custom values
|
|
422
|
-
| `ordered` | `boolean` | `false` | Opt-in: add an "Arrange" screen to manually order the selection (multi-select only)
|
|
423
|
-
| `
|
|
424
|
-
| `
|
|
425
|
-
| `
|
|
426
|
-
| `
|
|
427
|
-
| `
|
|
428
|
-
| `
|
|
429
|
-
| `
|
|
430
|
-
| `
|
|
415
|
+
| Prop | Type | Default | Description |
|
|
416
|
+
| ------------------- | ---------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------- |
|
|
417
|
+
| `value` | `string` | `"[]"` | JSON array of selected items (bindable) |
|
|
418
|
+
| `name` | `string` | - | Form field name |
|
|
419
|
+
| `getOptions` | `(q: string, current: Item[]) => Promise<{found: Item[]}>` | - | Async function to fetch options |
|
|
420
|
+
| `cardinality` | `number` | `Infinity` | Max selections (-1 for unlimited) |
|
|
421
|
+
| `allowUnknown` | `boolean` | `false` | Allow typing custom values |
|
|
422
|
+
| `ordered` | `boolean` | `false` | Opt-in: add an "Arrange" screen to manually order the selection (multi-select only) |
|
|
423
|
+
| `chips` | `boolean` | `false` | Opt-in: show the selection as inline removable chips; picking still happens in the modal |
|
|
424
|
+
| `classChip` | `string` | - | Classes for each chip (`chips` mode) |
|
|
425
|
+
| `chipIntent` | `IntentColorKey` | - | Pill intent of the chips (`chips` mode) |
|
|
426
|
+
| `renderOptionLabel` | `(item: Item) => string` | - | Custom option label renderer |
|
|
427
|
+
| `renderOptionGroup` | `(s: string) => string` | - | Custom optgroup label renderer |
|
|
428
|
+
| `renderValue` | `(stringifiedItems: string) => string` | - | Custom value display renderer |
|
|
429
|
+
| `showIconsCheckbox` | `boolean` | `true` | Show checkbox icons in multi-select |
|
|
430
|
+
| `showIconsRadio` | `boolean` | `false` | Show radio icons in single-select |
|
|
431
|
+
| `searchPlaceholder` | `string` | - | Custom search placeholder |
|
|
432
|
+
| `itemIdPropName` | `string` | `"id"` | Property name for item ID |
|
|
433
|
+
| `notifications` | `NotificationsStack` | - | Notification handler for errors |
|
|
431
434
|
|
|
432
435
|
### Class Props
|
|
433
436
|
|
|
434
|
-
| Prop | Target
|
|
435
|
-
| ------------------- |
|
|
436
|
-
| `classOption` | Option item (ListItemButton)
|
|
437
|
-
| `classOptionActive` | Active/selected option
|
|
438
|
-
| `classOptgroup` | Option group label
|
|
439
|
-
| `classModalField` | Modal field wrapper
|
|
437
|
+
| Prop | Target |
|
|
438
|
+
| ------------------- | -------------------------------- |
|
|
439
|
+
| `classOption` | Option item (ListItemButton) |
|
|
440
|
+
| `classOptionActive` | Active/selected option |
|
|
441
|
+
| `classOptgroup` | Option group label |
|
|
442
|
+
| `classModalField` | Modal field wrapper |
|
|
443
|
+
| `classChip` | Each chip (Pill) in `chips` mode |
|
|
440
444
|
|
|
441
445
|
### CSS Variables
|
|
442
446
|
|
|
443
447
|
#### Component Tokens
|
|
444
448
|
|
|
445
|
-
| Variable
|
|
446
|
-
|
|
|
447
|
-
| `--stuic-field-options-divider`
|
|
448
|
-
| `--stuic-field-options-control-text`
|
|
449
|
-
| `--stuic-field-options-control-text-hover`
|
|
450
|
-
| `--stuic-field-options-control-ring`
|
|
451
|
-
| `--stuic-field-options-muted-text`
|
|
452
|
-
| `--stuic-field-options-optgroup-text`
|
|
449
|
+
| Variable | Default | Description |
|
|
450
|
+
| ---------------------------------------------- | -------------------------------- | ----------------------------------------- |
|
|
451
|
+
| `--stuic-field-options-divider` | `--stuic-color-border` | Divider/separator color |
|
|
452
|
+
| `--stuic-field-options-control-text` | `--stuic-color-muted-foreground` | Control button text color |
|
|
453
|
+
| `--stuic-field-options-control-text-hover` | `--stuic-color-foreground` | Control button hover text color |
|
|
454
|
+
| `--stuic-field-options-control-ring` | `--stuic-color-ring` | Control button focus ring |
|
|
455
|
+
| `--stuic-field-options-muted-text` | `--stuic-color-muted-foreground` | Muted/secondary text color |
|
|
456
|
+
| `--stuic-field-options-optgroup-text` | `--stuic-color-muted-foreground` | Option group label color |
|
|
457
|
+
| `--stuic-field-options-chips-gap` | `0.25rem` | Gap between chips (`chips` mode) |
|
|
458
|
+
| `--stuic-field-options-chips-placeholder-text` | `--stuic-input-placeholder` | Placeholder color of an empty `chips` row |
|
|
453
459
|
|
|
454
460
|
### Usage
|
|
455
461
|
|
|
@@ -503,6 +509,113 @@ on submit (and round-trips on reopen). Single-select fields ignore the prop.
|
|
|
503
509
|
> default contract, so the Arrange list can render selected items even when they aren't in
|
|
504
510
|
> the current search results.
|
|
505
511
|
|
|
512
|
+
### Chips display (`chips`)
|
|
513
|
+
|
|
514
|
+
By default the closed field is a button showing the selection as comma-joined text. Opt in
|
|
515
|
+
with `chips` to show it as inline, individually removable `Pill` chips instead — the tags form
|
|
516
|
+
factor. Picking still happens in the same modal: open it with the trailing button, or by
|
|
517
|
+
clicking the empty part of the row. Each chip's × removes that item immediately (writes
|
|
518
|
+
`value`, fires `onChange`, re-runs validation) without opening the modal, and keyboard focus
|
|
519
|
+
stays in the field. Chips render in `value` order, so with `ordered` they mirror the arranged
|
|
520
|
+
order. `renderValue` is ignored in this mode; labels come from `renderOptionLabel`.
|
|
521
|
+
|
|
522
|
+
```svelte
|
|
523
|
+
<FieldOptions
|
|
524
|
+
label="Tags"
|
|
525
|
+
name="tags"
|
|
526
|
+
bind:value
|
|
527
|
+
{getOptions}
|
|
528
|
+
cardinality={-1}
|
|
529
|
+
allowUnknown
|
|
530
|
+
chips
|
|
531
|
+
chipIntent="primary"
|
|
532
|
+
/>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Each chip's × is a full-height 24px square (the Pill's own touch-target floor,
|
|
536
|
+
`--stuic-pill-dismiss-min-size`), so no extra hit-area padding is needed on touch devices.
|
|
537
|
+
Typing directly into the field (an inline combobox) is deliberately not part of this mode — the
|
|
538
|
+
modal remains the single place where options are searched and picked, which is what keeps the
|
|
539
|
+
field usable under a soft keyboard.
|
|
540
|
+
|
|
541
|
+
### i18n
|
|
542
|
+
|
|
543
|
+
All built-in texts go through the `t` prop. English is the built-in default; Slovak ships
|
|
544
|
+
bundled and opt-in (importing it is what pulls it into your bundle — English-only consumers
|
|
545
|
+
pay nothing).
|
|
546
|
+
|
|
547
|
+
```svelte
|
|
548
|
+
<script>
|
|
549
|
+
import {
|
|
550
|
+
FieldOptions,
|
|
551
|
+
createFieldOptionsT,
|
|
552
|
+
FIELD_OPTIONS_MESSAGES_SK,
|
|
553
|
+
} from "@marianmeres/stuic";
|
|
554
|
+
|
|
555
|
+
const t = createFieldOptionsT(FIELD_OPTIONS_MESSAGES_SK);
|
|
556
|
+
</script>
|
|
557
|
+
|
|
558
|
+
<FieldOptions name="tags" bind:value {getOptions} {t} />
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
`createFieldOptionsT(messages, fallbackMessages?)` falls back to
|
|
562
|
+
`FIELD_OPTIONS_MESSAGES_EN` for any key the catalog does not define, so a partial catalog is
|
|
563
|
+
fine and a raw key is never rendered — pass your own object to translate into a language
|
|
564
|
+
that is not bundled, or to override individual texts. This matters most with `chips`, where
|
|
565
|
+
`chips_placeholder`, `chips_open` and `chips_remove` end up in the **closed field**, on the
|
|
566
|
+
page, next to your own copy (a filter with nothing selected is not "nothing selected", it is
|
|
567
|
+
_everything_):
|
|
568
|
+
|
|
569
|
+
```ts
|
|
570
|
+
// reword two strings, keep English everywhere else
|
|
571
|
+
const t = createFieldOptionsT({ chips_placeholder: "Any tag", chips_open: "Edit tags" });
|
|
572
|
+
|
|
573
|
+
// or: full Slovak, plus a field specific tweak
|
|
574
|
+
const t = createFieldOptionsT({
|
|
575
|
+
...FIELD_OPTIONS_MESSAGES_SK,
|
|
576
|
+
chips_placeholder: "Bez štítkov",
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Placeholders are mustache-style (`{{value}}`).
|
|
581
|
+
|
|
582
|
+
| Key | English | Used by |
|
|
583
|
+
| --------------------------- | ----------------------------------------------------------- | ----------------------------------- |
|
|
584
|
+
| `field_req_att` | This field requires attention. Please review and try again. | Validation message |
|
|
585
|
+
| `chips_placeholder` | Nothing selected | Empty `chips` row |
|
|
586
|
+
| `chips_open` | Choose... | `chips` trailing button (+ tooltip) |
|
|
587
|
+
| `chips_remove` | Remove {{value}} | Each chip's × accessible name |
|
|
588
|
+
| `search_placeholder` | Type to search... | Modal search input |
|
|
589
|
+
| `search_submit_placeholder` | Type to search and submit... | Modal search input (`allowUnknown`) |
|
|
590
|
+
| `x_close` | Clear input or close [esc] | Search input's × tooltip |
|
|
591
|
+
| `close` | Close [esc] | Modal close button tooltip |
|
|
592
|
+
| `submit` | Submit | Modal submit button |
|
|
593
|
+
| `select_all` | Select all | Modal control |
|
|
594
|
+
| `clear_all` / `clear` | Clear selected / Clear | Modal control (multi / single) |
|
|
595
|
+
| `cardinality_of` | of max | Selection counter |
|
|
596
|
+
| `cardinality_selected` | selected | Selection counter |
|
|
597
|
+
| `cardinality_full` | Max selection reached | Notification |
|
|
598
|
+
| `no_results` | No results found. | Empty options list |
|
|
599
|
+
| `add_new` | Add "{{value}}"... | `allowUnknown` add row |
|
|
600
|
+
| `click_add_new` | You must add the value to continue | Notification |
|
|
601
|
+
| `select_from_list` | Please select from the list only | Notification |
|
|
602
|
+
| `unknown_allowed` | Select or type and submit | Modal footer hint |
|
|
603
|
+
| `unknown_not_allowed` | Select from the list | Modal footer hint |
|
|
604
|
+
| `pick_tab` / `arrange_tab` | Pick / Arrange ({{value}}) | `ordered` tab header |
|
|
605
|
+
| `arrange_help` | Reorder the selected items. Use the buttons to move them. | `ordered` screen hint |
|
|
606
|
+
| `sort_az` | Sort A–Z | `ordered` shortcut |
|
|
607
|
+
| `reverse` | Reverse | `ordered` shortcut |
|
|
608
|
+
| `shuffle` | Shuffle | `ordered` shortcut |
|
|
609
|
+
| `move_up` / `move_down` | Move up / Move down | `ordered` row buttons |
|
|
610
|
+
| `move_to_top` | Move to top | `ordered` row button |
|
|
611
|
+
| `move_to_bottom` | Move to bottom | `ordered` row button |
|
|
612
|
+
| `remove_item` | Remove | `ordered` row button |
|
|
613
|
+
| `moved_up` / `moved_down` | Moved {{value}} up / Moved {{value}} down | Screen reader announcement |
|
|
614
|
+
| `removed_item` | Removed {{value}} | Screen reader announcement |
|
|
615
|
+
| `sorted_az` | Sorted A to Z | Screen reader announcement |
|
|
616
|
+
| `reversed` | Order reversed | Screen reader announcement |
|
|
617
|
+
| `shuffled` | Order shuffled | Screen reader announcement |
|
|
618
|
+
|
|
506
619
|
### Customization Examples
|
|
507
620
|
|
|
508
621
|
```css
|