@human-kit/svelte-components 1.0.0-alpha.1
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/TODO.md +175 -0
- package/dist/combobox/button/combobox-button.svelte +57 -0
- package/dist/combobox/button/combobox-button.svelte.d.ts +9 -0
- package/dist/combobox/index.d.ts +14 -0
- package/dist/combobox/index.js +18 -0
- package/dist/combobox/index.parts.d.ts +10 -0
- package/dist/combobox/index.parts.js +11 -0
- package/dist/combobox/input/combobox-input.svelte +98 -0
- package/dist/combobox/input/combobox-input.svelte.d.ts +13 -0
- package/dist/combobox/item/combobox-item-implicit-text-test.svelte +21 -0
- package/dist/combobox/item/combobox-item-implicit-text-test.svelte.d.ts +3 -0
- package/dist/combobox/item/combobox-listboxitem.svelte +136 -0
- package/dist/combobox/item/combobox-listboxitem.svelte.d.ts +18 -0
- package/dist/combobox/item-indicator/combobox-item-indicator.svelte +63 -0
- package/dist/combobox/item-indicator/combobox-item-indicator.svelte.d.ts +17 -0
- package/dist/combobox/list/combobox-listbox.svelte +76 -0
- package/dist/combobox/list/combobox-listbox.svelte.d.ts +47 -0
- package/dist/combobox/popover/combobox-popover.svelte +69 -0
- package/dist/combobox/popover/combobox-popover.svelte.d.ts +12 -0
- package/dist/combobox/root/combobox-filtered-test.svelte +51 -0
- package/dist/combobox/root/combobox-filtered-test.svelte.d.ts +7 -0
- package/dist/combobox/root/combobox-multiselect-test.svelte +76 -0
- package/dist/combobox/root/combobox-multiselect-test.svelte.d.ts +13 -0
- package/dist/combobox/root/combobox-numeric-string-id-test.svelte +20 -0
- package/dist/combobox/root/combobox-numeric-string-id-test.svelte.d.ts +3 -0
- package/dist/combobox/root/combobox-test.svelte +43 -0
- package/dist/combobox/root/combobox-test.svelte.d.ts +9 -0
- package/dist/combobox/root/combobox.svelte +696 -0
- package/dist/combobox/root/combobox.svelte.d.ts +58 -0
- package/dist/combobox/root/context.d.ts +90 -0
- package/dist/combobox/root/context.js +15 -0
- package/dist/combobox/tag/combobox-tag.svelte +58 -0
- package/dist/combobox/tag/combobox-tag.svelte.d.ts +22 -0
- package/dist/combobox/tag/tag-context-provider.svelte +36 -0
- package/dist/combobox/tag/tag-context-provider.svelte.d.ts +14 -0
- package/dist/combobox/tag-remove/combobox-tag-remove.svelte +53 -0
- package/dist/combobox/tag-remove/combobox-tag-remove.svelte.d.ts +14 -0
- package/dist/combobox/tags/combobox-tags.svelte +50 -0
- package/dist/combobox/tags/combobox-tags.svelte.d.ts +20 -0
- package/dist/dialog/content/dialog-content.svelte +121 -0
- package/dist/dialog/content/dialog-content.svelte.d.ts +19 -0
- package/dist/dialog/index.d.ts +10 -0
- package/dist/dialog/index.js +15 -0
- package/dist/dialog/index.parts.d.ts +5 -0
- package/dist/dialog/index.parts.js +6 -0
- package/dist/dialog/overlay/dialog-overlay.svelte +39 -0
- package/dist/dialog/overlay/dialog-overlay.svelte.d.ts +12 -0
- package/dist/dialog/portal/dialog-portal.svelte +32 -0
- package/dist/dialog/portal/dialog-portal.svelte.d.ts +12 -0
- package/dist/dialog/root/context.d.ts +25 -0
- package/dist/dialog/root/context.js +8 -0
- package/dist/dialog/root/dialog-root.svelte +99 -0
- package/dist/dialog/root/dialog-root.svelte.d.ts +21 -0
- package/dist/dialog/root/dialog-stack.d.ts +32 -0
- package/dist/dialog/root/dialog-stack.js +55 -0
- package/dist/dialog/root/dialog-test.svelte +38 -0
- package/dist/dialog/root/dialog-test.svelte.d.ts +10 -0
- package/dist/dialog/root/dialog-with-combobox-test.svelte +61 -0
- package/dist/dialog/root/dialog-with-combobox-test.svelte.d.ts +7 -0
- package/dist/dialog/root/nested-dialog-test.svelte +63 -0
- package/dist/dialog/root/nested-dialog-test.svelte.d.ts +8 -0
- package/dist/dialog/root/types.d.ts +10 -0
- package/dist/dialog/root/types.js +1 -0
- package/dist/dialog/trigger/dialog-trigger.svelte +71 -0
- package/dist/dialog/trigger/dialog-trigger.svelte.d.ts +12 -0
- package/dist/hooks/use-virtual-focus.svelte.d.ts +55 -0
- package/dist/hooks/use-virtual-focus.svelte.js +201 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +19 -0
- package/dist/input/index.d.ts +3 -0
- package/dist/input/index.js +3 -0
- package/dist/input/input.svelte +19 -0
- package/dist/input/input.svelte.d.ts +8 -0
- package/dist/label/index.d.ts +3 -0
- package/dist/label/index.js +3 -0
- package/dist/label/label.svelte +21 -0
- package/dist/label/label.svelte.d.ts +8 -0
- package/dist/listbox/index.d.ts +6 -0
- package/dist/listbox/index.js +10 -0
- package/dist/listbox/index.parts.d.ts +2 -0
- package/dist/listbox/index.parts.js +3 -0
- package/dist/listbox/item/listbox-item.svelte +186 -0
- package/dist/listbox/item/listbox-item.svelte.d.ts +34 -0
- package/dist/listbox/root/context.d.ts +73 -0
- package/dist/listbox/root/context.js +249 -0
- package/dist/listbox/root/listbox-numeric-id-test.svelte +18 -0
- package/dist/listbox/root/listbox-numeric-id-test.svelte.d.ts +3 -0
- package/dist/listbox/root/listbox-test.svelte +27 -0
- package/dist/listbox/root/listbox-test.svelte.d.ts +8 -0
- package/dist/listbox/root/listbox.svelte +146 -0
- package/dist/listbox/root/listbox.svelte.d.ts +54 -0
- package/dist/popover/content/popover-content-test.svelte +43 -0
- package/dist/popover/content/popover-content-test.svelte.d.ts +12 -0
- package/dist/popover/content/popover-content.svelte +167 -0
- package/dist/popover/content/popover-content.svelte.d.ts +38 -0
- package/dist/popover/index.d.ts +8 -0
- package/dist/popover/index.js +14 -0
- package/dist/popover/index.parts.d.ts +4 -0
- package/dist/popover/index.parts.js +5 -0
- package/dist/popover/root/context.d.ts +24 -0
- package/dist/popover/root/context.js +10 -0
- package/dist/popover/root/popover-root.svelte +87 -0
- package/dist/popover/root/popover-root.svelte.d.ts +20 -0
- package/dist/popover/root/popover-test.svelte +40 -0
- package/dist/popover/root/popover-test.svelte.d.ts +11 -0
- package/dist/popover/trigger/popover-trigger-button.svelte +42 -0
- package/dist/popover/trigger/popover-trigger-button.svelte.d.ts +12 -0
- package/dist/popover/trigger/popover-trigger-in-dialog-test.svelte +29 -0
- package/dist/popover/trigger/popover-trigger-in-dialog-test.svelte.d.ts +18 -0
- package/dist/popover/trigger/popover-trigger.svelte +71 -0
- package/dist/popover/trigger/popover-trigger.svelte.d.ts +12 -0
- package/dist/portal/index.d.ts +1 -0
- package/dist/portal/index.js +1 -0
- package/dist/portal/portal.svelte +44 -0
- package/dist/portal/portal.svelte.d.ts +10 -0
- package/dist/primitives/aria-hide-outside.d.ts +38 -0
- package/dist/primitives/aria-hide-outside.js +152 -0
- package/dist/primitives/click-outside.d.ts +26 -0
- package/dist/primitives/click-outside.js +66 -0
- package/dist/primitives/floating.d.ts +57 -0
- package/dist/primitives/floating.js +179 -0
- package/dist/primitives/focus-trap.d.ts +19 -0
- package/dist/primitives/focus-trap.js +102 -0
- package/dist/primitives/index.d.ts +6 -0
- package/dist/primitives/index.js +7 -0
- package/dist/primitives/keyboard-navigation.d.ts +88 -0
- package/dist/primitives/keyboard-navigation.js +274 -0
- package/dist/primitives/scroll-lock.d.ts +19 -0
- package/dist/primitives/scroll-lock.js +62 -0
- package/dist/test-mocks/app-environment.d.ts +7 -0
- package/dist/test-mocks/app-environment.js +7 -0
- package/dist/test-mocks/app-navigation.d.ts +11 -0
- package/dist/test-mocks/app-navigation.js +11 -0
- package/dist/test-mocks/app-stores.d.ts +16 -0
- package/dist/test-mocks/app-stores.js +18 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.js +5 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
type ComboBoxProps<T> = {
|
|
3
|
+
/** Stable ID used to generate internal ARIA IDs (recommended for SSR). */
|
|
4
|
+
id?: string;
|
|
5
|
+
isDisabled?: boolean;
|
|
6
|
+
isReadOnly?: boolean;
|
|
7
|
+
/** Selected value(s). Single value for single mode, array for multiple mode. Can be bound with bind:value */
|
|
8
|
+
value?: string | number | (string | number)[];
|
|
9
|
+
defaultValue?: string | number | (string | number)[];
|
|
10
|
+
/** Current input value. Can be bound with bind:inputValue */
|
|
11
|
+
inputValue?: string;
|
|
12
|
+
defaultInputValue?: string;
|
|
13
|
+
selectionBehavior?: 'toggle' | 'replace';
|
|
14
|
+
selectionMode?: 'single' | 'multiple';
|
|
15
|
+
/** Whether to close popover after selection. Default: true for single, false for multiple */
|
|
16
|
+
closeOnSelect?: boolean;
|
|
17
|
+
/** Whether the popover is open. Can be bound with bind:isOpen */
|
|
18
|
+
isOpen?: boolean;
|
|
19
|
+
/** How the popover opens: 'focus' | 'input' | 'press'. Default: 'press' */
|
|
20
|
+
trigger?: 'focus' | 'input' | 'press';
|
|
21
|
+
onInputChange?: (value: string) => void;
|
|
22
|
+
onOpenChange?: (open: boolean) => void;
|
|
23
|
+
onChange?: (value: string | number | (string | number)[] | undefined) => void;
|
|
24
|
+
/** Optional: Array of items for dynamic rendering */
|
|
25
|
+
items?: T[];
|
|
26
|
+
/** Optional: Snippet to render each item (used with items prop) */
|
|
27
|
+
renderItem?: Snippet<[T]>;
|
|
28
|
+
children?: Snippet;
|
|
29
|
+
class?: string;
|
|
30
|
+
/** Accessible label for the combobox group */
|
|
31
|
+
'aria-label'?: string;
|
|
32
|
+
/** ID of element that labels this combobox group */
|
|
33
|
+
'aria-labelledby'?: string;
|
|
34
|
+
};
|
|
35
|
+
declare function $$render<T extends object = object>(): {
|
|
36
|
+
props: ComboBoxProps<T>;
|
|
37
|
+
exports: {};
|
|
38
|
+
bindings: "value" | "inputValue" | "isOpen";
|
|
39
|
+
slots: {};
|
|
40
|
+
events: {};
|
|
41
|
+
};
|
|
42
|
+
declare class __sveltets_Render<T extends object = object> {
|
|
43
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
44
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
45
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
46
|
+
bindings(): "value" | "inputValue" | "isOpen";
|
|
47
|
+
exports(): {};
|
|
48
|
+
}
|
|
49
|
+
interface $$IsomorphicComponent {
|
|
50
|
+
new <T extends object = object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
51
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
52
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
53
|
+
<T extends object = object>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
54
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
55
|
+
}
|
|
56
|
+
declare const Combobox: $$IsomorphicComponent;
|
|
57
|
+
type Combobox<T extends object = object> = InstanceType<typeof Combobox<T>>;
|
|
58
|
+
export default Combobox;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ListBoxContext } from '../../listbox/root/context';
|
|
3
|
+
/**
|
|
4
|
+
* Context shared between ComboBox and its children.
|
|
5
|
+
*/
|
|
6
|
+
export type ComboBoxContext<T extends object = object> = {
|
|
7
|
+
/** Unique instance ID for ARIA attributes */
|
|
8
|
+
instanceId: string;
|
|
9
|
+
/** Current input value (used for filtering, respects shouldFilter flag) */
|
|
10
|
+
inputValue: string;
|
|
11
|
+
/** Display value shown in the input element (always shows actual text) */
|
|
12
|
+
displayValue: string;
|
|
13
|
+
/** Whether the popover is open */
|
|
14
|
+
isOpen: boolean;
|
|
15
|
+
/** Reference to the input element for positioning */
|
|
16
|
+
inputRef: HTMLElement | null;
|
|
17
|
+
/** Reference to the wrapper for popover positioning */
|
|
18
|
+
triggerRef: HTMLElement | null;
|
|
19
|
+
/** Currently selected value(s) */
|
|
20
|
+
selectedValue: Set<string | number>;
|
|
21
|
+
/** Whether the combobox is disabled */
|
|
22
|
+
isDisabled: boolean;
|
|
23
|
+
/** Whether the combobox is read-only */
|
|
24
|
+
isReadOnly: boolean;
|
|
25
|
+
/** Selection mode */
|
|
26
|
+
selectionMode: 'single' | 'multiple';
|
|
27
|
+
/** How the popover opens: 'focus' | 'input' | 'press' */
|
|
28
|
+
trigger: 'focus' | 'input' | 'press';
|
|
29
|
+
/** Whether inputValue should be used for filtering (false = show all items) */
|
|
30
|
+
shouldFilter: boolean;
|
|
31
|
+
/** Currently focused item ID (virtual focus) */
|
|
32
|
+
focusedItemId: string | number | null;
|
|
33
|
+
/** Registered item IDs for navigation */
|
|
34
|
+
itemIds: (string | number)[];
|
|
35
|
+
/** Item labels map for selection display */
|
|
36
|
+
itemLabels: Map<string | number, string>;
|
|
37
|
+
/** Persistent labels for selected items (not cleared on item unregister) */
|
|
38
|
+
selectedLabels: Map<string | number, string>;
|
|
39
|
+
/** Pending focus direction when opening with keyboard */
|
|
40
|
+
pendingFocusDirection: 'first' | 'last' | null;
|
|
41
|
+
/** Reference to the ListBox context for navigation delegation */
|
|
42
|
+
listboxCtx: ListBoxContext | null;
|
|
43
|
+
/** Reference to the ListBox DOM element for scoped queries */
|
|
44
|
+
listboxRef: HTMLElement | null;
|
|
45
|
+
/** Optional: Array of items for dynamic rendering */
|
|
46
|
+
items?: T[];
|
|
47
|
+
/** Optional: Snippet to render each item */
|
|
48
|
+
renderItem?: Snippet<[T]>;
|
|
49
|
+
/** Set the input ref */
|
|
50
|
+
setInputRef: (el: HTMLElement | null) => void;
|
|
51
|
+
/** Set the trigger ref (wrapper) */
|
|
52
|
+
setTriggerRef: (el: HTMLElement | null) => void;
|
|
53
|
+
/** Set the ListBox context reference */
|
|
54
|
+
setListboxCtx: (ctx: ListBoxContext) => void;
|
|
55
|
+
/** Set the ListBox DOM element reference */
|
|
56
|
+
setListboxRef: (el: HTMLElement | null) => void;
|
|
57
|
+
/** Update input value */
|
|
58
|
+
setInputValue: (value: string) => void;
|
|
59
|
+
/** Open the popover */
|
|
60
|
+
open: () => void;
|
|
61
|
+
/** Close the popover. Pass refocusInput=true to keep focus on input. */
|
|
62
|
+
close: (refocusInput?: boolean) => void;
|
|
63
|
+
/** Toggle the popover */
|
|
64
|
+
toggle: () => void;
|
|
65
|
+
/** Select an item */
|
|
66
|
+
select: (id: string | number, label: string) => void;
|
|
67
|
+
/** Remove an item from selection (multiple mode) */
|
|
68
|
+
removeItem: (id: string | number) => void;
|
|
69
|
+
/** Clear all selections */
|
|
70
|
+
clearSelection: () => void;
|
|
71
|
+
/** Called when popover open state changes */
|
|
72
|
+
onOpenChange: (open: boolean) => void;
|
|
73
|
+
/** Set focused item ID (virtual focus) */
|
|
74
|
+
setFocusedItemId: (id: string | number | null) => void;
|
|
75
|
+
/** Register an item for navigation */
|
|
76
|
+
registerItem: (id: string | number, label: string) => void;
|
|
77
|
+
/** Unregister an item */
|
|
78
|
+
unregisterItem: (id: string | number) => void;
|
|
79
|
+
/** Handle keyboard events (arrow navigation, enter, escape) */
|
|
80
|
+
handleKeydown: (event: KeyboardEvent) => void;
|
|
81
|
+
/** Handle input blur - restore selection or deselect if empty */
|
|
82
|
+
handleInputBlur: () => void;
|
|
83
|
+
/** Currently focused tag ID (virtual focus for tag navigation) */
|
|
84
|
+
focusedTagId: string | number | null;
|
|
85
|
+
/** Set focused tag ID (virtual focus for tag navigation) */
|
|
86
|
+
setFocusedTagId: (id: string | number | null) => void;
|
|
87
|
+
};
|
|
88
|
+
export declare function setComboBoxContext<T extends object = object>(ctx: ComboBoxContext<T>): void;
|
|
89
|
+
export declare function getComboBoxContext<T extends object = object>(): ComboBoxContext<T> | undefined;
|
|
90
|
+
export declare function useComboBoxContext<T extends object = object>(): ComboBoxContext<T>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { setContext, getContext } from 'svelte';
|
|
2
|
+
const COMBOBOX_KEY = Symbol('combobox');
|
|
3
|
+
export function setComboBoxContext(ctx) {
|
|
4
|
+
setContext(COMBOBOX_KEY, ctx);
|
|
5
|
+
}
|
|
6
|
+
export function getComboBoxContext() {
|
|
7
|
+
return getContext(COMBOBOX_KEY);
|
|
8
|
+
}
|
|
9
|
+
export function useComboBoxContext() {
|
|
10
|
+
const ctx = getComboBoxContext();
|
|
11
|
+
if (!ctx) {
|
|
12
|
+
throw new Error('ComboBox components must be used within a ComboBox');
|
|
13
|
+
}
|
|
14
|
+
return ctx;
|
|
15
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export const TAG_CONTEXT_KEY = Symbol.for('combobox-tag');
|
|
3
|
+
|
|
4
|
+
export type TagContext = {
|
|
5
|
+
id: string | number;
|
|
6
|
+
label: string;
|
|
7
|
+
remove: () => void;
|
|
8
|
+
disabled: boolean;
|
|
9
|
+
};
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import type { Snippet } from 'svelte';
|
|
14
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
15
|
+
import { getContext } from 'svelte';
|
|
16
|
+
import { cn } from '../../utils/cn';
|
|
17
|
+
import { useComboBoxContext } from '../root/context';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ComboBox.Tag - Individual tag representing a selected value.
|
|
21
|
+
* Must be used inside ComboBox.Tags which provides context.
|
|
22
|
+
* Supports keyboard navigation: ArrowLeft/Right to navigate, Delete/Backspace to remove, ArrowDown to focus input.
|
|
23
|
+
*/
|
|
24
|
+
type ComboBoxTagProps = {
|
|
25
|
+
/** Content of the tag - use ComboBox.TagRemove for remove button */
|
|
26
|
+
children: Snippet;
|
|
27
|
+
class?: string;
|
|
28
|
+
} & Omit<HTMLAttributes<HTMLSpanElement>, 'class' | 'children'>;
|
|
29
|
+
|
|
30
|
+
let { children, class: className, ...restProps }: ComboBoxTagProps = $props();
|
|
31
|
+
|
|
32
|
+
const tagCtx = getContext<TagContext>(TAG_CONTEXT_KEY);
|
|
33
|
+
const comboboxCtx = useComboBoxContext();
|
|
34
|
+
|
|
35
|
+
/** Whether this tag has virtual focus (navigated via arrow keys) */
|
|
36
|
+
const isFocused = $derived(comboboxCtx.focusedTagId === tagCtx.id);
|
|
37
|
+
|
|
38
|
+
function handleMouseDown(event: MouseEvent) {
|
|
39
|
+
// Prevent focus theft - tags use virtual focus only via arrow keys
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<span
|
|
45
|
+
role="listitem"
|
|
46
|
+
data-tag-id={tagCtx.id}
|
|
47
|
+
data-disabled={tagCtx.disabled || undefined}
|
|
48
|
+
data-focused={isFocused || undefined}
|
|
49
|
+
onmousedown={handleMouseDown}
|
|
50
|
+
class={cn(
|
|
51
|
+
'inline-flex items-center gap-1 rounded-md bg-gray-100 px-2 py-0.5 text-sm data-[focused=true]:ring-2 data-[focused=true]:ring-blue-500 data-[focused=true]:outline-none dark:bg-gray-700',
|
|
52
|
+
tagCtx.disabled && 'opacity-50',
|
|
53
|
+
className
|
|
54
|
+
)}
|
|
55
|
+
{...restProps}
|
|
56
|
+
>
|
|
57
|
+
{@render children()}
|
|
58
|
+
</span>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const TAG_CONTEXT_KEY: unique symbol;
|
|
2
|
+
export type TagContext = {
|
|
3
|
+
id: string | number;
|
|
4
|
+
label: string;
|
|
5
|
+
remove: () => void;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
};
|
|
8
|
+
import type { Snippet } from 'svelte';
|
|
9
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
10
|
+
/**
|
|
11
|
+
* ComboBox.Tag - Individual tag representing a selected value.
|
|
12
|
+
* Must be used inside ComboBox.Tags which provides context.
|
|
13
|
+
* Supports keyboard navigation: ArrowLeft/Right to navigate, Delete/Backspace to remove, ArrowDown to focus input.
|
|
14
|
+
*/
|
|
15
|
+
type ComboBoxTagProps = {
|
|
16
|
+
/** Content of the tag - use ComboBox.TagRemove for remove button */
|
|
17
|
+
children: Snippet;
|
|
18
|
+
class?: string;
|
|
19
|
+
} & Omit<HTMLAttributes<HTMLSpanElement>, 'class' | 'children'>;
|
|
20
|
+
declare const ComboboxTag: import("svelte").Component<ComboBoxTagProps, {}, "">;
|
|
21
|
+
type ComboboxTag = ReturnType<typeof ComboboxTag>;
|
|
22
|
+
export default ComboboxTag;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { setContext } from 'svelte';
|
|
4
|
+
import { TAG_CONTEXT_KEY, type TagContext } from './combobox-tag.svelte';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal component that provides tag context for each iteration.
|
|
8
|
+
*/
|
|
9
|
+
type TagContextProviderProps = {
|
|
10
|
+
id: string | number;
|
|
11
|
+
label: string;
|
|
12
|
+
remove: () => void;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
children: Snippet;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let { id, label, remove, disabled, children }: TagContextProviderProps = $props();
|
|
18
|
+
|
|
19
|
+
// Set context for Tag and TagRemove children
|
|
20
|
+
setContext<TagContext>(TAG_CONTEXT_KEY, {
|
|
21
|
+
get id() {
|
|
22
|
+
return id;
|
|
23
|
+
},
|
|
24
|
+
get label() {
|
|
25
|
+
return label;
|
|
26
|
+
},
|
|
27
|
+
get remove() {
|
|
28
|
+
return remove;
|
|
29
|
+
},
|
|
30
|
+
get disabled() {
|
|
31
|
+
return disabled;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
{@render children()}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* Internal component that provides tag context for each iteration.
|
|
4
|
+
*/
|
|
5
|
+
type TagContextProviderProps = {
|
|
6
|
+
id: string | number;
|
|
7
|
+
label: string;
|
|
8
|
+
remove: () => void;
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
children: Snippet;
|
|
11
|
+
};
|
|
12
|
+
declare const TagContextProvider: import("svelte").Component<TagContextProviderProps, {}, "">;
|
|
13
|
+
type TagContextProvider = ReturnType<typeof TagContextProvider>;
|
|
14
|
+
export default TagContextProvider;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
import { getContext } from 'svelte';
|
|
5
|
+
import { cn } from '../../utils/cn';
|
|
6
|
+
import { TAG_CONTEXT_KEY, type TagContext } from '../tag/combobox-tag.svelte';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ComboBox.TagRemove - Remove button for a tag.
|
|
10
|
+
* Must be used inside ComboBox.Tag.
|
|
11
|
+
*/
|
|
12
|
+
type ComboBoxTagRemoveProps = {
|
|
13
|
+
/** Content of the button (defaults to X icon) */
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
class?: string;
|
|
16
|
+
} & Omit<HTMLButtonAttributes, 'class' | 'children' | 'type' | 'onclick' | 'aria-label'>;
|
|
17
|
+
|
|
18
|
+
let { children, class: className, ...restProps }: ComboBoxTagRemoveProps = $props();
|
|
19
|
+
|
|
20
|
+
const tagCtx = getContext<TagContext>(TAG_CONTEXT_KEY);
|
|
21
|
+
|
|
22
|
+
function handleClick(e: MouseEvent) {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
e.stopPropagation();
|
|
25
|
+
tagCtx.remove();
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
{#if !tagCtx.disabled}
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onclick={handleClick}
|
|
33
|
+
aria-label={`Remove ${tagCtx.label}`}
|
|
34
|
+
tabindex={-1}
|
|
35
|
+
class={cn('ml-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600', className)}
|
|
36
|
+
{...restProps}
|
|
37
|
+
>
|
|
38
|
+
{#if children}
|
|
39
|
+
{@render children()}
|
|
40
|
+
{:else}
|
|
41
|
+
<svg
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
viewBox="0 0 16 16"
|
|
44
|
+
fill="currentColor"
|
|
45
|
+
class="h-3.5 w-3.5"
|
|
46
|
+
>
|
|
47
|
+
<path
|
|
48
|
+
d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"
|
|
49
|
+
/>
|
|
50
|
+
</svg>
|
|
51
|
+
{/if}
|
|
52
|
+
</button>
|
|
53
|
+
{/if}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
/**
|
|
4
|
+
* ComboBox.TagRemove - Remove button for a tag.
|
|
5
|
+
* Must be used inside ComboBox.Tag.
|
|
6
|
+
*/
|
|
7
|
+
type ComboBoxTagRemoveProps = {
|
|
8
|
+
/** Content of the button (defaults to X icon) */
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
class?: string;
|
|
11
|
+
} & Omit<HTMLButtonAttributes, 'class' | 'children' | 'type' | 'onclick' | 'aria-label'>;
|
|
12
|
+
declare const ComboboxTagRemove: import("svelte").Component<ComboBoxTagRemoveProps, {}, "">;
|
|
13
|
+
type ComboboxTagRemove = ReturnType<typeof ComboboxTagRemove>;
|
|
14
|
+
export default ComboboxTagRemove;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { useComboBoxContext } from '../root/context';
|
|
4
|
+
import TagContextProvider from '../tag/tag-context-provider.svelte';
|
|
5
|
+
|
|
6
|
+
export type TagItem = {
|
|
7
|
+
value: string | number;
|
|
8
|
+
label: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ComboBox.Tags - Container for selected value tags in multiple mode.
|
|
13
|
+
* Renders each selected value as a tag using the provided snippet.
|
|
14
|
+
* Sets context for each tag so ComboBox.Tag can access id, label, and remove.
|
|
15
|
+
*/
|
|
16
|
+
type ComboBoxTagsProps = {
|
|
17
|
+
/** Snippet to render each tag. Receives { item: { value, label } } */
|
|
18
|
+
children: Snippet<[{ item: TagItem }]>;
|
|
19
|
+
class?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let { children, class: className = '' }: ComboBoxTagsProps = $props();
|
|
23
|
+
|
|
24
|
+
const ctx = useComboBoxContext();
|
|
25
|
+
|
|
26
|
+
// Get selected items with their labels from persistent selectedLabels map
|
|
27
|
+
const selectedItems = $derived(
|
|
28
|
+
Array.from(ctx.selectedValue).map((id) => ({
|
|
29
|
+
id,
|
|
30
|
+
// Use selectedLabels (persistent) first, fallback to itemLabels (may be cleared on filter)
|
|
31
|
+
label: ctx.selectedLabels.get(id) ?? ctx.itemLabels.get(id) ?? String(id),
|
|
32
|
+
remove: () => ctx.removeItem(id)
|
|
33
|
+
}))
|
|
34
|
+
);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
{#if ctx.selectionMode === 'multiple' && selectedItems.length > 0}
|
|
38
|
+
<div class={className} role="list" aria-label="Selected values">
|
|
39
|
+
{#each selectedItems as selected (selected.id)}
|
|
40
|
+
<TagContextProvider
|
|
41
|
+
id={selected.id}
|
|
42
|
+
label={selected.label}
|
|
43
|
+
remove={selected.remove}
|
|
44
|
+
disabled={ctx.isDisabled}
|
|
45
|
+
>
|
|
46
|
+
{@render children({ item: { value: selected.id, label: selected.label } })}
|
|
47
|
+
</TagContextProvider>
|
|
48
|
+
{/each}
|
|
49
|
+
</div>
|
|
50
|
+
{/if}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type TagItem = {
|
|
3
|
+
value: string | number;
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* ComboBox.Tags - Container for selected value tags in multiple mode.
|
|
8
|
+
* Renders each selected value as a tag using the provided snippet.
|
|
9
|
+
* Sets context for each tag so ComboBox.Tag can access id, label, and remove.
|
|
10
|
+
*/
|
|
11
|
+
type ComboBoxTagsProps = {
|
|
12
|
+
/** Snippet to render each tag. Receives { item: { value, label } } */
|
|
13
|
+
children: Snippet<[{
|
|
14
|
+
item: TagItem;
|
|
15
|
+
}]>;
|
|
16
|
+
class?: string;
|
|
17
|
+
};
|
|
18
|
+
declare const ComboboxTags: import("svelte").Component<ComboBoxTagsProps, {}, "">;
|
|
19
|
+
type ComboboxTags = ReturnType<typeof ComboboxTags>;
|
|
20
|
+
export default ComboboxTags;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { onMount, onDestroy } from 'svelte';
|
|
5
|
+
import { browser } from '$app/environment';
|
|
6
|
+
import { focusTrap } from '../../primitives/focus-trap';
|
|
7
|
+
import { scrollLock } from '../../primitives/scroll-lock';
|
|
8
|
+
import { clickOutside } from '../../primitives/click-outside';
|
|
9
|
+
import { ariaHideOutside } from '../../primitives/aria-hide-outside';
|
|
10
|
+
import { getDialogContext } from '../root/context';
|
|
11
|
+
import { pushDialog, popDialog, isTopmostDialog, getContentZIndex } from '../root/dialog-stack';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Dialog.Content - The centered modal dialog panel.
|
|
15
|
+
* Must be used inside a Dialog.Portal.
|
|
16
|
+
*/
|
|
17
|
+
type DialogContentProps = {
|
|
18
|
+
/** Content of the dialog. */
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
/** CSS class for the dialog container. */
|
|
21
|
+
class?: string;
|
|
22
|
+
/** Whether clicking outside the dialog should close it. */
|
|
23
|
+
shouldCloseOnInteractOutside?: boolean;
|
|
24
|
+
/** Whether pressing Escape should close the dialog. */
|
|
25
|
+
shouldCloseOnEscape?: boolean;
|
|
26
|
+
} & Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'>;
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
children,
|
|
30
|
+
class: className = '',
|
|
31
|
+
shouldCloseOnInteractOutside = true,
|
|
32
|
+
shouldCloseOnEscape = true,
|
|
33
|
+
...restProps
|
|
34
|
+
}: DialogContentProps = $props();
|
|
35
|
+
|
|
36
|
+
const ctx = getDialogContext();
|
|
37
|
+
|
|
38
|
+
if (!ctx) {
|
|
39
|
+
throw new Error('Dialog.Content must be used inside a Dialog.Root');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dialogCtx = ctx;
|
|
43
|
+
|
|
44
|
+
let dialogRef: HTMLElement | undefined = $state();
|
|
45
|
+
let dialogId: symbol | null = null;
|
|
46
|
+
let dialogLevel = $state(0);
|
|
47
|
+
|
|
48
|
+
function close() {
|
|
49
|
+
dialogCtx.close();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Wrapper for close that only executes if this is the topmost dialog.
|
|
54
|
+
*/
|
|
55
|
+
function closeIfTopmost() {
|
|
56
|
+
if (dialogId && isTopmostDialog(dialogId)) {
|
|
57
|
+
close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
62
|
+
if (event.key === 'Escape' && shouldCloseOnEscape) {
|
|
63
|
+
// Only handle if this is the topmost dialog
|
|
64
|
+
if (dialogId && isTopmostDialog(dialogId)) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onMount(() => {
|
|
72
|
+
if (!browser) return;
|
|
73
|
+
// Register this dialog in the stack
|
|
74
|
+
const { id, level } = pushDialog(close);
|
|
75
|
+
dialogId = id;
|
|
76
|
+
dialogLevel = level;
|
|
77
|
+
// Share level with overlay via context
|
|
78
|
+
dialogCtx.setStackLevel(level);
|
|
79
|
+
document.addEventListener('keydown', handleKeydown);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
onDestroy(() => {
|
|
83
|
+
if (!browser) return;
|
|
84
|
+
// Unregister this dialog from the stack
|
|
85
|
+
if (dialogId) {
|
|
86
|
+
popDialog(dialogId);
|
|
87
|
+
}
|
|
88
|
+
document.removeEventListener('keydown', handleKeydown);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Calculate z-index based on dialog level
|
|
92
|
+
const zIndex = $derived(getContentZIndex(dialogLevel));
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<div
|
|
96
|
+
bind:this={dialogRef}
|
|
97
|
+
class={className}
|
|
98
|
+
role="dialog"
|
|
99
|
+
aria-modal="true"
|
|
100
|
+
data-dialog-content
|
|
101
|
+
use:clickOutside={{
|
|
102
|
+
handler: closeIfTopmost,
|
|
103
|
+
enabled: shouldCloseOnInteractOutside,
|
|
104
|
+
ignore: [dialogCtx.triggerRef]
|
|
105
|
+
}}
|
|
106
|
+
use:focusTrap={true}
|
|
107
|
+
use:scrollLock={true}
|
|
108
|
+
use:ariaHideOutside={true}
|
|
109
|
+
style="
|
|
110
|
+
position: fixed;
|
|
111
|
+
z-index: {zIndex};
|
|
112
|
+
top: 50%;
|
|
113
|
+
left: 50%;
|
|
114
|
+
transform: translate(-50%, -50%);
|
|
115
|
+
"
|
|
116
|
+
{...restProps}
|
|
117
|
+
>
|
|
118
|
+
{#if children}
|
|
119
|
+
{@render children()}
|
|
120
|
+
{/if}
|
|
121
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
/**
|
|
4
|
+
* Dialog.Content - The centered modal dialog panel.
|
|
5
|
+
* Must be used inside a Dialog.Portal.
|
|
6
|
+
*/
|
|
7
|
+
type DialogContentProps = {
|
|
8
|
+
/** Content of the dialog. */
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
/** CSS class for the dialog container. */
|
|
11
|
+
class?: string;
|
|
12
|
+
/** Whether clicking outside the dialog should close it. */
|
|
13
|
+
shouldCloseOnInteractOutside?: boolean;
|
|
14
|
+
/** Whether pressing Escape should close the dialog. */
|
|
15
|
+
shouldCloseOnEscape?: boolean;
|
|
16
|
+
} & Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'>;
|
|
17
|
+
declare const DialogContent: import("svelte").Component<DialogContentProps, {}, "">;
|
|
18
|
+
type DialogContent = ReturnType<typeof DialogContent>;
|
|
19
|
+
export default DialogContent;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as DialogParts from './index.parts.ts';
|
|
2
|
+
export declare const Dialog: typeof DialogParts;
|
|
3
|
+
export default DialogParts;
|
|
4
|
+
export { default as DialogRoot } from './root/dialog-root.svelte';
|
|
5
|
+
export { default as DialogTrigger } from './trigger/dialog-trigger.svelte';
|
|
6
|
+
export { default as DialogPortal } from './portal/dialog-portal.svelte';
|
|
7
|
+
export { default as DialogOverlay } from './overlay/dialog-overlay.svelte';
|
|
8
|
+
export { default as DialogContent } from './content/dialog-content.svelte';
|
|
9
|
+
export { getDialogContext, setDialogContext, type DialogContext } from './root/context';
|
|
10
|
+
export type { DialogStateHelpers } from './root/types';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Dialog component with namespace pattern
|
|
2
|
+
// Usage: <Dialog.Root>, <Dialog.Trigger>, <Dialog.Portal>, <Dialog.Overlay>, <Dialog.Content>
|
|
3
|
+
import * as DialogParts from './index.parts.ts';
|
|
4
|
+
// Named export for namespace usage: import { Dialog } from '...'
|
|
5
|
+
export const Dialog = DialogParts;
|
|
6
|
+
// Default export for backwards compatibility
|
|
7
|
+
export default DialogParts;
|
|
8
|
+
// Re-export individual parts for direct imports
|
|
9
|
+
export { default as DialogRoot } from './root/dialog-root.svelte';
|
|
10
|
+
export { default as DialogTrigger } from './trigger/dialog-trigger.svelte';
|
|
11
|
+
export { default as DialogPortal } from './portal/dialog-portal.svelte';
|
|
12
|
+
export { default as DialogOverlay } from './overlay/dialog-overlay.svelte';
|
|
13
|
+
export { default as DialogContent } from './content/dialog-content.svelte';
|
|
14
|
+
// Re-export context utilities
|
|
15
|
+
export { getDialogContext, setDialogContext } from './root/context';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as Root } from './root/dialog-root.svelte';
|
|
2
|
+
export { default as Trigger } from './trigger/dialog-trigger.svelte';
|
|
3
|
+
export { default as Portal } from './portal/dialog-portal.svelte';
|
|
4
|
+
export { default as Overlay } from './overlay/dialog-overlay.svelte';
|
|
5
|
+
export { default as Content } from './content/dialog-content.svelte';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Namespace parts for <Dialog.Root>, <Dialog.Content>, etc.
|
|
2
|
+
export { default as Root } from './root/dialog-root.svelte';
|
|
3
|
+
export { default as Trigger } from './trigger/dialog-trigger.svelte';
|
|
4
|
+
export { default as Portal } from './portal/dialog-portal.svelte';
|
|
5
|
+
export { default as Overlay } from './overlay/dialog-overlay.svelte';
|
|
6
|
+
export { default as Content } from './content/dialog-content.svelte';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { getDialogContext } from '../root/context';
|
|
4
|
+
import { getOverlayZIndex } from '../root/dialog-stack';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dialog.Overlay - The backdrop overlay behind the dialog.
|
|
8
|
+
* Must be used inside a Dialog.Portal.
|
|
9
|
+
*/
|
|
10
|
+
type DialogOverlayProps = {
|
|
11
|
+
/** CSS class for the overlay. */
|
|
12
|
+
class?: string;
|
|
13
|
+
} & Omit<HTMLAttributes<HTMLDivElement>, 'class'>;
|
|
14
|
+
|
|
15
|
+
let { class: className = '', ...restProps }: DialogOverlayProps = $props();
|
|
16
|
+
|
|
17
|
+
const ctx = getDialogContext();
|
|
18
|
+
|
|
19
|
+
if (!ctx) {
|
|
20
|
+
throw new Error('Dialog.Overlay must be used inside a Dialog.Root');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const dialogCtx = ctx;
|
|
24
|
+
|
|
25
|
+
// Calculate z-index based on dialog level from context
|
|
26
|
+
const zIndex = $derived(getOverlayZIndex(dialogCtx.stackLevel));
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div
|
|
30
|
+
class={className}
|
|
31
|
+
data-dialog-overlay
|
|
32
|
+
style="
|
|
33
|
+
position: fixed;
|
|
34
|
+
inset: 0;
|
|
35
|
+
z-index: {zIndex};
|
|
36
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
37
|
+
"
|
|
38
|
+
{...restProps}
|
|
39
|
+
></div>
|