@astryxdesign/core 0.3.0-canary.98ea85c → 0.3.0-canary.b26c2b2
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/DropdownMenu/DropdownMenu.d.ts +5 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +46 -7
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +2 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +75 -20
- package/dist/Selector/hooks.d.ts +14 -2
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +19 -21
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +3 -3
- package/dist/hooks/useTypeahead.d.ts +3 -2
- package/dist/hooks/useTypeahead.d.ts.map +1 -1
- package/dist/hooks/useTypeahead.js +19 -7
- package/package.json +2 -2
- package/src/CommandPalette/CommandPalette.test.tsx +29 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +214 -4
- package/src/DropdownMenu/DropdownMenu.tsx +74 -30
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +44 -14
- package/src/MoreMenu/MoreMenu.test.tsx +28 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +1 -0
- package/src/MultiSelector/MultiSelector.test.tsx +55 -0
- package/src/MultiSelector/MultiSelector.tsx +1 -0
- package/src/Selector/Selector.doc.mjs +4 -3
- package/src/Selector/Selector.test.tsx +591 -3
- package/src/Selector/Selector.tsx +83 -22
- package/src/Selector/hooks.ts +32 -29
- package/src/hooks/useTypeahead.test.tsx +150 -0
- package/src/hooks/useTypeahead.ts +24 -9
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Both modes use useListFocus for DOM-based keyboard navigation.
|
|
12
12
|
*
|
|
13
|
+
* Initial focus on open follows the input modality: a keyboard open
|
|
14
|
+
* (Enter / Space / ArrowDown on the trigger) focuses the first enabled item
|
|
15
|
+
* (APG menu-button); a pointer open focuses the menu container itself so no
|
|
16
|
+
* item reads as pre-selected, and the first ArrowDown then moves to item 1.
|
|
17
|
+
*
|
|
13
18
|
* SYNC: When modified, update these files to stay in sync:
|
|
14
19
|
* - /packages/core/src/DropdownMenu/DropdownMenu.doc.mjs
|
|
15
20
|
* - /packages/core/src/DropdownMenu/DropdownMenu.test.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAS,KAAK,WAAW,EAAC,MAAM,WAAW,CAAC;AAInD,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AAa9D,OAAO,KAAK,EAAC,cAAc,EAAE,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAQtE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAyC5C;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAChD,qBAAqB,EACrB,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAC9C;IACC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,kBAAkB,GAC5B,oBAAoB,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAMnE,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;AAEnE,UAAU,qBAAsB,SAAQ,SAAS;IAC/C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,qBAAsB,SAAQ,qBAAqB;IAC3D,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,UAAU,yBAA0B,SAAQ,qBAAqB;IAC/D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,MAAM,iBAAiB,GAC3B,qBAAqB,GAAG,yBAAyB,CAAC;AA+BpD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,gBAAgB,EAC5B,YAAY,EACZ,SAAS,EACT,OAAO,EACP,UAAiB,EACjB,SAAmB,EACnB,SAAmB,EACnB,SAAS,EACT,KAAK,EACL,MAAM,EACN,aAAa,EAAE,MAAM,EACrB,GAAG,KAAK,EACT,EAAE,iBAAiB,qBA+TnB;yBA7Ue,YAAY"}
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Both modes use useListFocus for DOM-based keyboard navigation.
|
|
16
16
|
*
|
|
17
|
+
* Initial focus on open follows the input modality: a keyboard open
|
|
18
|
+
* (Enter / Space / ArrowDown on the trigger) focuses the first enabled item
|
|
19
|
+
* (APG menu-button); a pointer open focuses the menu container itself so no
|
|
20
|
+
* item reads as pre-selected, and the first ArrowDown then moves to item 1.
|
|
21
|
+
*
|
|
17
22
|
* SYNC: When modified, update these files to stay in sync:
|
|
18
23
|
* - /packages/core/src/DropdownMenu/DropdownMenu.doc.mjs
|
|
19
24
|
* - /packages/core/src/DropdownMenu/DropdownMenu.test.tsx
|
|
@@ -179,6 +184,13 @@ export function DropdownMenu({
|
|
|
179
184
|
// Defer item focus until the layer has committed open, so focus restore
|
|
180
185
|
// captures the trigger instead of the first menu item.
|
|
181
186
|
const shouldFocusOnOpenRef = useRef(false);
|
|
187
|
+
|
|
188
|
+
// How the next open was initiated. Keyboard (and programmatic) opens focus
|
|
189
|
+
// the first enabled item per the APG menu-button pattern; pointer opens
|
|
190
|
+
// focus the menu container instead, so no item is visually highlighted as
|
|
191
|
+
// if pre-selected (#4477). Reset to 'keyboard' after every open so
|
|
192
|
+
// programmatic controlled opens keep the item-focus behavior.
|
|
193
|
+
const openModalityRef = useRef('keyboard');
|
|
182
194
|
const handleLayerShow = useCallback(() => {
|
|
183
195
|
onOpenChange?.(true);
|
|
184
196
|
if (!isControlled) {
|
|
@@ -238,14 +250,26 @@ export function DropdownMenu({
|
|
|
238
250
|
}
|
|
239
251
|
}, [controlledIsOpen, isControlled, popover]);
|
|
240
252
|
|
|
241
|
-
// Move focus into the menu only after the layer has committed open
|
|
253
|
+
// Move focus into the menu only after the layer has committed open,
|
|
254
|
+
// honoring the input modality: keyboard (and programmatic) opens land on
|
|
255
|
+
// the first enabled item per the APG menu-button pattern; pointer opens
|
|
256
|
+
// focus the menu container itself (tabIndex={-1}) so no item is
|
|
257
|
+
// highlighted as if pre-selected (#4477). Container focus keeps arrows,
|
|
258
|
+
// typeahead, Escape and Tab in the menu's onKeyDown, and is also the
|
|
259
|
+
// fallback when no item is focusable (e.g. all disabled), mirroring the
|
|
260
|
+
// submenu flyout fallback.
|
|
242
261
|
useEffect(() => {
|
|
243
262
|
if (!popover.isOpen || !shouldFocusOnOpenRef.current) {
|
|
244
263
|
return;
|
|
245
264
|
}
|
|
246
265
|
shouldFocusOnOpenRef.current = false;
|
|
247
|
-
requestAnimationFrame(() =>
|
|
248
|
-
|
|
266
|
+
requestAnimationFrame(() => {
|
|
267
|
+
if (openModalityRef.current === 'pointer' || !focusFirst()) {
|
|
268
|
+
listRef.current?.focus();
|
|
269
|
+
}
|
|
270
|
+
openModalityRef.current = 'keyboard';
|
|
271
|
+
});
|
|
272
|
+
}, [popover.isOpen, focusFirst, listRef]);
|
|
249
273
|
|
|
250
274
|
// Extend useListFocus with Enter/Space activation + typeahead
|
|
251
275
|
const listKeyDown = useCallback(e => {
|
|
@@ -278,25 +302,34 @@ export function DropdownMenu({
|
|
|
278
302
|
}
|
|
279
303
|
listNavKeyDown(e);
|
|
280
304
|
}, [listNavKeyDown, closeMenu, typeahead, ownsEvent]);
|
|
281
|
-
const openAndFocus = useCallback(() => {
|
|
305
|
+
const openAndFocus = useCallback((modality = 'keyboard') => {
|
|
306
|
+
openModalityRef.current = modality;
|
|
282
307
|
shouldFocusOnOpenRef.current = true;
|
|
283
308
|
popover.show();
|
|
284
309
|
}, [popover]);
|
|
285
|
-
const handleButtonClick = useCallback(
|
|
310
|
+
const handleButtonClick = useCallback(e => {
|
|
286
311
|
// If the menu was just closed by light dismiss (e.g. iOS Safari fires
|
|
287
312
|
// pointerdown → hide before the trigger's click), the click would
|
|
288
|
-
// otherwise immediately re-open it. Short-circuit within the guard
|
|
313
|
+
// otherwise immediately re-open it. Short-circuit within the guard
|
|
314
|
+
// window.
|
|
289
315
|
if (Date.now() - lastHideTimeRef.current < 50) {
|
|
290
316
|
return;
|
|
291
317
|
}
|
|
292
318
|
onClick?.();
|
|
319
|
+
// detail === 0 marks a synthesized click (screen reader / AT
|
|
320
|
+
// activation): treat it as keyboard so those users still land on the
|
|
321
|
+
// first item. Real pointer clicks report detail >= 1.
|
|
322
|
+
const modality = e.detail === 0 ? 'keyboard' : 'pointer';
|
|
293
323
|
if (isControlled) {
|
|
324
|
+
if (!controlledIsOpen) {
|
|
325
|
+
openModalityRef.current = modality;
|
|
326
|
+
}
|
|
294
327
|
onOpenChange?.(!controlledIsOpen);
|
|
295
328
|
} else {
|
|
296
329
|
if (popover.isOpen) {
|
|
297
330
|
popover.hide();
|
|
298
331
|
} else {
|
|
299
|
-
openAndFocus();
|
|
332
|
+
openAndFocus(modality);
|
|
300
333
|
}
|
|
301
334
|
}
|
|
302
335
|
}, [onClick, isControlled, onOpenChange, controlledIsOpen, popover, openAndFocus]);
|
|
@@ -356,6 +389,12 @@ export function DropdownMenu({
|
|
|
356
389
|
ref: listRef,
|
|
357
390
|
id: menuId,
|
|
358
391
|
role: "menu"
|
|
392
|
+
// Focus target for pointer opens (not in the Tab order): holding
|
|
393
|
+
// focus on the container keeps key events (arrows, typeahead,
|
|
394
|
+
// Escape, Tab) inside the menu without highlighting any item.
|
|
395
|
+
// Mirrors the DropdownMenuSubMenu flyout container.
|
|
396
|
+
,
|
|
397
|
+
tabIndex: -1
|
|
359
398
|
// Give the menu an accessible name from its trigger's label, so
|
|
360
399
|
// screen readers announce e.g. "Actions menu" rather than an unnamed
|
|
361
400
|
// menu (menus-13).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiSelector.d.ts","sourceRoot":"","sources":["../../src/MultiSelector/MultiSelector.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"MultiSelector.d.ts","sourceRoot":"","sources":["../../src/MultiSelector/MultiSelector.tsx"],"names":[],"mappings":"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"}
|
|
@@ -842,7 +842,8 @@ export function MultiSelector({
|
|
|
842
842
|
...mergeProps(themeProps('multi-selector', {
|
|
843
843
|
variant,
|
|
844
844
|
size,
|
|
845
|
-
status: status?.type ?? null
|
|
845
|
+
status: status?.type ?? null,
|
|
846
|
+
disabled: isDisabled ? 'disabled' : null
|
|
846
847
|
}), stylex.props(inputWrapperStyles.base, styles.triggerContainer, sizeStyles[size], variant === 'ghost' && styles.triggerGhost, isDisabled && inputWrapperStyles.disabled, variant === 'ghost' && isDisabled && styles.triggerGhostDisabled, optimisticValue.length === 0 && styles.triggerPlaceholder, variant !== 'ghost' && status && inputStatusBorderStyles[status.type], variant !== 'ghost' && status && !isDisabled && inputStatusHoverShadowStyles[status.type], variant !== 'ghost' && inputGroup && groupStyles.inGroup, xstyle), className, style),
|
|
847
848
|
children: [startIcon && renderIconSlot(startIcon, {
|
|
848
849
|
size: 'sm',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Selector.d.ts","sourceRoot":"","sources":["../../src/Selector/Selector.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EASZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAE5D,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAC;AAgBtD,OAAO,KAAK,EAAC,kBAAkB,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"Selector.d.ts","sourceRoot":"","sources":["../../src/Selector/Selector.tsx"],"names":[],"mappings":"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"}
|
|
@@ -31,6 +31,7 @@ import "../theme/tokens.stylex.js";
|
|
|
31
31
|
import { colorVars, sizeVars, spacingVars, radiusVars, durationVars, easeVars, typographyVars, fontWeightVars, typeScaleVars, borderVars } from "../theme/tokens.stylex.js";
|
|
32
32
|
import { isOptionData, isDivider, isSection, normalizeOption, getSelectableOptions } from "./utils.js";
|
|
33
33
|
import { useCombobox, useSelectedItemOffset } from "./hooks.js";
|
|
34
|
+
import { useTypeahead } from "../hooks/useTypeahead.js";
|
|
34
35
|
import { SelectorOption } from "./SelectorOption.js";
|
|
35
36
|
import { getInputARIA, mergeProps } from "../utils/index.js";
|
|
36
37
|
import { useSize } from "../SizeContext/SizeContext.js";
|
|
@@ -304,6 +305,7 @@ export function Selector(props) {
|
|
|
304
305
|
const [, startTransition] = useTransition();
|
|
305
306
|
const [optimisticValue, setOptimisticValue] = useOptimistic(normalizedValue);
|
|
306
307
|
const isBusy = isLoading || optimisticValue !== normalizedValue;
|
|
308
|
+
const announce = useAnnounce();
|
|
307
309
|
|
|
308
310
|
// Disabled-reason tooltip. Disabled controls swallow pointer events, so the
|
|
309
311
|
// tooltip listeners attach to the trigger container (which already exists)
|
|
@@ -344,15 +346,15 @@ export function Selector(props) {
|
|
|
344
346
|
// Ref for listbox to measure selected item position
|
|
345
347
|
const listboxRef = useRef(null);
|
|
346
348
|
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
const announce = useAnnounce();
|
|
349
|
+
// Typeahead is defined below (it needs the popover), but closing and clearing
|
|
350
|
+
// must drop its pending buffer — otherwise a stale prefix survives the reset
|
|
351
|
+
// window and poisons the next keystroke ("Dog" then "c" would search "dc").
|
|
352
|
+
const resetTypeaheadRef = useRef(() => {});
|
|
352
353
|
|
|
353
354
|
// Layer for dropdown positioning
|
|
354
355
|
const handleLayerHide = useCallback(() => {
|
|
355
356
|
setSearchQuery('');
|
|
357
|
+
resetTypeaheadRef.current();
|
|
356
358
|
// Clear any lingering result count when the popover closes so stale status
|
|
357
359
|
// text does not linger in the a11y tree.
|
|
358
360
|
announce('');
|
|
@@ -415,6 +417,7 @@ export function Selector(props) {
|
|
|
415
417
|
// Clear the current value. Shared by the clear button and the keyboard
|
|
416
418
|
// Delete/Backspace path so clearing is reachable without a mouse.
|
|
417
419
|
const clearValue = useCallback(() => {
|
|
420
|
+
resetTypeaheadRef.current();
|
|
418
421
|
onChange?.(null);
|
|
419
422
|
if (changeAction) {
|
|
420
423
|
startTransition(async () => {
|
|
@@ -424,10 +427,25 @@ export function Selector(props) {
|
|
|
424
427
|
}
|
|
425
428
|
}, [onChange, changeAction, startTransition, setOptimisticValue]);
|
|
426
429
|
|
|
427
|
-
//
|
|
430
|
+
// Type-to-find appends to the query rather than replacing it: characters
|
|
431
|
+
// typed before focus reaches the search input must not be dropped.
|
|
432
|
+
const appendSearchQuery = useCallback(char => {
|
|
433
|
+
setSearchQuery(query => query + char);
|
|
434
|
+
}, []);
|
|
435
|
+
const commitValue = useCallback(newValue => {
|
|
436
|
+
onChange?.(newValue);
|
|
437
|
+
if (changeAction) {
|
|
438
|
+
startTransition(async () => {
|
|
439
|
+
setOptimisticValue(newValue);
|
|
440
|
+
await changeAction(newValue);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}, [onChange, changeAction, startTransition, setOptimisticValue]);
|
|
444
|
+
|
|
445
|
+
// Selector behavior (keyboard nav, selection)
|
|
428
446
|
const {
|
|
429
447
|
highlightedIndex,
|
|
430
|
-
setHighlightedIndex
|
|
448
|
+
setHighlightedIndex,
|
|
431
449
|
getItemId,
|
|
432
450
|
onTriggerClick,
|
|
433
451
|
onKeyDown,
|
|
@@ -435,7 +453,11 @@ export function Selector(props) {
|
|
|
435
453
|
onItemMouseEnter
|
|
436
454
|
} = useCombobox({
|
|
437
455
|
selectableItems: filteredItems,
|
|
438
|
-
value:
|
|
456
|
+
// The optimistic value, not the raw prop: with a pending changeAction the
|
|
457
|
+
// prop still holds the old selection, so the popup would open with the
|
|
458
|
+
// highlight on it and Delete/Backspace could clear a value the action has
|
|
459
|
+
// already replaced.
|
|
460
|
+
value: optimisticValue,
|
|
439
461
|
isDisabled,
|
|
440
462
|
isOpen: popover.isOpen,
|
|
441
463
|
hasSearch,
|
|
@@ -443,24 +465,56 @@ export function Selector(props) {
|
|
|
443
465
|
popover.show();
|
|
444
466
|
if (hasSearch) {
|
|
445
467
|
requestAnimationFrame(() => {
|
|
446
|
-
searchRef.current
|
|
468
|
+
const input = searchRef.current;
|
|
469
|
+
if (input) {
|
|
470
|
+
input.focus();
|
|
471
|
+
// When typing seeded the query, place the caret after it so the
|
|
472
|
+
// user keeps typing where they left off.
|
|
473
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
474
|
+
}
|
|
447
475
|
});
|
|
448
476
|
}
|
|
449
477
|
}, [popover, hasSearch]),
|
|
450
478
|
onClose: popover.hide,
|
|
451
|
-
onSelect:
|
|
452
|
-
onChange?.(newValue);
|
|
453
|
-
if (changeAction) {
|
|
454
|
-
startTransition(async () => {
|
|
455
|
-
setOptimisticValue(newValue);
|
|
456
|
-
await changeAction(newValue);
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
}, [onChange, changeAction, startTransition, setOptimisticValue]),
|
|
479
|
+
onSelect: commitValue,
|
|
460
480
|
onClear: hasClear ? clearValue : undefined,
|
|
481
|
+
onSearchSeed: appendSearchQuery,
|
|
461
482
|
listboxId
|
|
462
483
|
});
|
|
463
484
|
|
|
485
|
+
// Type-to-select, shared with the other collections (menus, listboxes).
|
|
486
|
+
// Open, it walks the highlight — aria-activedescendant announces each match.
|
|
487
|
+
// Closed, it commits the match like a native select, which changes the value
|
|
488
|
+
// without opening the popup or moving focus, so nothing else would prompt
|
|
489
|
+
// assistive tech to re-read the trigger: announce it explicitly.
|
|
490
|
+
const typeahead = useTypeahead({
|
|
491
|
+
getItemLabels: () => selectableItems.map(item => item.label ?? item.value),
|
|
492
|
+
isDisabled: index => selectableItems[index]?.disabled === true,
|
|
493
|
+
// Cycle onward from the highlight when open, from the committed selection
|
|
494
|
+
// when closed — the optimistic one, so a pending changeAction cannot strand
|
|
495
|
+
// cycling on the first match. -1 means nothing is selected or highlighted,
|
|
496
|
+
// which the hook reads as "search from the top".
|
|
497
|
+
getCurrentIndex: () => popover.isOpen ? highlightedIndex : selectedItemIndex,
|
|
498
|
+
onMatch: index => {
|
|
499
|
+
const item = selectableItems[index];
|
|
500
|
+
if (popover.isOpen) {
|
|
501
|
+
setHighlightedIndex(index);
|
|
502
|
+
} else if (item.value !== optimisticValue) {
|
|
503
|
+
commitValue(item.value);
|
|
504
|
+
announce(item.label ?? item.value);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
resetTypeaheadRef.current = typeahead.reset;
|
|
509
|
+
const handleTriggerKeyDown = useCallback(e => {
|
|
510
|
+
// With hasSearch the query input owns typing, so type-to-select is off.
|
|
511
|
+
if (!isDisabled && !hasSearch && typeahead.onKeyDown(e)) {
|
|
512
|
+
e.preventDefault();
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
onKeyDown(e);
|
|
516
|
+
}, [isDisabled, hasSearch, typeahead, onKeyDown]);
|
|
517
|
+
|
|
464
518
|
// Keep the highlighted option visible during keyboard navigation. The
|
|
465
519
|
// listbox is a fixed-height scroll container, so without this the virtual
|
|
466
520
|
// cursor walks off-screen once navigation passes the visible window. Mirrors
|
|
@@ -674,7 +728,8 @@ export function Selector(props) {
|
|
|
674
728
|
...mergeProps(themeProps('selector', {
|
|
675
729
|
variant,
|
|
676
730
|
size,
|
|
677
|
-
status: status?.type ?? null
|
|
731
|
+
status: status?.type ?? null,
|
|
732
|
+
disabled: isDisabled ? 'disabled' : null
|
|
678
733
|
}), stylex.props(inputWrapperStyles.base, styles.triggerContainer, sizeStyles[size], variant === 'ghost' && styles.triggerGhost, isDisabled && inputWrapperStyles.disabled, variant === 'ghost' && isDisabled && styles.triggerGhostDisabled, !selectedItem && styles.triggerPlaceholder, variant !== 'ghost' && status && inputStatusBorderStyles[status.type], variant !== 'ghost' && status && !isDisabled && inputStatusHoverShadowStyles[status.type], variant !== 'ghost' && inputGroup && groupStyles.inGroup, xstyle), className, style),
|
|
679
734
|
children: [startIcon && renderIconSlot(startIcon, {
|
|
680
735
|
size: 'sm',
|
|
@@ -707,7 +762,7 @@ export function Selector(props) {
|
|
|
707
762
|
,
|
|
708
763
|
disabled: isDisabled && !showsDisabledMessage,
|
|
709
764
|
"aria-disabled": showsDisabledMessage ? 'true' : undefined,
|
|
710
|
-
onKeyDown:
|
|
765
|
+
onKeyDown: handleTriggerKeyDown,
|
|
711
766
|
tabIndex: isDisabled && !showsDisabledMessage ? -1 : 0,
|
|
712
767
|
...{
|
|
713
768
|
className: "x78zum5 x6s0dn4 x1qughib x1txdalj x1iyjqo2 xs83m0k x1r8uery xeuugli x1717udv x1ghz6dp xc342km xng3xce xjbqb8w xjb2p0i x1qlqyl8 x15bjb6t x1heor9g x1ypdohk x1a2a7pz"
|
package/dist/Selector/hooks.d.ts
CHANGED
|
@@ -38,6 +38,13 @@ interface UseComboboxOptions {
|
|
|
38
38
|
* navigation owns those keys there) or when there is no value.
|
|
39
39
|
*/
|
|
40
40
|
onClear?: () => void;
|
|
41
|
+
/**
|
|
42
|
+
* With `hasSearch`, printable characters typed on the trigger are appended to
|
|
43
|
+
* the search query (opening the popup if needed), so type-to-find works
|
|
44
|
+
* without a separate open step. Characters keep arriving here until focus
|
|
45
|
+
* lands in the search input, which then owns its own typing.
|
|
46
|
+
*/
|
|
47
|
+
onSearchSeed?: (char: string) => void;
|
|
41
48
|
listboxId: string;
|
|
42
49
|
}
|
|
43
50
|
interface UseComboboxResult {
|
|
@@ -50,8 +57,13 @@ interface UseComboboxResult {
|
|
|
50
57
|
onItemMouseEnter: (item: SelectorOptionData, index: number) => void;
|
|
51
58
|
}
|
|
52
59
|
/**
|
|
53
|
-
* Handles keyboard navigation
|
|
60
|
+
* Handles keyboard navigation and selection for combobox/listbox patterns.
|
|
61
|
+
*
|
|
62
|
+
* Type-to-select is not handled here: callers that want it compose the shared
|
|
63
|
+
* `useTypeahead` hook and run it ahead of this handler (see Selector). That
|
|
64
|
+
* keeps matching consistent with the other collections and leaves consumers
|
|
65
|
+
* whose own input already filters the items (CommandPalette) untouched.
|
|
54
66
|
*/
|
|
55
|
-
export declare function useCombobox({ selectableItems, value, isDisabled, isOpen, hasSearch, onOpen, onClose, onSelect, onClear, listboxId, }: UseComboboxOptions): UseComboboxResult;
|
|
67
|
+
export declare function useCombobox({ selectableItems, value, isDisabled, isOpen, hasSearch, onOpen, onClose, onSelect, onClear, onSearchSeed, listboxId, }: UseComboboxOptions): UseComboboxResult;
|
|
56
68
|
export {};
|
|
57
69
|
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/Selector/hooks.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,SAAS,CAAC;AAyBhD,UAAU,4BAA4B;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7C,SAAS,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC1C;AAED,UAAU,2BAA2B;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,SAAS,GACV,EAAE,4BAA4B,GAAG,2BAA2B,CAgF5D;AAMD,UAAU,kBAAkB;IAC1B,eAAe,EAAE,kBAAkB,EAAE,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,iBAAiB;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjD,gBAAgB,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACrE;AAED
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/Selector/hooks.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,SAAS,CAAC;AAyBhD,UAAU,4BAA4B;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7C,SAAS,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CAC1C;AAED,UAAU,2BAA2B;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,SAAS,GACV,EAAE,4BAA4B,GAAG,2BAA2B,CAgF5D;AAMD,UAAU,kBAAkB;IAC1B,eAAe,EAAE,kBAAkB,EAAE,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,iBAAiB;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACjD,gBAAgB,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACrE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,eAAe,EACf,KAAK,EACL,UAAkB,EAClB,MAAM,EACN,SAAiB,EACjB,MAAM,EACN,OAAO,EACP,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,SAAS,GACV,EAAE,kBAAkB,GAAG,iBAAiB,CA2NxC"}
|
package/dist/Selector/hooks.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @output Hooks for Selector
|
|
10
10
|
* @position Internal hooks; used by Selector.tsx
|
|
11
11
|
*/
|
|
12
|
-
import { useCallback,
|
|
12
|
+
import { useCallback, useState } from 'react';
|
|
13
13
|
import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect.js";
|
|
14
14
|
// The selected row renders one optical pixel below the closed trigger label at
|
|
15
15
|
// the same mathematical center, so compensate before viewport clamping.
|
|
@@ -114,11 +114,16 @@ export function useSelectedItemOffset({
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// =============================================================================
|
|
117
|
-
// useCombobox - Keyboard navigation
|
|
117
|
+
// useCombobox - Keyboard navigation and selection
|
|
118
118
|
// =============================================================================
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
* Handles keyboard navigation
|
|
121
|
+
* Handles keyboard navigation and selection for combobox/listbox patterns.
|
|
122
|
+
*
|
|
123
|
+
* Type-to-select is not handled here: callers that want it compose the shared
|
|
124
|
+
* `useTypeahead` hook and run it ahead of this handler (see Selector). That
|
|
125
|
+
* keeps matching consistent with the other collections and leaves consumers
|
|
126
|
+
* whose own input already filters the items (CommandPalette) untouched.
|
|
122
127
|
*/
|
|
123
128
|
export function useCombobox({
|
|
124
129
|
selectableItems,
|
|
@@ -130,11 +135,10 @@ export function useCombobox({
|
|
|
130
135
|
onClose,
|
|
131
136
|
onSelect,
|
|
132
137
|
onClear,
|
|
138
|
+
onSearchSeed,
|
|
133
139
|
listboxId
|
|
134
140
|
}) {
|
|
135
141
|
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
136
|
-
const [typeahead, setTypeahead] = useState('');
|
|
137
|
-
const typeaheadTimeoutRef = useRef(undefined);
|
|
138
142
|
const getItemId = useCallback(index => `${listboxId}-item-${index}`, [listboxId]);
|
|
139
143
|
const getEnabledIndices = useCallback(() => {
|
|
140
144
|
return selectableItems.map((item, i) => !item.disabled ? i : -1).filter(i => i >= 0);
|
|
@@ -204,6 +208,8 @@ export function useCombobox({
|
|
|
204
208
|
if (hasSearch) {
|
|
205
209
|
break;
|
|
206
210
|
}
|
|
211
|
+
// A space mid-typeahead extends the query instead of activating; the
|
|
212
|
+
// caller's useTypeahead claims it before this handler ever runs.
|
|
207
213
|
// falls through
|
|
208
214
|
case 'Enter':
|
|
209
215
|
e.preventDefault();
|
|
@@ -272,26 +278,18 @@ export function useCombobox({
|
|
|
272
278
|
}
|
|
273
279
|
break;
|
|
274
280
|
default:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
typeaheadTimeoutRef.current = setTimeout(() => {
|
|
282
|
-
setTypeahead('');
|
|
283
|
-
}, 500);
|
|
284
|
-
const matchIndex = selectableItems.findIndex(item => !item.disabled && item.label?.toLowerCase().startsWith(newTypeahead));
|
|
285
|
-
if (matchIndex >= 0) {
|
|
286
|
-
if (!isOpen) {
|
|
287
|
-
onOpen();
|
|
288
|
-
}
|
|
289
|
-
setHighlightedIndex(matchIndex);
|
|
281
|
+
// Keys land here only while the trigger holds focus — once the search
|
|
282
|
+
// input takes over it handles its own typing — so they belong in the
|
|
283
|
+
// query, including the ones racing the open.
|
|
284
|
+
if (hasSearch && onSearchSeed && e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
|
|
285
|
+
if (!isOpen) {
|
|
286
|
+
onOpen();
|
|
290
287
|
}
|
|
288
|
+
onSearchSeed(e.key);
|
|
291
289
|
}
|
|
292
290
|
break;
|
|
293
291
|
}
|
|
294
|
-
}, [isDisabled, isOpen, onOpen, closeAndReset, selectableItems, highlightedIndex, selectItem, findSelectedIndex, getEnabledIndices,
|
|
292
|
+
}, [isDisabled, isOpen, onOpen, closeAndReset, selectableItems, highlightedIndex, selectItem, findSelectedIndex, getEnabledIndices, hasSearch, onClear, onSearchSeed, value]);
|
|
295
293
|
return {
|
|
296
294
|
highlightedIndex,
|
|
297
295
|
setHighlightedIndex,
|