@k2b/ui 0.35.0 → 0.37.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/dist/browser/actions/Dropdown.js +132 -116
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/Tabs.js +18 -15
- package/dist/browser/actions/Tabs.js.map +1 -1
- package/dist/browser/chat/ChatComposer.js +58 -25
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/content/MapView.js +128 -13
- package/dist/browser/content/MapView.js.map +1 -1
- package/dist/browser/content/MapViewFrame.js +1 -1
- package/dist/browser/content/MapViewFrame.js.map +1 -1
- package/dist/browser/content/MessageRow.js +164 -152
- package/dist/browser/content/MessageRow.js.map +1 -1
- package/dist/browser/content/index.js +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/map-view-glyphs.js +23 -0
- package/dist/browser/content/map-view-glyphs.js.map +1 -0
- package/dist/browser/content/map-view-live.js +45 -22
- package/dist/browser/content/map-view-live.js.map +1 -1
- package/dist/browser/content/map-view-snapshot.js +191 -0
- package/dist/browser/content/map-view-snapshot.js.map +1 -0
- package/dist/browser/content/map-view-types.js.map +1 -1
- package/dist/browser/feedback/prompts.js +23 -9
- package/dist/browser/feedback/prompts.js.map +1 -1
- package/dist/browser/inputs/Combobox.js +164 -82
- package/dist/browser/inputs/Combobox.js.map +1 -1
- package/dist/browser/inputs/RichSelect.js +86 -67
- package/dist/browser/inputs/RichSelect.js.map +1 -1
- package/dist/browser/inputs/TextInput.js +1 -1
- package/dist/browser/inputs/TextInput.js.map +1 -1
- package/dist/browser/internal/keyed-for.js +47 -0
- package/dist/browser/internal/keyed-for.js.map +1 -0
- package/dist/browser/layout/AppWorkspace.js +73 -70
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/ScrollArea.js +4 -1
- package/dist/browser/layout/ScrollArea.js.map +1 -1
- package/dist/browser/text-editor/engine.js +13 -10
- package/dist/browser/text-editor/engine.js.map +1 -1
- package/dist/ssr/actions/Dropdown.js +101 -85
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/Tabs.js +12 -9
- package/dist/ssr/actions/Tabs.js.map +1 -1
- package/dist/ssr/chat/ChatComposer.js +41 -15
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/content/MapView.js +128 -13
- package/dist/ssr/content/MapView.js.map +1 -1
- package/dist/ssr/content/MapViewFrame.js +1 -1
- package/dist/ssr/content/MapViewFrame.js.map +1 -1
- package/dist/ssr/content/MessageRow.js +9 -2
- package/dist/ssr/content/MessageRow.js.map +1 -1
- package/dist/ssr/content/index.js +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/map-view-glyphs.js +23 -0
- package/dist/ssr/content/map-view-glyphs.js.map +1 -0
- package/dist/ssr/content/map-view-snapshot.js +191 -0
- package/dist/ssr/content/map-view-snapshot.js.map +1 -0
- package/dist/ssr/content/map-view-types.js.map +1 -1
- package/dist/ssr/feedback/prompts.js +22 -8
- package/dist/ssr/feedback/prompts.js.map +1 -1
- package/dist/ssr/inputs/Combobox.js +79 -28
- package/dist/ssr/inputs/Combobox.js.map +1 -1
- package/dist/ssr/inputs/RichSelect.js +47 -28
- package/dist/ssr/inputs/RichSelect.js.map +1 -1
- package/dist/ssr/inputs/TextInput.js.map +1 -1
- package/dist/ssr/internal/keyed-for.js +47 -0
- package/dist/ssr/internal/keyed-for.js.map +1 -0
- package/dist/ssr/layout/AppWorkspace.js +1 -1
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/ScrollArea.js +4 -1
- package/dist/ssr/layout/ScrollArea.js.map +1 -1
- package/dist/ssr/text-editor/engine.js +13 -10
- package/dist/ssr/text-editor/engine.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/content/MapViewFrame.d.ts +1 -1
- package/dist/types/content/MessageRow.d.ts +1 -1
- package/dist/types/content/index.d.ts +2 -1
- package/dist/types/content/map-view-glyphs.d.ts +5 -0
- package/dist/types/content/map-view-live.d.ts +1 -1
- package/dist/types/content/map-view-snapshot.d.ts +13 -0
- package/dist/types/content/map-view-types.d.ts +15 -0
- package/dist/types/feedback/prompts.d.ts +5 -0
- package/dist/types/inputs/Combobox.d.ts +12 -0
- package/dist/types/inputs/TextInput.d.ts +2 -1
- package/dist/types/internal/keyed-for.d.ts +13 -0
- package/dist/types/layout/AppWorkspace.d.ts +4 -1
- package/dist/types/layout/ScrollArea.d.ts +2 -0
- package/dist/types/text-editor/engine.d.ts +1 -1
- package/package.json +4 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ssrStyleProperty as _$ssrStyleProperty } from "solid-js/web";
|
|
1
2
|
import { memo as _$memo } from "solid-js/web";
|
|
2
3
|
import { createComponent as _$createComponent } from "solid-js/web";
|
|
3
4
|
import { ssr as _$ssr } from "solid-js/web";
|
|
@@ -6,14 +7,17 @@ import { mergeProps as _$mergeProps } from "solid-js/web";
|
|
|
6
7
|
import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
|
|
7
8
|
import { escape as _$escape } from "solid-js/web";
|
|
8
9
|
var _tmpl$ = "<i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i>",
|
|
9
|
-
_tmpl$2 = ["<div class=\"k2b-combobox\"", "><div class=\"k2b-combobox__input\"><i class=\"ti ti-search\" aria-hidden=\"true\"></i>", "", "</div><div popover=\"manual\" class=\"k2b-choice-popover\"><div class=\"k2b-choice-options\" role=\"listbox\"", ">", "", "</div
|
|
10
|
+
_tmpl$2 = ["<div class=\"k2b-combobox\"", "><div class=\"k2b-combobox__input\"><i class=\"ti ti-search\" aria-hidden=\"true\"></i>", "", "</div><div popover=\"manual\" class=\"k2b-choice-popover\"><div class=\"k2b-choice-options\" role=\"listbox\"", ">", "", "", "</div>", "</div></div>"],
|
|
10
11
|
_tmpl$3 = "<i class=\"ti ti-chevron-down\" aria-hidden=\"true\"></i>",
|
|
11
|
-
_tmpl$4 = ["<div class=\"k2b-choice-status\"
|
|
12
|
-
_tmpl$5 = ["<div class=\"k2b-choice-status\">", "</div>"],
|
|
13
|
-
_tmpl$6 = ["<
|
|
14
|
-
_tmpl$7 = ["<
|
|
15
|
-
_tmpl$8 = ["<
|
|
16
|
-
|
|
12
|
+
_tmpl$4 = ["<div class=\"k2b-choice-status\">", "</div>"],
|
|
13
|
+
_tmpl$5 = ["<div class=\"k2b-choice-status\" data-tone=\"danger\"><span>", "</span><button type=\"button\">", "</button></div>"],
|
|
14
|
+
_tmpl$6 = ["<div class=\"k2b-choice-loading\" role=\"status\"><span class=\"k2b-sr-only\">", "</span>", "</div>"],
|
|
15
|
+
_tmpl$7 = ["<div class=\"k2b-choice-loading__row\" aria-hidden=\"true\"><span class=\"k2b-skeleton\"></span><span class=\"k2b-skeleton\" style=\"", "\"></span></div>"],
|
|
16
|
+
_tmpl$8 = ["<button type=\"button\" id=\"", "\" class=\"k2b-choice-option\" role=\"option\"", ">", "<span><strong>", "</strong>", "</span></button>"],
|
|
17
|
+
_tmpl$9 = ["<i", " aria-hidden=\"true\"></i>"],
|
|
18
|
+
_tmpl$0 = ["<small>", "</small>"],
|
|
19
|
+
_tmpl$1 = ["<div class=\"k2b-choice-footer\">", "</div>"];
|
|
20
|
+
import { createEffect, createMemo, createSignal, For, on, Show } from "solid-js";
|
|
17
21
|
import { createFieldMeta, Field, fieldControlAria } from "../internal/field";
|
|
18
22
|
import { useUiMessages } from "../intl/messages";
|
|
19
23
|
import { createChoiceLoader, createChoicePopover, nextEnabledChoiceIndex } from "./choice";
|
|
@@ -45,8 +49,11 @@ export function Combobox(props) {
|
|
|
45
49
|
// 150ms keeps typing responsive without hammering the loader.
|
|
46
50
|
() => props.debounceMs ?? 150);
|
|
47
51
|
const popover = createChoicePopover(() => Boolean(props.disabled || props.readOnly));
|
|
48
|
-
|
|
52
|
+
// A host notice or an error replaces the results; hidden results are never selectable.
|
|
53
|
+
const options = () => props.status || loader.error() ? [] : loader.options();
|
|
54
|
+
const optionsById = createMemo(() => new Map(options().map(option => [option.value, option])));
|
|
49
55
|
const focusedOption = () => options()[focusedIndex()];
|
|
56
|
+
const searchable = value => value.trim().length >= (props.minQueryLength ?? 0);
|
|
50
57
|
const focus = index => {
|
|
51
58
|
setFocusedIndex(index);
|
|
52
59
|
optionRefs[index]?.scrollIntoView({
|
|
@@ -56,9 +63,9 @@ export function Combobox(props) {
|
|
|
56
63
|
/** `eager` loads the currently controlled query on open; typing supplies its
|
|
57
64
|
* own query, so opening from `onInput` skips the duplicate request. */
|
|
58
65
|
const open = (eager = true) => {
|
|
59
|
-
if (props.disabled || props.readOnly || popover.open()) return;
|
|
66
|
+
if (props.disabled || props.readOnly || popover.open() || !searchable(query())) return;
|
|
60
67
|
popover.show();
|
|
61
|
-
if (eager) loader.load(query(), true);
|
|
68
|
+
if (eager && !props.status) loader.load(query(), true);
|
|
62
69
|
focus(-1);
|
|
63
70
|
};
|
|
64
71
|
const reset = () => {
|
|
@@ -68,8 +75,14 @@ export function Combobox(props) {
|
|
|
68
75
|
};
|
|
69
76
|
const close = () => {
|
|
70
77
|
popover.hide();
|
|
71
|
-
reset();
|
|
78
|
+
if (!props.freeText) return reset();
|
|
79
|
+
loader.cancel();
|
|
80
|
+
focus(-1);
|
|
72
81
|
};
|
|
82
|
+
createEffect(on(options, list => {
|
|
83
|
+
const index = props.autoHighlight;
|
|
84
|
+
if (index !== undefined && focusedIndex() < 0 && popover.open() && list.length > 0) setFocusedIndex(Math.min(index, list.length - 1));
|
|
85
|
+
}));
|
|
73
86
|
createEffect(() => {
|
|
74
87
|
if (props.readOnly) {
|
|
75
88
|
popover.hide();
|
|
@@ -86,7 +99,10 @@ export function Combobox(props) {
|
|
|
86
99
|
description: option.description,
|
|
87
100
|
icon: option.icon
|
|
88
101
|
});
|
|
89
|
-
|
|
102
|
+
if (props.freeText) {
|
|
103
|
+
loader.cancel();
|
|
104
|
+
focus(-1);
|
|
105
|
+
} else reset();
|
|
90
106
|
inputRef?.focus();
|
|
91
107
|
};
|
|
92
108
|
const onKeyDown = event => {
|
|
@@ -98,7 +114,7 @@ export function Combobox(props) {
|
|
|
98
114
|
// While the results are open, Enter belongs to them: it never submits an enclosing form, even with no option active.
|
|
99
115
|
event.preventDefault();
|
|
100
116
|
const option = options()[focusedIndex()];
|
|
101
|
-
if (option) select(option);
|
|
117
|
+
if (option) select(option);else if (props.freeText) close();
|
|
102
118
|
} else if ((event.key === "Escape" || event.key === "Tab") && popover.open()) {
|
|
103
119
|
// Escape is only ours while the list is open; otherwise it has to reach an
|
|
104
120
|
// enclosing dialog or drawer.
|
|
@@ -148,6 +164,7 @@ export function Combobox(props) {
|
|
|
148
164
|
return props.readOnly ? "true" : undefined;
|
|
149
165
|
},
|
|
150
166
|
role: "combobox",
|
|
167
|
+
autocomplete: "off",
|
|
151
168
|
"aria-autocomplete": "list",
|
|
152
169
|
get ["aria-expanded"]() {
|
|
153
170
|
return popover.open();
|
|
@@ -168,27 +185,61 @@ export function Combobox(props) {
|
|
|
168
185
|
}
|
|
169
186
|
})), _$ssrAttribute("id", _$escape(listboxId, true), false), _$escape(_$createComponent(Show, {
|
|
170
187
|
get when() {
|
|
171
|
-
return
|
|
188
|
+
return props.status;
|
|
189
|
+
},
|
|
190
|
+
children: status => _$ssr(_tmpl$4, _$escape(status()))
|
|
191
|
+
})), _$escape(_$createComponent(Show, {
|
|
192
|
+
get when() {
|
|
193
|
+
return _$memo(() => !!!props.status)() && loader.error();
|
|
172
194
|
},
|
|
173
|
-
children: message => _$ssr(_tmpl$
|
|
195
|
+
children: message => _$ssr(_tmpl$5, _$escape(message()), _$escape(messages().retry))
|
|
174
196
|
})), _$escape(_$createComponent(For, {
|
|
175
197
|
get each() {
|
|
176
|
-
return
|
|
198
|
+
return options().map(option => option.value);
|
|
177
199
|
},
|
|
178
200
|
get fallback() {
|
|
179
|
-
return _$
|
|
201
|
+
return _$createComponent(Show, {
|
|
202
|
+
get when() {
|
|
203
|
+
return _$memo(() => !!!props.status)() && !loader.error();
|
|
204
|
+
},
|
|
205
|
+
get children() {
|
|
206
|
+
return _$createComponent(Show, {
|
|
207
|
+
get when() {
|
|
208
|
+
return loader.loading();
|
|
209
|
+
},
|
|
210
|
+
get fallback() {
|
|
211
|
+
return _$ssr(_tmpl$4, query().length >= 2 ? _$escape(props.empty ?? selectMessages().noResultsFound) : _$escape(selectMessages().typeToSearch));
|
|
212
|
+
},
|
|
213
|
+
get children() {
|
|
214
|
+
return _$ssr(_tmpl$6, _$escape(messages().loading), _$escape(_$createComponent(For, {
|
|
215
|
+
each: [62, 48, 55],
|
|
216
|
+
children: width => _$ssr(_tmpl$7, _$ssrStyleProperty("width:", `${_$escape(width, true)}%`))
|
|
217
|
+
})));
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
children: (id, index) => {
|
|
224
|
+
const first = optionsById().get(id);
|
|
225
|
+
const option = () => optionsById().get(id) ?? first;
|
|
226
|
+
return _$ssr(_tmpl$8, `${_$escape(listboxId, true)}-${_$escape(index(), true)}`, _$ssrAttribute("aria-selected", _$escape(index(), true) === _$escape(focusedIndex(), true), false) + _$ssrAttribute("data-focused", index() === focusedIndex() ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
227
|
+
get when() {
|
|
228
|
+
return option().icon;
|
|
229
|
+
},
|
|
230
|
+
children: icon => _$ssr(_tmpl$9, _$ssrAttribute("class", _$escape(iconClass(icon()), true), false))
|
|
231
|
+
})), _$escape(option().label), _$escape(_$createComponent(Show, {
|
|
232
|
+
get when() {
|
|
233
|
+
return option().description;
|
|
234
|
+
},
|
|
235
|
+
children: description => _$ssr(_tmpl$0, _$escape(description()))
|
|
236
|
+
})));
|
|
237
|
+
}
|
|
238
|
+
})), _$escape(_$createComponent(Show, {
|
|
239
|
+
get when() {
|
|
240
|
+
return props.footer;
|
|
180
241
|
},
|
|
181
|
-
children:
|
|
182
|
-
get when() {
|
|
183
|
-
return option.icon;
|
|
184
|
-
},
|
|
185
|
-
children: icon => _$ssr(_tmpl$7, _$ssrAttribute("class", _$escape(iconClass(icon()), true), false))
|
|
186
|
-
})), _$escape(option.label), _$escape(_$createComponent(Show, {
|
|
187
|
-
get when() {
|
|
188
|
-
return option.description;
|
|
189
|
-
},
|
|
190
|
-
children: description => _$ssr(_tmpl$8, _$escape(description()))
|
|
191
|
-
})))
|
|
242
|
+
children: footer => _$ssr(_tmpl$1, _$escape(footer()))
|
|
192
243
|
})));
|
|
193
244
|
}
|
|
194
245
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["inputs/Combobox.tsx"],"sourcesContent":["import { createEffect, createSignal, For, type JSX, Show } from \"solid-js\";\nimport { createFieldMeta, Field, fieldControlAria } from \"../internal/field\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { type ChoiceOption, createChoiceLoader, createChoicePopover, nextEnabledChoiceIndex } from \"./choice\";\nimport type { FieldProps, MaybeAccessor } from \"./field-contract\";\nimport { resolveMaybeAccessor } from \"./field-contract\";\nimport { useSelectMessages } from \"./select-messages\";\n\nexport type ComboboxOption = { id: string; label: string; description?: string; icon?: string };\nexport type ComboboxProps = FieldProps & {\n readOnly?: boolean;\n query?: MaybeAccessor<string>;\n onQueryChange?: (query: string) => void;\n placeholder?: string;\n fetchData: (query: string, signal: AbortSignal) => Promise<ComboboxOption[]>;\n onSelect: (option: ComboboxOption) => void;\n debounceMs?: number;\n};\n\nconst iconClass = (icon: string) => (icon.split(/\\s+/).includes(\"ti\") ? icon : `ti ${icon}`);\n\nexport function Combobox(props: ComboboxProps): JSX.Element {\n const messages = useUiMessages();\n const selectMessages = useSelectMessages();\n const meta = createFieldMeta(props.id);\n const listboxId = `${meta.controlId}-listbox`;\n const [localQuery, setLocalQuery] = createSignal(\"\");\n const query = () => resolveMaybeAccessor(props.query) ?? localQuery();\n const setQuery = (value: string) => {\n setLocalQuery(value);\n props.onQueryChange?.(value);\n };\n const error = () => resolveMaybeAccessor(props.error);\n const [focusedIndex, setFocusedIndex] = createSignal(-1);\n let inputRef: HTMLInputElement | undefined;\n let optionRefs: HTMLButtonElement[] = [];\n const loader = createChoiceLoader(\n () => async (value, signal) => (await props.fetchData(value, signal)).map((option) => ({ ...option, value: option.id })),\n // Cloud debounces at 200ms and then pads the response with a 200ms minimum\n // load time to stop the spinner flickering. The spinner here sits in the\n // chevron slot and stale results stay visible, so there is nothing to pad —\n // 150ms keeps typing responsive without hammering the loader.\n () => props.debounceMs ?? 150,\n );\n const popover = createChoicePopover(() => Boolean(props.disabled || props.readOnly));\n const options = loader.options;\n const focusedOption = () => options()[focusedIndex()];\n const focus = (index: number) => {\n setFocusedIndex(index);\n optionRefs[index]?.scrollIntoView({ block: \"nearest\" });\n };\n /** `eager` loads the currently controlled query on open; typing supplies its\n * own query, so opening from `onInput` skips the duplicate request. */\n const open = (eager = true) => {\n if (props.disabled || props.readOnly || popover.open()) return;\n popover.show();\n if (eager) loader.load(query(), true);\n focus(-1);\n };\n const reset = () => {\n loader.cancel();\n setQuery(\"\");\n focus(-1);\n };\n const close = () => {\n popover.hide();\n reset();\n };\n createEffect(() => {\n if (props.readOnly) {\n popover.hide();\n loader.cancel();\n setFocusedIndex(-1);\n }\n });\n const select = (option: ChoiceOption<string>) => {\n if (props.disabled || props.readOnly) return;\n popover.hide();\n props.onSelect({ id: option.value, label: option.label, description: option.description, icon: option.icon });\n reset();\n inputRef?.focus();\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (props.disabled || props.readOnly) return;\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n if (!popover.open()) open();\n else focus(nextEnabledChoiceIndex(options(), focusedIndex(), event.key === \"ArrowDown\" ? 1 : -1));\n } else if (event.key === \"Enter\" && popover.open()) {\n // While the results are open, Enter belongs to them: it never submits an enclosing form, even with no option active.\n event.preventDefault();\n const option = options()[focusedIndex()];\n if (option) select(option);\n } else if ((event.key === \"Escape\" || event.key === \"Tab\") && popover.open()) {\n // Escape is only ours while the list is open; otherwise it has to reach an\n // enclosing dialog or drawer.\n if (event.key === \"Escape\") event.preventDefault();\n close();\n }\n };\n\n return (\n <Field\n class={props.class}\n label={props.label}\n description={props.description}\n error={error()}\n meta={meta}\n required={props.required}\n disabled={props.disabled}\n >\n <div class=\"k2b-combobox\" data-invalid={error() ? \"true\" : undefined}>\n <div ref={popover.setTrigger} class=\"k2b-combobox__input\">\n <i class=\"ti ti-search\" aria-hidden=\"true\" />\n {/* A click, typing, or an arrow key opens the list. Focus alone (Tab, a dialog's initial focus) does not, so Escape still closes the enclosing dialog. */}\n <input\n ref={(element) => {\n inputRef = element;\n }}\n id={meta.controlId}\n type=\"text\"\n value={query()}\n placeholder={props.placeholder ?? messages().search}\n disabled={props.disabled}\n readOnly={props.readOnly}\n aria-readonly={props.readOnly ? \"true\" : undefined}\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={popover.open()}\n aria-controls={listboxId}\n aria-activedescendant={focusedOption() ? `${listboxId}-${focusedIndex()}` : undefined}\n {...fieldControlAria(meta, props)}\n onClick={() => open()}\n onInput={(event) => {\n if (props.disabled || props.readOnly) {\n event.currentTarget.value = query();\n return;\n }\n setQuery(event.currentTarget.value);\n open(false);\n loader.load(event.currentTarget.value);\n focus(-1);\n }}\n onKeyDown={onKeyDown}\n />\n <Show when={loader.loading()} fallback={<i class=\"ti ti-chevron-down\" aria-hidden=\"true\" />}>\n <i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\" />\n </Show>\n </div>\n <div ref={popover.setPopover} popover=\"manual\" class=\"k2b-choice-popover\">\n <div class=\"k2b-choice-options\" role=\"listbox\" id={listboxId}>\n <Show when={loader.error()}>\n {(message) => (\n <div class=\"k2b-choice-status\" data-tone=\"danger\">\n <span>{message()}</span>\n <button type=\"button\" onClick={loader.retry}>\n {messages().retry}\n </button>\n </div>\n )}\n </Show>\n <For\n each={loader.error() ? [] : options()}\n fallback={\n <div class=\"k2b-choice-status\">{query().length >= 2 ? selectMessages().noResultsFound : selectMessages().typeToSearch}</div>\n }\n >\n {(option, index) => (\n <button\n ref={(element) => (optionRefs[index()] = element)}\n type=\"button\"\n id={`${listboxId}-${index()}`}\n class=\"k2b-choice-option\"\n role=\"option\"\n aria-selected={index() === focusedIndex()}\n data-focused={index() === focusedIndex() ? \"true\" : undefined}\n onPointerMove={() => focus(index())}\n onPointerDown={(event) => event.preventDefault()}\n onClick={() => select(option)}\n >\n <Show when={option.icon}>{(icon) => <i class={iconClass(icon())} aria-hidden=\"true\" />}</Show>\n <span>\n <strong>{option.label}</strong>\n <Show when={option.description}>{(description) => <small>{description()}</small>}</Show>\n </span>\n </button>\n )}\n </For>\n </div>\n </div>\n </div>\n </Field>\n );\n}\n\nexport default Combobox;\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":[],"sources":["inputs/Combobox.tsx"],"sourcesContent":["import { createEffect, createMemo, createSignal, For, type JSX, on, Show } from \"solid-js\";\nimport { createFieldMeta, Field, fieldControlAria } from \"../internal/field\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { type ChoiceOption, createChoiceLoader, createChoicePopover, nextEnabledChoiceIndex } from \"./choice\";\nimport type { FieldProps, MaybeAccessor } from \"./field-contract\";\nimport { resolveMaybeAccessor } from \"./field-contract\";\nimport { useSelectMessages } from \"./select-messages\";\n\nexport type ComboboxOption = { id: string; label: string; description?: string; icon?: string };\nexport type ComboboxProps = FieldProps & {\n readOnly?: boolean;\n query?: MaybeAccessor<string>;\n onQueryChange?: (query: string) => void;\n placeholder?: string;\n fetchData: (query: string, signal: AbortSignal) => Promise<ComboboxOption[]>;\n onSelect: (option: ComboboxOption) => void;\n debounceMs?: number;\n /** The query is the field's own value: picking or closing keeps the typed text, and Enter without an active option only closes the list. */\n freeText?: boolean;\n /** Below this many characters nothing loads and the list stays closed. */\n minQueryLength?: number;\n /** This result (by position) is active as soon as results arrive, so Enter takes it. */\n autoHighlight?: number;\n /** Replaces the results with a host-owned notice, such as an exhausted quota; nothing loads while it is set. */\n status?: JSX.Element;\n /** Shown instead of the default text when a search finds nothing. */\n empty?: JSX.Element;\n /** One line under the results, such as their source. */\n footer?: JSX.Element;\n};\n\nconst iconClass = (icon: string) => (icon.split(/\\s+/).includes(\"ti\") ? icon : `ti ${icon}`);\n\nexport function Combobox(props: ComboboxProps): JSX.Element {\n const messages = useUiMessages();\n const selectMessages = useSelectMessages();\n const meta = createFieldMeta(props.id);\n const listboxId = `${meta.controlId}-listbox`;\n const [localQuery, setLocalQuery] = createSignal(\"\");\n const query = () => resolveMaybeAccessor(props.query) ?? localQuery();\n const setQuery = (value: string) => {\n setLocalQuery(value);\n props.onQueryChange?.(value);\n };\n const error = () => resolveMaybeAccessor(props.error);\n const [focusedIndex, setFocusedIndex] = createSignal(-1);\n let inputRef: HTMLInputElement | undefined;\n let optionRefs: HTMLButtonElement[] = [];\n const loader = createChoiceLoader(\n () => async (value, signal) => (await props.fetchData(value, signal)).map((option) => ({ ...option, value: option.id })),\n // Cloud debounces at 200ms and then pads the response with a 200ms minimum\n // load time to stop the spinner flickering. The spinner here sits in the\n // chevron slot and stale results stay visible, so there is nothing to pad —\n // 150ms keeps typing responsive without hammering the loader.\n () => props.debounceMs ?? 150,\n );\n const popover = createChoicePopover(() => Boolean(props.disabled || props.readOnly));\n // A host notice or an error replaces the results; hidden results are never selectable.\n const options = () => (props.status || loader.error() ? [] : loader.options());\n const optionsById = createMemo(() => new Map(options().map((option) => [option.value, option])));\n const focusedOption = () => options()[focusedIndex()];\n const searchable = (value: string) => value.trim().length >= (props.minQueryLength ?? 0);\n const focus = (index: number) => {\n setFocusedIndex(index);\n optionRefs[index]?.scrollIntoView({ block: \"nearest\" });\n };\n /** `eager` loads the currently controlled query on open; typing supplies its\n * own query, so opening from `onInput` skips the duplicate request. */\n const open = (eager = true) => {\n if (props.disabled || props.readOnly || popover.open() || !searchable(query())) return;\n popover.show();\n if (eager && !props.status) loader.load(query(), true);\n focus(-1);\n };\n const reset = () => {\n loader.cancel();\n setQuery(\"\");\n focus(-1);\n };\n const close = () => {\n popover.hide();\n if (!props.freeText) return reset();\n loader.cancel();\n focus(-1);\n };\n createEffect(\n on(options, (list) => {\n const index = props.autoHighlight;\n if (index !== undefined && focusedIndex() < 0 && popover.open() && list.length > 0) setFocusedIndex(Math.min(index, list.length - 1));\n }),\n );\n createEffect(() => {\n if (props.readOnly) {\n popover.hide();\n loader.cancel();\n setFocusedIndex(-1);\n }\n });\n const select = (option: ChoiceOption<string>) => {\n if (props.disabled || props.readOnly) return;\n popover.hide();\n props.onSelect({ id: option.value, label: option.label, description: option.description, icon: option.icon });\n if (props.freeText) {\n loader.cancel();\n focus(-1);\n } else reset();\n inputRef?.focus();\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (props.disabled || props.readOnly) return;\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n if (!popover.open()) open();\n else focus(nextEnabledChoiceIndex(options(), focusedIndex(), event.key === \"ArrowDown\" ? 1 : -1));\n } else if (event.key === \"Enter\" && popover.open()) {\n // While the results are open, Enter belongs to them: it never submits an enclosing form, even with no option active.\n event.preventDefault();\n const option = options()[focusedIndex()];\n if (option) select(option);\n else if (props.freeText) close();\n } else if ((event.key === \"Escape\" || event.key === \"Tab\") && popover.open()) {\n // Escape is only ours while the list is open; otherwise it has to reach an\n // enclosing dialog or drawer.\n if (event.key === \"Escape\") event.preventDefault();\n close();\n }\n };\n\n return (\n <Field\n class={props.class}\n label={props.label}\n description={props.description}\n error={error()}\n meta={meta}\n required={props.required}\n disabled={props.disabled}\n >\n <div class=\"k2b-combobox\" data-invalid={error() ? \"true\" : undefined}>\n <div ref={popover.setTrigger} class=\"k2b-combobox__input\">\n <i class=\"ti ti-search\" aria-hidden=\"true\" />\n {/* A click, typing, or an arrow key opens the list. Focus alone (Tab, a dialog's initial focus) does not, so Escape still closes the enclosing dialog. */}\n <input\n ref={(element) => {\n inputRef = element;\n }}\n id={meta.controlId}\n type=\"text\"\n value={query()}\n placeholder={props.placeholder ?? messages().search}\n disabled={props.disabled}\n readOnly={props.readOnly}\n aria-readonly={props.readOnly ? \"true\" : undefined}\n role=\"combobox\"\n autocomplete=\"off\"\n aria-autocomplete=\"list\"\n aria-expanded={popover.open()}\n aria-controls={listboxId}\n aria-activedescendant={focusedOption() ? `${listboxId}-${focusedIndex()}` : undefined}\n {...fieldControlAria(meta, props)}\n onClick={() => open()}\n onInput={(event) => {\n if (props.disabled || props.readOnly) {\n event.currentTarget.value = query();\n return;\n }\n const value = event.currentTarget.value;\n setQuery(value);\n focus(-1);\n if (!searchable(value)) {\n popover.hide();\n loader.cancel();\n return;\n }\n open(false);\n if (!props.status) loader.load(value);\n }}\n onKeyDown={onKeyDown}\n />\n <Show when={loader.loading()} fallback={<i class=\"ti ti-chevron-down\" aria-hidden=\"true\" />}>\n <i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\" />\n </Show>\n </div>\n <div ref={popover.setPopover} popover=\"manual\" class=\"k2b-choice-popover\">\n <div class=\"k2b-choice-options\" role=\"listbox\" id={listboxId}>\n <Show when={props.status}>{(status) => <div class=\"k2b-choice-status\">{status()}</div>}</Show>\n <Show when={!props.status && loader.error()}>\n {(message) => (\n <div class=\"k2b-choice-status\" data-tone=\"danger\">\n <span>{message()}</span>\n <button type=\"button\" onClick={loader.retry}>\n {messages().retry}\n </button>\n </div>\n )}\n </Show>\n {/* Keyed by option ID, not object: each fetch returns new objects, and a row replaced between press and release\n would swallow the click. */}\n <For\n each={options().map((option) => option.value)}\n fallback={\n <Show when={!props.status && !loader.error()}>\n <Show\n when={loader.loading()}\n fallback={\n <div class=\"k2b-choice-status\">\n {query().length >= 2 ? (props.empty ?? selectMessages().noResultsFound) : selectMessages().typeToSearch}\n </div>\n }\n >\n {/* The first search has nothing to keep visible yet: rows of the final size wait in its place. */}\n <div class=\"k2b-choice-loading\" role=\"status\">\n <span class=\"k2b-sr-only\">{messages().loading}</span>\n <For each={[62, 48, 55]}>\n {(width) => (\n <div class=\"k2b-choice-loading__row\" aria-hidden=\"true\">\n <span class=\"k2b-skeleton\" />\n <span class=\"k2b-skeleton\" style={{ width: `${width}%` }} />\n </div>\n )}\n </For>\n </div>\n </Show>\n </Show>\n }\n >\n {(id, index) => {\n const first = optionsById().get(id)!;\n const option = () => optionsById().get(id) ?? first;\n return (\n <button\n ref={(element) => (optionRefs[index()] = element)}\n type=\"button\"\n id={`${listboxId}-${index()}`}\n class=\"k2b-choice-option\"\n role=\"option\"\n aria-selected={index() === focusedIndex()}\n data-focused={index() === focusedIndex() ? \"true\" : undefined}\n onPointerMove={() => focus(index())}\n onPointerDown={(event) => event.preventDefault()}\n onClick={() => select(option())}\n >\n <Show when={option().icon}>{(icon) => <i class={iconClass(icon())} aria-hidden=\"true\" />}</Show>\n <span>\n <strong>{option().label}</strong>\n <Show when={option().description}>{(description) => <small>{description()}</small>}</Show>\n </span>\n </button>\n );\n }}\n </For>\n </div>\n <Show when={props.footer}>{(footer) => <div class=\"k2b-choice-footer\">{footer()}</div>}</Show>\n </div>\n </div>\n </Field>\n );\n}\n\nexport default Combobox;\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -63,6 +63,7 @@ import { useRichSelectMessages } from "./rich-select-messages";
|
|
|
63
63
|
export const RICH_SELECT_SEARCH_FROM = 8;
|
|
64
64
|
const PHONE_QUERY = "(max-width: 47.999rem)";
|
|
65
65
|
const PLACEHOLDER_ROWS = 4;
|
|
66
|
+
const rowKey = row => row.kind === "heading" ? `heading:${row.id}` : `item:${row.item.value}`;
|
|
66
67
|
const isPhone = () => typeof matchMedia === "function" && matchMedia(PHONE_QUERY).matches;
|
|
67
68
|
function RichSelectVisual(props) {
|
|
68
69
|
return _$createComponent(Switch, {
|
|
@@ -254,6 +255,9 @@ export function RichSelect(props) {
|
|
|
254
255
|
});
|
|
255
256
|
return result;
|
|
256
257
|
});
|
|
258
|
+
// Rows are keyed by heading and value, not by object: a host that rebuilds equal items must not replace the option
|
|
259
|
+
// under a press, which would swallow its click.
|
|
260
|
+
const rowsByKey = createMemo(() => new Map(rows().map(row => [rowKey(row), row])));
|
|
257
261
|
const selected = createMemo(() => {
|
|
258
262
|
const current = value();
|
|
259
263
|
return current === null ? undefined : props.items.find(item => item.value === current);
|
|
@@ -584,44 +588,59 @@ export function RichSelect(props) {
|
|
|
584
588
|
get children() {
|
|
585
589
|
return _$createComponent(For, {
|
|
586
590
|
get each() {
|
|
587
|
-
return rows();
|
|
591
|
+
return rows().map(rowKey);
|
|
588
592
|
},
|
|
589
593
|
get fallback() {
|
|
590
594
|
return _$ssr(_tmpl$25, _$escape(messages().noResults));
|
|
591
595
|
},
|
|
592
|
-
children:
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
596
|
+
children: key => {
|
|
597
|
+
const first = rowsByKey().get(key);
|
|
598
|
+
if (first.kind === "heading") {
|
|
599
|
+
const label = () => {
|
|
600
|
+
const current = rowsByKey().get(key);
|
|
601
|
+
return current?.kind === "heading" ? current.label : first.label;
|
|
602
|
+
};
|
|
603
|
+
return _$ssr(_tmpl$26, _$escape(label()));
|
|
604
|
+
}
|
|
605
|
+
const entry = () => {
|
|
606
|
+
const current = rowsByKey().get(key);
|
|
607
|
+
return current?.kind === "item" ? current : first;
|
|
608
|
+
};
|
|
609
|
+
return (// biome-ignore lint/a11y/useKeyWithClickEvents: the trigger or search field handles the keys through aria-activedescendant.
|
|
610
|
+
_$ssr(_tmpl$27, _$ssrAttribute("id", _$escape(optionId(entry().index), true), false), _$ssrAttribute("aria-selected", _$escape(entry().item.value, true) === _$escape(value(), true), false) + _$ssrAttribute("aria-disabled", entry().item.disabledReason ? "true" : _$escape(undefined, true), false), `${_$escape(optionId(entry().index), true)}-label`, _$ssrAttribute("aria-describedby", _$escape([entry().item.disabledReason || entry().item.description ? `${optionId(entry().index)}-line` : "", withPreview() && entry().index === active() ? previewId : ""].filter(Boolean).join(" ") || undefined, true), false) + _$ssrAttribute("data-active", entry().index === active() ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
599
611
|
get when() {
|
|
600
|
-
return
|
|
601
|
-
},
|
|
602
|
-
get fallback() {
|
|
603
|
-
return _$ssr(_tmpl$28);
|
|
612
|
+
return anyVisual();
|
|
604
613
|
},
|
|
605
614
|
get children() {
|
|
606
|
-
return _$createComponent(
|
|
607
|
-
get
|
|
608
|
-
return
|
|
615
|
+
return _$createComponent(Show, {
|
|
616
|
+
get when() {
|
|
617
|
+
return hasVisual(entry().item);
|
|
618
|
+
},
|
|
619
|
+
get fallback() {
|
|
620
|
+
return _$ssr(_tmpl$28);
|
|
621
|
+
},
|
|
622
|
+
get children() {
|
|
623
|
+
return _$createComponent(RichSelectVisual, {
|
|
624
|
+
get item() {
|
|
625
|
+
return entry().item;
|
|
626
|
+
}
|
|
627
|
+
});
|
|
609
628
|
}
|
|
610
629
|
});
|
|
611
630
|
}
|
|
612
|
-
})
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
631
|
+
})), _$escape(_$createComponent(RichSelectRowText, {
|
|
632
|
+
get item() {
|
|
633
|
+
return entry().item;
|
|
634
|
+
},
|
|
635
|
+
get labelId() {
|
|
636
|
+
return `${optionId(entry().index)}-label`;
|
|
637
|
+
},
|
|
638
|
+
get lineId() {
|
|
639
|
+
return `${optionId(entry().index)}-line`;
|
|
640
|
+
}
|
|
641
|
+
})))
|
|
642
|
+
);
|
|
643
|
+
}
|
|
625
644
|
});
|
|
626
645
|
}
|
|
627
646
|
})), _$escape(_$createComponent(Show, {
|