@fluixi-ui/listbox 0.2.0-alpha.18 → 0.2.0-alpha.19
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/index.js +3 -11
- package/dist/listbox.js +338 -267
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/virtual-listbox.js +218 -166
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
import "@fluixi-ui/tokens/css/base.css";
|
|
2
|
-
import "@fluixi-ui/tokens/css/parts/listbox.css";
|
|
3
1
|
import { Listbox, ListboxGroup, ListboxGroupLabel, ListboxOption, ListboxSearch, useListboxContext } from "./listbox.js";
|
|
4
2
|
import { VirtualListbox } from "./virtual-listbox.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
ListboxGroupLabel,
|
|
9
|
-
ListboxOption,
|
|
10
|
-
ListboxSearch,
|
|
11
|
-
VirtualListbox,
|
|
12
|
-
useListboxContext
|
|
13
|
-
};
|
|
3
|
+
import "@fluixi-ui/tokens/css/base.css";
|
|
4
|
+
import "@fluixi-ui/tokens/css/parts/listbox.css";
|
|
5
|
+
export { Listbox, ListboxGroup, ListboxGroupLabel, ListboxOption, ListboxSearch, VirtualListbox, useListboxContext };
|
package/dist/listbox.js
CHANGED
|
@@ -1,281 +1,352 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { createComponent, createNativeElement, delegateEvents, handleRef, insert, mergeProps, splitProps, spread } from "@fluixi/dom";
|
|
2
|
+
import { createEffect, createMemo, createRenderEffect, createSignal, onCleanup } from "@fluixi/reactive/signal";
|
|
3
|
+
import { composeEventHandlers, composeId, createId, createKeyOf, createSafeContext, createValuesEqual, dataAttr } from "@fluixi-ui/kit";
|
|
4
4
|
import { createCollection, createSelectionManager, createTypeahead, getNextEnabledIndex } from "@fluixi-ui/primitives";
|
|
5
5
|
import { fxProps } from "@fluixi-ui/fx";
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
//#region src/listbox.tsx
|
|
7
|
+
delegateEvents([
|
|
8
|
+
"input",
|
|
9
|
+
"click",
|
|
10
|
+
"keydown",
|
|
11
|
+
"focus"
|
|
12
|
+
]);
|
|
13
|
+
var [ListboxProvider, useListboxContext] = createSafeContext("Listbox");
|
|
8
14
|
function toArray(value) {
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
if (value == null) return [];
|
|
16
|
+
return Array.isArray(value) ? value : [value];
|
|
11
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A selectable list of options using the active-descendant model (focus stays
|
|
20
|
+
* on the listbox; the active option is tracked via `aria-activedescendant`).
|
|
21
|
+
* Supports `single` or `multiple` selection, arrow-key navigation that skips
|
|
22
|
+
* disabled options, Home/End, and type-ahead.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* import { Listbox, ListboxOption } from '@fluixi-ui/listbox';
|
|
27
|
+
*
|
|
28
|
+
* <Listbox selectionMode="single" defaultValue="apple" onChange={setFruit}>
|
|
29
|
+
* <ListboxOption value="apple">Apple</ListboxOption>
|
|
30
|
+
* <ListboxOption value="banana">Banana</ListboxOption>
|
|
31
|
+
* <ListboxOption value="cherry" disabled>Cherry</ListboxOption>
|
|
32
|
+
* </Listbox>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
12
35
|
function Listbox(props) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
36
|
+
const [local, others] = splitProps(props, [
|
|
37
|
+
"selectionMode",
|
|
38
|
+
"value",
|
|
39
|
+
"defaultValue",
|
|
40
|
+
"onChange",
|
|
41
|
+
"disabled",
|
|
42
|
+
"disallowEmptySelection",
|
|
43
|
+
"itemToKey",
|
|
44
|
+
"compare",
|
|
45
|
+
"ref",
|
|
46
|
+
"children",
|
|
47
|
+
"onKeyDown",
|
|
48
|
+
"onFocus",
|
|
49
|
+
"fx",
|
|
50
|
+
"style"
|
|
51
|
+
]);
|
|
52
|
+
const keyOf = createKeyOf(() => local.itemToKey);
|
|
53
|
+
const valuesEqual = createValuesEqual(keyOf, () => local.compare);
|
|
54
|
+
const valuesByKey = /* @__PURE__ */ new Map();
|
|
55
|
+
const registerValue = (key, item) => {
|
|
56
|
+
valuesByKey.set(key, item);
|
|
57
|
+
return () => valuesByKey.delete(key);
|
|
58
|
+
};
|
|
59
|
+
const valueFor = (key) => valuesByKey.get(key) ?? key;
|
|
60
|
+
const baseId = createId("listbox");
|
|
61
|
+
const mode = () => local.selectionMode ?? "single";
|
|
62
|
+
const isDisabled = () => local.disabled ?? false;
|
|
63
|
+
const collection = createCollection();
|
|
64
|
+
const selection = createSelectionManager({
|
|
65
|
+
mode: local.selectionMode ?? "single",
|
|
66
|
+
selectedKeys: local.value !== void 0 ? () => toArray(local.value).map(keyOf) : void 0,
|
|
67
|
+
defaultSelectedKeys: toArray(local.defaultValue).map(keyOf),
|
|
68
|
+
disallowEmptySelection: local.disallowEmptySelection,
|
|
69
|
+
onSelectionChange: (keys) => local.onChange?.([...keys].map(valueFor))
|
|
70
|
+
});
|
|
71
|
+
/** The selected values, from the controlled prop or resolved from the keys. */
|
|
72
|
+
const selectedValues = () => {
|
|
73
|
+
if (local.value !== void 0 && local.value !== null) return toArray(local.value);
|
|
74
|
+
return [...selection.selectedKeys()].map(valueFor);
|
|
75
|
+
};
|
|
76
|
+
const isValueSelected = (candidate) => {
|
|
77
|
+
if (!local.compare) return selection.isSelected(keyOf(candidate));
|
|
78
|
+
return selectedValues().some((selected) => valuesEqual(selected, candidate));
|
|
79
|
+
};
|
|
80
|
+
const [activeKey, setActiveKey] = createSignal(void 0);
|
|
81
|
+
const [query, setQuery] = createSignal("");
|
|
82
|
+
const optionId = (key) => composeId(baseId, `option-${key}`);
|
|
83
|
+
const activeIndex = () => collection.items().findIndex((item) => item.value === activeKey());
|
|
84
|
+
const move = (action) => {
|
|
85
|
+
const items = collection.items();
|
|
86
|
+
const next = getNextEnabledIndex(items, activeIndex(), action, true);
|
|
87
|
+
if (next !== -1) setActiveKey(items[next].value);
|
|
88
|
+
};
|
|
89
|
+
const firstMeaningfulKey = () => {
|
|
90
|
+
const selected = selection.firstSelectedKey();
|
|
91
|
+
if (selected !== void 0) return selected;
|
|
92
|
+
return collection.items().find((item) => !item.disabled)?.value;
|
|
93
|
+
};
|
|
94
|
+
const pressActive = () => {
|
|
95
|
+
const key = activeKey();
|
|
96
|
+
if (key !== void 0) selection.toggle(key);
|
|
97
|
+
};
|
|
98
|
+
const onOptionPress = (key) => {
|
|
99
|
+
setActiveKey(key);
|
|
100
|
+
selection.toggle(key);
|
|
101
|
+
};
|
|
102
|
+
const typeahead = createTypeahead({
|
|
103
|
+
getItems: () => collection.items().map((item) => ({
|
|
104
|
+
textValue: item.textValue ?? item.ref.textContent ?? "",
|
|
105
|
+
disabled: item.disabled
|
|
106
|
+
})),
|
|
107
|
+
getActiveIndex: () => activeIndex(),
|
|
108
|
+
onMatch: (index) => {
|
|
109
|
+
const item = collection.items()[index];
|
|
110
|
+
if (item) setActiveKey(item.value);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const onKeyDown = (event) => {
|
|
114
|
+
switch (event.key) {
|
|
115
|
+
case "ArrowDown":
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
move(activeKey() === void 0 ? "first" : "next");
|
|
118
|
+
break;
|
|
119
|
+
case "ArrowUp":
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
move(activeKey() === void 0 ? "last" : "previous");
|
|
122
|
+
break;
|
|
123
|
+
case "Home":
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
move("first");
|
|
126
|
+
break;
|
|
127
|
+
case "End":
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
move("last");
|
|
130
|
+
break;
|
|
131
|
+
case "Enter":
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
pressActive();
|
|
134
|
+
break;
|
|
135
|
+
case " ":
|
|
136
|
+
if (typeahead.buffer() === "") {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
pressActive();
|
|
139
|
+
} else typeahead.onKeyDown(event);
|
|
140
|
+
break;
|
|
141
|
+
default: typeahead.onKeyDown(event);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const onFocus = () => {
|
|
145
|
+
if (activeKey() === void 0) setActiveKey(firstMeaningfulKey());
|
|
146
|
+
};
|
|
147
|
+
createEffect(() => {
|
|
148
|
+
const key = activeKey();
|
|
149
|
+
if (key === void 0) return;
|
|
150
|
+
collection.items().find((entry) => entry.value === key)?.ref.scrollIntoView?.({ block: "nearest" });
|
|
151
|
+
});
|
|
152
|
+
const context = {
|
|
153
|
+
selection,
|
|
154
|
+
keyOf,
|
|
155
|
+
isValueSelected,
|
|
156
|
+
registerValue,
|
|
157
|
+
collection,
|
|
158
|
+
activeKey,
|
|
159
|
+
setActiveKey,
|
|
160
|
+
optionId,
|
|
161
|
+
isDisabled,
|
|
162
|
+
onOptionPress,
|
|
163
|
+
query,
|
|
164
|
+
setQuery
|
|
165
|
+
};
|
|
166
|
+
createEffect(() => {
|
|
167
|
+
query();
|
|
168
|
+
const key = activeKey();
|
|
169
|
+
if (key === void 0) return;
|
|
170
|
+
const items = collection.items();
|
|
171
|
+
if (!items.some((item) => item.value === key)) setActiveKey(items.find((item) => !item.disabled)?.value);
|
|
172
|
+
});
|
|
173
|
+
return createMemo(() => createComponent(ListboxProvider, {
|
|
174
|
+
get value() {
|
|
175
|
+
return context;
|
|
176
|
+
},
|
|
177
|
+
get children() {
|
|
178
|
+
return (() => {
|
|
179
|
+
const _el$ = createNativeElement("div");
|
|
180
|
+
spread({
|
|
181
|
+
element: _el$,
|
|
182
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
183
|
+
ref: (node) => handleRef(local.ref, node),
|
|
184
|
+
role: "listbox",
|
|
185
|
+
id: baseId,
|
|
186
|
+
tabindex: () => isDisabled() ? -1 : 0,
|
|
187
|
+
"aria-multiselectable": () => mode() === "multiple" ? "true" : void 0,
|
|
188
|
+
"aria-disabled": () => isDisabled() ? "true" : void 0,
|
|
189
|
+
"aria-activedescendant": () => activeKey() !== void 0 ? optionId(activeKey()) : void 0,
|
|
190
|
+
"data-disabled": () => dataAttr(isDisabled()),
|
|
191
|
+
onKeyDown: composeEventHandlers([local.onKeyDown, (event) => {
|
|
192
|
+
if (!isDisabled()) onKeyDown(event);
|
|
193
|
+
}]),
|
|
194
|
+
onFocus: composeEventHandlers([local.onFocus, onFocus])
|
|
195
|
+
})
|
|
196
|
+
});
|
|
197
|
+
insert(_el$, local.children, null);
|
|
198
|
+
return _el$;
|
|
199
|
+
})();
|
|
200
|
+
}
|
|
201
|
+
}));
|
|
177
202
|
}
|
|
203
|
+
/** A selectable option within a {@link Listbox}, identified by `value`. */
|
|
178
204
|
function ListboxOption(props) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
205
|
+
const ctx = useListboxContext();
|
|
206
|
+
const [local, others] = splitProps(props, [
|
|
207
|
+
"value",
|
|
208
|
+
"textValue",
|
|
209
|
+
"disabled",
|
|
210
|
+
"onClick",
|
|
211
|
+
"ref",
|
|
212
|
+
"children",
|
|
213
|
+
"fx",
|
|
214
|
+
"style"
|
|
215
|
+
]);
|
|
216
|
+
const key = () => ctx.keyOf(local.value);
|
|
217
|
+
const isDisabled = () => local.disabled ?? false;
|
|
218
|
+
const isSelected = () => ctx.isValueSelected(local.value);
|
|
219
|
+
const isActive = () => ctx.activeKey() === key();
|
|
220
|
+
const [node, setNode] = createSignal(void 0);
|
|
221
|
+
const label = () => local.textValue ?? (typeof local.children === "string" ? local.children : void 0) ?? node()?.textContent ?? key();
|
|
222
|
+
const isMatch = () => {
|
|
223
|
+
const q = ctx.query().trim().toLowerCase();
|
|
224
|
+
return q === "" || label().toLowerCase().includes(q);
|
|
225
|
+
};
|
|
226
|
+
const setRef = (element) => {
|
|
227
|
+
setNode(element);
|
|
228
|
+
handleRef(local.ref, element);
|
|
229
|
+
};
|
|
230
|
+
createRenderEffect(() => {
|
|
231
|
+
const element = node();
|
|
232
|
+
if (!element || !isMatch()) return;
|
|
233
|
+
const remove = ctx.collection.register({
|
|
234
|
+
ref: element,
|
|
235
|
+
value: key(),
|
|
236
|
+
textValue: local.textValue,
|
|
237
|
+
disabled: local.disabled
|
|
238
|
+
});
|
|
239
|
+
onCleanup(remove);
|
|
240
|
+
onCleanup(ctx.registerValue(key(), local.value));
|
|
241
|
+
});
|
|
242
|
+
return (() => {
|
|
243
|
+
const _el$ = createNativeElement("div");
|
|
244
|
+
spread({
|
|
245
|
+
element: _el$,
|
|
246
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
247
|
+
ref: setRef,
|
|
248
|
+
role: "option",
|
|
249
|
+
id: () => ctx.optionId(key()),
|
|
250
|
+
hidden: () => !isMatch(),
|
|
251
|
+
"aria-selected": () => isSelected() ? "true" : "false",
|
|
252
|
+
"aria-disabled": () => isDisabled() ? "true" : void 0,
|
|
253
|
+
"data-selected": () => dataAttr(isSelected()),
|
|
254
|
+
"data-active": () => dataAttr(isActive()),
|
|
255
|
+
"data-disabled": () => dataAttr(isDisabled()),
|
|
256
|
+
onClick: composeEventHandlers([local.onClick, () => {
|
|
257
|
+
if (!isDisabled()) ctx.onOptionPress(key());
|
|
258
|
+
}]),
|
|
259
|
+
onPointerMove: () => {
|
|
260
|
+
if (!isDisabled()) ctx.setActiveKey(key());
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
insert(_el$, local.children, null);
|
|
265
|
+
return _el$;
|
|
266
|
+
})();
|
|
234
267
|
}
|
|
268
|
+
/** Groups related options under a shared {@link ListboxGroupLabel}. */
|
|
235
269
|
function ListboxGroup(props) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
270
|
+
const [local, others] = splitProps(props, [
|
|
271
|
+
"ref",
|
|
272
|
+
"children",
|
|
273
|
+
"fx",
|
|
274
|
+
"style"
|
|
275
|
+
]);
|
|
276
|
+
const groupId = createId("listbox-group");
|
|
277
|
+
return (() => {
|
|
278
|
+
const _el$ = createNativeElement("div");
|
|
279
|
+
spread({
|
|
280
|
+
element: _el$,
|
|
281
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
282
|
+
ref: (node) => handleRef(local.ref, node),
|
|
283
|
+
role: "group",
|
|
284
|
+
"aria-labelledby": () => composeId(groupId, "label"),
|
|
285
|
+
"data-listbox-group": ""
|
|
286
|
+
})
|
|
287
|
+
});
|
|
288
|
+
insert(_el$, createMemo(() => createComponent(ListboxGroupIdProvider, {
|
|
289
|
+
get value() {
|
|
290
|
+
return groupId;
|
|
291
|
+
},
|
|
292
|
+
get children() {
|
|
293
|
+
return local.children;
|
|
294
|
+
}
|
|
295
|
+
})), null);
|
|
296
|
+
return _el$;
|
|
297
|
+
})();
|
|
251
298
|
}
|
|
252
|
-
|
|
299
|
+
var [ListboxGroupIdProvider, useListboxGroupId] = createSafeContext("ListboxGroup");
|
|
300
|
+
/**
|
|
301
|
+
* A search box that filters the {@link Listbox}'s options by their text. Options
|
|
302
|
+
* that don't match are hidden and skipped by keyboard navigation. Place it above
|
|
303
|
+
* the options.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```tsx
|
|
307
|
+
* <Listbox>
|
|
308
|
+
* <ListboxSearch placeholder="Search…" />
|
|
309
|
+
* <ListboxOption value="apple">Apple</ListboxOption>
|
|
310
|
+
* </Listbox>
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
253
313
|
function ListboxSearch(props) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
314
|
+
const ctx = useListboxContext();
|
|
315
|
+
return (() => {
|
|
316
|
+
const _el$ = createNativeElement("input");
|
|
317
|
+
spread({
|
|
318
|
+
element: _el$,
|
|
319
|
+
props: mergeProps(props, {
|
|
320
|
+
type: "text",
|
|
321
|
+
"data-listbox-search": "",
|
|
322
|
+
"aria-label": "Search options",
|
|
323
|
+
value: () => ctx.query(),
|
|
324
|
+
onInput: (event) => ctx.setQuery(event.target.value)
|
|
325
|
+
})
|
|
326
|
+
});
|
|
327
|
+
return _el$;
|
|
328
|
+
})();
|
|
260
329
|
}
|
|
330
|
+
/** The heading for a {@link ListboxGroup}, linked via `aria-labelledby`. */
|
|
261
331
|
function ListboxGroupLabel(props) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
332
|
+
const groupId = useListboxGroupId();
|
|
333
|
+
const [local, others] = splitProps(props, [
|
|
334
|
+
"children",
|
|
335
|
+
"fx",
|
|
336
|
+
"style"
|
|
337
|
+
]);
|
|
338
|
+
return (() => {
|
|
339
|
+
const _el$ = createNativeElement("div");
|
|
340
|
+
spread({
|
|
341
|
+
element: _el$,
|
|
342
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
343
|
+
id: () => composeId(groupId, "label"),
|
|
344
|
+
"data-listbox-group-label": ""
|
|
345
|
+
})
|
|
346
|
+
});
|
|
347
|
+
insert(_el$, local.children, null);
|
|
348
|
+
return _el$;
|
|
349
|
+
})();
|
|
273
350
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
ListboxGroup,
|
|
277
|
-
ListboxGroupLabel,
|
|
278
|
-
ListboxOption,
|
|
279
|
-
ListboxSearch,
|
|
280
|
-
useListboxContext
|
|
281
|
-
};
|
|
351
|
+
//#endregion
|
|
352
|
+
export { Listbox, ListboxGroup, ListboxGroupLabel, ListboxOption, ListboxSearch, useListboxContext };
|