@human-kit/svelte-components 1.0.0-alpha.10 → 1.0.0-alpha.11
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/combobox/item/combobox-listboxitem.svelte +2 -0
- package/dist/combobox/popover/README.md +18 -4
- package/dist/combobox/popover/combobox-popover-props-test.svelte +38 -0
- package/dist/combobox/popover/combobox-popover-props-test.svelte.d.ts +11 -0
- package/dist/combobox/popover/combobox-popover.svelte +33 -6
- package/dist/combobox/popover/combobox-popover.svelte.d.ts +3 -3
- package/dist/combobox/root/combobox.svelte +26 -6
- package/dist/combobox/root/context.d.ts +10 -0
- package/dist/listbox/item/listbox-item.svelte +7 -1
- package/dist/listbox/item/listbox-item.svelte.d.ts +2 -0
- package/dist/popover/content/popover-content.svelte +1 -1
- package/package.json +1 -1
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
// Virtual focus from ComboBox context
|
|
68
68
|
const isFocused = $derived(ctx.focusedItemId === id);
|
|
69
|
+
const isFocusVisible = $derived(isFocused && ctx.isFocusVisible);
|
|
69
70
|
|
|
70
71
|
// Generate unique ID using instanceId
|
|
71
72
|
const uniqueId = $derived(`combobox-item-${ctx.instanceId}-${id}`);
|
|
@@ -120,6 +121,7 @@
|
|
|
120
121
|
customId={uniqueId}
|
|
121
122
|
disableFocusHandling={true}
|
|
122
123
|
isFocusedOverride={isFocused}
|
|
124
|
+
isFocusVisibleOverride={isFocusVisible}
|
|
123
125
|
onItemSelect={handleSelect}
|
|
124
126
|
onResolvedTextValue={handleResolvedTextValue}
|
|
125
127
|
scrollOnFocus={true}
|
|
@@ -7,7 +7,21 @@
|
|
|
7
7
|
Name: `ComboBox.Popover`
|
|
8
8
|
Description: Floating container for combobox options. Internally composes `Popover.Root` and `Popover.Content` in non-modal mode.
|
|
9
9
|
|
|
10
|
-
| Prop
|
|
11
|
-
|
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
10
|
+
| Prop | Type | Default | Description |
|
|
11
|
+
| ------------------------------ | ---------------------------------- | ---------------- | -------------------------------------------------------------------------- |
|
|
12
|
+
| `offset` | `number` | `8` | Main-axis offset from the combobox trigger. |
|
|
13
|
+
| `placement` | `ExtendedPlacement` | `'bottom-start'` | Preferred floating placement. |
|
|
14
|
+
| `shouldFlip` | `boolean` | `true` | Enables automatic fallback placement when space is limited. |
|
|
15
|
+
| `boundaryElement` | `Element \| null` | `null` | Optional boundary element for positioning constraints. |
|
|
16
|
+
| `class` | `string` | `''` | CSS class names for the floating panel. |
|
|
17
|
+
| `children` | `Snippet` | `undefined` | Popover content, typically `ComboBox.List`. |
|
|
18
|
+
| `isNonModal` | `boolean` | `true` | Controls whether the popover behaves as a non-modal overlay. |
|
|
19
|
+
| `shouldCloseOnInteractOutside` | `boolean` | `true` | Closes when interacting outside the panel. |
|
|
20
|
+
| `shouldCloseOnEscape` | `boolean` | `true` | Closes on Escape key press. |
|
|
21
|
+
| `shouldCloseOnBlur` | `boolean` | `true` | Closes on focus leaving trigger/content in the combobox interaction model. |
|
|
22
|
+
| `initialFocus` | `FocusTrapOptions['initialFocus']` | `undefined` | Initial focus target when modal focus trapping is enabled. |
|
|
23
|
+
|
|
24
|
+
## Notes
|
|
25
|
+
|
|
26
|
+
- `ComboBox.Popover` forwards all `Popover.Content` configuration props except the controlled open-state wiring (`open`, `triggerRef`, and `onOpenChange`).
|
|
27
|
+
- The default placement is `bottom-start` to match the combobox input.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ComponentProps } from 'svelte';
|
|
3
|
+
import ComboBox from '../index';
|
|
4
|
+
import type { PopoverContent } from '../../popover';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
offset?: number;
|
|
8
|
+
placement?: ComponentProps<typeof PopoverContent>['placement'];
|
|
9
|
+
shouldFlip?: boolean;
|
|
10
|
+
shouldCloseOnEscape?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
offset = 8,
|
|
15
|
+
placement = 'bottom-start',
|
|
16
|
+
shouldFlip = true,
|
|
17
|
+
shouldCloseOnEscape = true
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
|
|
20
|
+
const countries = [
|
|
21
|
+
{ id: 'ar', name: 'Argentina' },
|
|
22
|
+
{ id: 'br', name: 'Brazil' },
|
|
23
|
+
{ id: 'ca', name: 'Canada' }
|
|
24
|
+
];
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<ComboBox.Root trigger="press">
|
|
28
|
+
<ComboBox.Input placeholder="Search countries..." />
|
|
29
|
+
<ComboBox.Trigger />
|
|
30
|
+
|
|
31
|
+
<ComboBox.Popover {offset} {placement} {shouldFlip} {shouldCloseOnEscape}>
|
|
32
|
+
<ComboBox.List emptyPlaceholder="No countries found">
|
|
33
|
+
{#each countries as country (country.id)}
|
|
34
|
+
<ComboBox.Item id={country.id} textValue={country.name}>{country.name}</ComboBox.Item>
|
|
35
|
+
{/each}
|
|
36
|
+
</ComboBox.List>
|
|
37
|
+
</ComboBox.Popover>
|
|
38
|
+
</ComboBox.Root>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import type { PopoverContent } from '../../popover';
|
|
3
|
+
type Props = {
|
|
4
|
+
offset?: number;
|
|
5
|
+
placement?: ComponentProps<typeof PopoverContent>['placement'];
|
|
6
|
+
shouldFlip?: boolean;
|
|
7
|
+
shouldCloseOnEscape?: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare const ComboboxPopoverPropsTest: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type ComboboxPopoverPropsTest = ReturnType<typeof ComboboxPopoverPropsTest>;
|
|
11
|
+
export default ComboboxPopoverPropsTest;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ComponentProps, Snippet } from 'svelte';
|
|
3
3
|
import { useComboBoxContext } from '../root/context';
|
|
4
4
|
import { Popover } from '../../popover';
|
|
5
5
|
import { focusWithModality, type InputModality } from '../../primitives/input-modality';
|
|
@@ -9,12 +9,22 @@
|
|
|
9
9
|
* ComboBox.Popover - Just the floating container wrapper.
|
|
10
10
|
* Should contain ComboBox.ListBox as a child.
|
|
11
11
|
*/
|
|
12
|
-
type ComboBoxPopoverProps =
|
|
13
|
-
|
|
12
|
+
type ComboBoxPopoverProps = Omit<
|
|
13
|
+
ComponentProps<typeof Popover.Content>,
|
|
14
|
+
'open' | 'triggerRef' | 'onOpenChange' | 'children'
|
|
15
|
+
> & {
|
|
14
16
|
children?: Snippet;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
let {
|
|
19
|
+
let {
|
|
20
|
+
class: className = '',
|
|
21
|
+
children,
|
|
22
|
+
placement = 'bottom-start',
|
|
23
|
+
isNonModal = true,
|
|
24
|
+
shouldCloseOnEscape = true,
|
|
25
|
+
shouldCloseOnBlur = true,
|
|
26
|
+
...contentProps
|
|
27
|
+
}: ComboBoxPopoverProps = $props();
|
|
18
28
|
|
|
19
29
|
const ctx = useComboBoxContext();
|
|
20
30
|
let restoreListboxMaxHeight: (() => void) | undefined;
|
|
@@ -168,15 +178,32 @@
|
|
|
168
178
|
ctx.inputRef?.focus();
|
|
169
179
|
}
|
|
170
180
|
});
|
|
181
|
+
|
|
182
|
+
$effect(() => {
|
|
183
|
+
ctx.setShouldCloseOnEscape(shouldCloseOnEscape);
|
|
184
|
+
return () => {
|
|
185
|
+
ctx.setShouldCloseOnEscape(true);
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
$effect(() => {
|
|
190
|
+
ctx.setShouldCloseOnBlur(shouldCloseOnBlur);
|
|
191
|
+
return () => {
|
|
192
|
+
ctx.setShouldCloseOnBlur(true);
|
|
193
|
+
};
|
|
194
|
+
});
|
|
171
195
|
</script>
|
|
172
196
|
|
|
173
197
|
<Popover.Root open={ctx.isOpen} triggerRef={ctx.triggerRef} onOpenChange={handleOpenChange}>
|
|
174
198
|
<Popover.Content
|
|
175
|
-
isNonModal
|
|
176
|
-
placement
|
|
199
|
+
{isNonModal}
|
|
200
|
+
{placement}
|
|
201
|
+
{shouldCloseOnEscape}
|
|
202
|
+
{shouldCloseOnBlur}
|
|
177
203
|
class={className}
|
|
178
204
|
onmousedown={handleMouseDown}
|
|
179
205
|
onwheel={handleWheel}
|
|
206
|
+
{...contentProps}
|
|
180
207
|
>
|
|
181
208
|
{#if children}
|
|
182
209
|
{@render children()}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
1
|
+
import type { ComponentProps, Snippet } from 'svelte';
|
|
2
|
+
import { Popover } from '../../popover';
|
|
2
3
|
/**
|
|
3
4
|
* ComboBox.Popover - Just the floating container wrapper.
|
|
4
5
|
* Should contain ComboBox.ListBox as a child.
|
|
5
6
|
*/
|
|
6
|
-
type ComboBoxPopoverProps = {
|
|
7
|
-
class?: string;
|
|
7
|
+
type ComboBoxPopoverProps = Omit<ComponentProps<typeof Popover.Content>, 'open' | 'triggerRef' | 'onOpenChange' | 'children'> & {
|
|
8
8
|
children?: Snippet;
|
|
9
9
|
};
|
|
10
10
|
declare const ComboboxPopover: import("svelte").Component<ComboBoxPopoverProps, {}, "">;
|
|
@@ -107,6 +107,8 @@
|
|
|
107
107
|
let focusWithin = $state(false);
|
|
108
108
|
let focusVisible = $state(false);
|
|
109
109
|
let popoverPointerDownPending = $state(false);
|
|
110
|
+
let shouldCloseOnEscapeState = $state(true);
|
|
111
|
+
let shouldCloseOnBlurState = $state(true);
|
|
110
112
|
|
|
111
113
|
// Flag to control whether inputValue should be used for filtering
|
|
112
114
|
// When false, all items are shown regardless of inputValue
|
|
@@ -407,6 +409,9 @@
|
|
|
407
409
|
return;
|
|
408
410
|
}
|
|
409
411
|
|
|
412
|
+
if (!shouldCloseOnBlurState) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
410
415
|
// Close popover first to prevent flash of options when clearing input
|
|
411
416
|
closePopover();
|
|
412
417
|
|
|
@@ -611,16 +616,16 @@
|
|
|
611
616
|
}
|
|
612
617
|
break;
|
|
613
618
|
case 'Escape':
|
|
614
|
-
if (currentIsOpen) {
|
|
619
|
+
if (currentIsOpen && shouldCloseOnEscapeState) {
|
|
615
620
|
closePopover(true); // Keep focus on input after Escape
|
|
616
621
|
// Stop propagation so parent dialogs don't also close
|
|
617
622
|
event.stopPropagation();
|
|
618
623
|
event.stopImmediatePropagation();
|
|
624
|
+
handleInputBlur();
|
|
625
|
+
// Escape is a keyboard-only path, so focus-visible remains enabled for the input.
|
|
626
|
+
focusVisible = true;
|
|
627
|
+
event.preventDefault();
|
|
619
628
|
}
|
|
620
|
-
handleInputBlur();
|
|
621
|
-
// Escape is a keyboard-only path, so focus-visible remains enabled for the input.
|
|
622
|
-
focusVisible = true;
|
|
623
|
-
event.preventDefault();
|
|
624
629
|
break;
|
|
625
630
|
case 'Backspace':
|
|
626
631
|
// In multiple mode, remove last tag when input is empty
|
|
@@ -668,6 +673,15 @@
|
|
|
668
673
|
get isPending() {
|
|
669
674
|
return isPending;
|
|
670
675
|
},
|
|
676
|
+
get isFocusVisible() {
|
|
677
|
+
return focusVisible;
|
|
678
|
+
},
|
|
679
|
+
get shouldCloseOnEscape() {
|
|
680
|
+
return shouldCloseOnEscapeState;
|
|
681
|
+
},
|
|
682
|
+
get shouldCloseOnBlur() {
|
|
683
|
+
return shouldCloseOnBlurState;
|
|
684
|
+
},
|
|
671
685
|
get isReadOnly() {
|
|
672
686
|
return isReadOnly;
|
|
673
687
|
},
|
|
@@ -742,7 +756,13 @@
|
|
|
742
756
|
markPopoverPointerDown: () => {
|
|
743
757
|
popoverPointerDownPending = true;
|
|
744
758
|
},
|
|
745
|
-
consumePopoverPointerDown
|
|
759
|
+
consumePopoverPointerDown,
|
|
760
|
+
setShouldCloseOnEscape: (value: boolean) => {
|
|
761
|
+
shouldCloseOnEscapeState = value;
|
|
762
|
+
},
|
|
763
|
+
setShouldCloseOnBlur: (value: boolean) => {
|
|
764
|
+
shouldCloseOnBlurState = value;
|
|
765
|
+
}
|
|
746
766
|
};
|
|
747
767
|
|
|
748
768
|
setComboBoxContext(ctx);
|
|
@@ -22,6 +22,8 @@ export type ComboBoxContext<T extends object = object> = {
|
|
|
22
22
|
isDisabled: boolean;
|
|
23
23
|
/** Whether the combobox is pending async work */
|
|
24
24
|
isPending: boolean;
|
|
25
|
+
/** Whether focus should currently be presented as keyboard-visible */
|
|
26
|
+
isFocusVisible: boolean;
|
|
25
27
|
/** Whether the combobox is read-only */
|
|
26
28
|
isReadOnly: boolean;
|
|
27
29
|
/** Selection mode */
|
|
@@ -92,6 +94,14 @@ export type ComboBoxContext<T extends object = object> = {
|
|
|
92
94
|
markPopoverPointerDown: () => void;
|
|
93
95
|
/** Consume the pending popover-pointer marker. */
|
|
94
96
|
consumePopoverPointerDown: () => boolean;
|
|
97
|
+
/** Whether Escape should close the popover. */
|
|
98
|
+
shouldCloseOnEscape: boolean;
|
|
99
|
+
/** Whether blur should close the popover. */
|
|
100
|
+
shouldCloseOnBlur: boolean;
|
|
101
|
+
/** Update whether Escape should close the popover. */
|
|
102
|
+
setShouldCloseOnEscape: (value: boolean) => void;
|
|
103
|
+
/** Update whether blur should close the popover. */
|
|
104
|
+
setShouldCloseOnBlur: (value: boolean) => void;
|
|
95
105
|
};
|
|
96
106
|
export declare function setComboBoxContext<T extends object = object>(ctx: ComboBoxContext<T>): void;
|
|
97
107
|
export declare function getComboBoxContext<T extends object = object>(): ComboBoxContext<T> | undefined;
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
disableFocusHandling?: boolean;
|
|
31
31
|
/** Override the focused state. When provided, this value is used instead of internal focus tracking. */
|
|
32
32
|
isFocusedOverride?: boolean;
|
|
33
|
+
/** Override the focus-visible presentation state. */
|
|
34
|
+
isFocusVisibleOverride?: boolean;
|
|
33
35
|
/** Override the select behavior. When provided, called instead of default listbox selection. */
|
|
34
36
|
onItemSelect?: (id: string | number, label: string) => void;
|
|
35
37
|
/** Callback with resolved text value when mounted (from prop or rendered content). */
|
|
@@ -52,6 +54,7 @@
|
|
|
52
54
|
customId,
|
|
53
55
|
disableFocusHandling = false,
|
|
54
56
|
isFocusedOverride,
|
|
57
|
+
isFocusVisibleOverride,
|
|
55
58
|
onItemSelect,
|
|
56
59
|
onResolvedTextValue,
|
|
57
60
|
scrollOnFocus = false,
|
|
@@ -82,6 +85,9 @@
|
|
|
82
85
|
? Boolean(pressedOverride) && !isDisabledComputed
|
|
83
86
|
: isPressed && !isDisabledComputed
|
|
84
87
|
);
|
|
88
|
+
const isFocusVisibleComputed = $derived(
|
|
89
|
+
isFocusVisibleOverride !== undefined ? isFocusVisibleOverride : isFocusVisible
|
|
90
|
+
);
|
|
85
91
|
|
|
86
92
|
// ID: use custom if provided, otherwise generate
|
|
87
93
|
const uniqueId = $derived(customId ?? `listbox-item-${id}`);
|
|
@@ -311,7 +317,7 @@
|
|
|
311
317
|
data-selected={isSelected || undefined}
|
|
312
318
|
data-disabled={isDisabledComputed || undefined}
|
|
313
319
|
data-focused={isFocusedComputed || undefined}
|
|
314
|
-
data-focus-visible={
|
|
320
|
+
data-focus-visible={isFocusVisibleComputed || undefined}
|
|
315
321
|
data-hovered={isHovered || undefined}
|
|
316
322
|
data-pressed={isPressedComputed || undefined}
|
|
317
323
|
onpointerdown={handlePointerDown}
|
|
@@ -20,6 +20,8 @@ type ListBoxItemProps = Omit<HTMLAttributes<HTMLDivElement>, 'id' | 'children'>
|
|
|
20
20
|
disableFocusHandling?: boolean;
|
|
21
21
|
/** Override the focused state. When provided, this value is used instead of internal focus tracking. */
|
|
22
22
|
isFocusedOverride?: boolean;
|
|
23
|
+
/** Override the focus-visible presentation state. */
|
|
24
|
+
isFocusVisibleOverride?: boolean;
|
|
23
25
|
/** Override the select behavior. When provided, called instead of default listbox selection. */
|
|
24
26
|
onItemSelect?: (id: string | number, label: string) => void;
|
|
25
27
|
/** Callback with resolved text value when mounted (from prop or rendered content). */
|