@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,167 @@
|
|
|
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 { floating, type ExtendedPlacement } from '../../primitives/floating';
|
|
7
|
+
import { focusTrap } from '../../primitives/focus-trap';
|
|
8
|
+
import { scrollLock } from '../../primitives/scroll-lock';
|
|
9
|
+
import { clickOutside } from '../../primitives/click-outside';
|
|
10
|
+
import { ariaHideOutside } from '../../primitives/aria-hide-outside';
|
|
11
|
+
import { Portal } from '../../portal';
|
|
12
|
+
import { getPopoverContext } from '../root/context';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Popover.Content - The floating content panel.
|
|
16
|
+
* Can be used inside Popover.Root (reads context) or standalone (props required).
|
|
17
|
+
*/
|
|
18
|
+
type PopoverContentProps = {
|
|
19
|
+
/** Offset along the main axis from the anchor element. */
|
|
20
|
+
offset?: number;
|
|
21
|
+
/** Placement relative to the anchor element. */
|
|
22
|
+
placement?: ExtendedPlacement;
|
|
23
|
+
/** Whether to flip when there's insufficient space. */
|
|
24
|
+
shouldFlip?: boolean;
|
|
25
|
+
/** Boundary element for positioning constraints. */
|
|
26
|
+
boundaryElement?: Element | null;
|
|
27
|
+
/** Content of the popover. */
|
|
28
|
+
children?: Snippet;
|
|
29
|
+
/** CSS class for the popover container. */
|
|
30
|
+
class?: string;
|
|
31
|
+
/** Whether the popover is non-modal (allows outside interaction, no focus trap, no scroll lock). */
|
|
32
|
+
isNonModal?: boolean;
|
|
33
|
+
/** Whether clicking outside the popover should close it. */
|
|
34
|
+
shouldCloseOnInteractOutside?: boolean;
|
|
35
|
+
/** Whether pressing Escape should close the popover. */
|
|
36
|
+
shouldCloseOnEscape?: boolean;
|
|
37
|
+
/** Whether losing focus (blur) should close the popover. Defaults to true for non-modal popovers. */
|
|
38
|
+
shouldCloseOnBlur?: boolean;
|
|
39
|
+
// Standalone mode props (used when not inside Popover.Root)
|
|
40
|
+
/** Controlled open state (standalone mode). */
|
|
41
|
+
open?: boolean;
|
|
42
|
+
/** Reference to the trigger element (standalone mode). */
|
|
43
|
+
triggerRef?: HTMLElement | null;
|
|
44
|
+
/** Callback when open state changes (standalone mode). */
|
|
45
|
+
onOpenChange?: (open: boolean) => void;
|
|
46
|
+
} & Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'>;
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
offset = 8,
|
|
50
|
+
placement = 'bottom',
|
|
51
|
+
shouldFlip = true,
|
|
52
|
+
boundaryElement = null,
|
|
53
|
+
children,
|
|
54
|
+
class: className = '',
|
|
55
|
+
isNonModal = false,
|
|
56
|
+
shouldCloseOnInteractOutside = true,
|
|
57
|
+
shouldCloseOnEscape = true,
|
|
58
|
+
shouldCloseOnBlur,
|
|
59
|
+
// Standalone mode props
|
|
60
|
+
open: openProp,
|
|
61
|
+
triggerRef: triggerRefProp = null,
|
|
62
|
+
onOpenChange: onOpenChangeProp,
|
|
63
|
+
...restProps
|
|
64
|
+
}: PopoverContentProps = $props();
|
|
65
|
+
|
|
66
|
+
const ctx = getPopoverContext();
|
|
67
|
+
|
|
68
|
+
// Support both context mode (inside Root) and standalone mode (props)
|
|
69
|
+
const isStandalone = $derived(ctx === undefined);
|
|
70
|
+
const triggerRef = $derived(isStandalone ? triggerRefProp : ctx!.triggerRef);
|
|
71
|
+
const isOpen = $derived(isStandalone ? (openProp ?? false) : ctx!.isOpen);
|
|
72
|
+
const isModal = $derived(!isNonModal);
|
|
73
|
+
const shouldCloseOnBlurResolved = $derived(shouldCloseOnBlur ?? isNonModal);
|
|
74
|
+
|
|
75
|
+
let popoverRef: HTMLElement | undefined = $state();
|
|
76
|
+
|
|
77
|
+
function close() {
|
|
78
|
+
if (isStandalone) {
|
|
79
|
+
onOpenChangeProp?.(false);
|
|
80
|
+
triggerRefProp?.focus();
|
|
81
|
+
} else {
|
|
82
|
+
ctx!.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleOpenChange(value: boolean) {
|
|
87
|
+
if (isStandalone) {
|
|
88
|
+
onOpenChangeProp?.(value);
|
|
89
|
+
} else {
|
|
90
|
+
ctx!.onOpenChange(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
95
|
+
if (event.key === 'Escape' && isOpen && shouldCloseOnEscape) {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handleDocumentFocusIn(event: FocusEvent) {
|
|
102
|
+
if (!shouldCloseOnBlurResolved || !isOpen) return;
|
|
103
|
+
|
|
104
|
+
const target = event.target as Node;
|
|
105
|
+
|
|
106
|
+
const focusInPopover = popoverRef?.contains(target) || target === popoverRef;
|
|
107
|
+
const focusInTrigger = triggerRef?.contains(target) || target === triggerRef;
|
|
108
|
+
|
|
109
|
+
if (!focusInPopover && !focusInTrigger) {
|
|
110
|
+
handleOpenChange(false);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function handleScroll(event: Event) {
|
|
115
|
+
if (!isNonModal || !isOpen || !shouldCloseOnInteractOutside) return;
|
|
116
|
+
|
|
117
|
+
// Check if scroll is inside the popover or trigger
|
|
118
|
+
const target = event.target as Node;
|
|
119
|
+
const isInsidePopover = popoverRef?.contains(target) || target === popoverRef;
|
|
120
|
+
const isInsideTrigger = triggerRef?.contains(target) || target === triggerRef;
|
|
121
|
+
|
|
122
|
+
// Only close on external scroll
|
|
123
|
+
if (!isInsidePopover && !isInsideTrigger) {
|
|
124
|
+
close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onMount(() => {
|
|
129
|
+
if (!browser) return;
|
|
130
|
+
document.addEventListener('keydown', handleKeydown);
|
|
131
|
+
document.addEventListener('focusin', handleDocumentFocusIn);
|
|
132
|
+
document.addEventListener('scroll', handleScroll, true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
onDestroy(() => {
|
|
136
|
+
if (!browser) return;
|
|
137
|
+
document.removeEventListener('keydown', handleKeydown);
|
|
138
|
+
document.removeEventListener('focusin', handleDocumentFocusIn);
|
|
139
|
+
document.removeEventListener('scroll', handleScroll, true);
|
|
140
|
+
});
|
|
141
|
+
</script>
|
|
142
|
+
|
|
143
|
+
{#if isOpen}
|
|
144
|
+
<Portal>
|
|
145
|
+
<div
|
|
146
|
+
bind:this={popoverRef}
|
|
147
|
+
class={className}
|
|
148
|
+
role="dialog"
|
|
149
|
+
aria-modal={isModal}
|
|
150
|
+
use:floating={{ anchor: triggerRef, offset, placement, shouldFlip, boundaryElement }}
|
|
151
|
+
use:clickOutside={{
|
|
152
|
+
handler: close,
|
|
153
|
+
enabled: shouldCloseOnInteractOutside,
|
|
154
|
+
ignore: [triggerRef]
|
|
155
|
+
}}
|
|
156
|
+
use:focusTrap={isModal}
|
|
157
|
+
use:scrollLock={isModal}
|
|
158
|
+
use:ariaHideOutside={isModal}
|
|
159
|
+
style="position: fixed; z-index: 9999;"
|
|
160
|
+
{...restProps}
|
|
161
|
+
>
|
|
162
|
+
{#if children}
|
|
163
|
+
{@render children()}
|
|
164
|
+
{/if}
|
|
165
|
+
</div>
|
|
166
|
+
</Portal>
|
|
167
|
+
{/if}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type ExtendedPlacement } from '../../primitives/floating';
|
|
4
|
+
/**
|
|
5
|
+
* Popover.Content - The floating content panel.
|
|
6
|
+
* Can be used inside Popover.Root (reads context) or standalone (props required).
|
|
7
|
+
*/
|
|
8
|
+
type PopoverContentProps = {
|
|
9
|
+
/** Offset along the main axis from the anchor element. */
|
|
10
|
+
offset?: number;
|
|
11
|
+
/** Placement relative to the anchor element. */
|
|
12
|
+
placement?: ExtendedPlacement;
|
|
13
|
+
/** Whether to flip when there's insufficient space. */
|
|
14
|
+
shouldFlip?: boolean;
|
|
15
|
+
/** Boundary element for positioning constraints. */
|
|
16
|
+
boundaryElement?: Element | null;
|
|
17
|
+
/** Content of the popover. */
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
/** CSS class for the popover container. */
|
|
20
|
+
class?: string;
|
|
21
|
+
/** Whether the popover is non-modal (allows outside interaction, no focus trap, no scroll lock). */
|
|
22
|
+
isNonModal?: boolean;
|
|
23
|
+
/** Whether clicking outside the popover should close it. */
|
|
24
|
+
shouldCloseOnInteractOutside?: boolean;
|
|
25
|
+
/** Whether pressing Escape should close the popover. */
|
|
26
|
+
shouldCloseOnEscape?: boolean;
|
|
27
|
+
/** Whether losing focus (blur) should close the popover. Defaults to true for non-modal popovers. */
|
|
28
|
+
shouldCloseOnBlur?: boolean;
|
|
29
|
+
/** Controlled open state (standalone mode). */
|
|
30
|
+
open?: boolean;
|
|
31
|
+
/** Reference to the trigger element (standalone mode). */
|
|
32
|
+
triggerRef?: HTMLElement | null;
|
|
33
|
+
/** Callback when open state changes (standalone mode). */
|
|
34
|
+
onOpenChange?: (open: boolean) => void;
|
|
35
|
+
} & Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'>;
|
|
36
|
+
declare const PopoverContent: import("svelte").Component<PopoverContentProps, {}, "">;
|
|
37
|
+
type PopoverContent = ReturnType<typeof PopoverContent>;
|
|
38
|
+
export default PopoverContent;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * as Popover from './index.parts.ts';
|
|
2
|
+
export { default as PopoverRoot } from './root/popover-root.svelte';
|
|
3
|
+
export { default as PopoverContent } from './content/popover-content.svelte';
|
|
4
|
+
export { default as PopoverTrigger } from './trigger/popover-trigger.svelte';
|
|
5
|
+
export { default as PopoverTriggerButton } from './trigger/popover-trigger-button.svelte';
|
|
6
|
+
export { getPopoverContext, setPopoverContext, type PopoverContext, getPopoverTriggerContext, setPopoverTriggerContext, type PopoverTriggerContext } from './root/context.ts';
|
|
7
|
+
import * as PopoverParts from './index.parts.ts';
|
|
8
|
+
export default PopoverParts;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Namespace export for component composition: <Popover.Root>, <Popover.Trigger>, etc.
|
|
2
|
+
export * as Popover from './index.parts.ts';
|
|
3
|
+
// Direct named exports for individual imports
|
|
4
|
+
export { default as PopoverRoot } from './root/popover-root.svelte';
|
|
5
|
+
export { default as PopoverContent } from './content/popover-content.svelte';
|
|
6
|
+
export { default as PopoverTrigger } from './trigger/popover-trigger.svelte';
|
|
7
|
+
export { default as PopoverTriggerButton } from './trigger/popover-trigger-button.svelte';
|
|
8
|
+
// Context and types
|
|
9
|
+
export { getPopoverContext, setPopoverContext,
|
|
10
|
+
// Legacy aliases
|
|
11
|
+
getPopoverTriggerContext, setPopoverTriggerContext } from './root/context.ts';
|
|
12
|
+
// Default export as namespace object
|
|
13
|
+
import * as PopoverParts from './index.parts.ts';
|
|
14
|
+
export default PopoverParts;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as Root } from './root/popover-root.svelte';
|
|
2
|
+
export { default as Content } from './content/popover-content.svelte';
|
|
3
|
+
export { default as Trigger } from './trigger/popover-trigger.svelte';
|
|
4
|
+
export { default as TriggerButton } from './trigger/popover-trigger-button.svelte';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Namespace parts for <Popover.Root>, <Popover.Content>, etc.
|
|
2
|
+
export { default as Root } from './root/popover-root.svelte';
|
|
3
|
+
export { default as Content } from './content/popover-content.svelte';
|
|
4
|
+
export { default as Trigger } from './trigger/popover-trigger.svelte';
|
|
5
|
+
export { default as TriggerButton } from './trigger/popover-trigger-button.svelte';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context shared between Popover components (Root, Trigger, Content).
|
|
3
|
+
*/
|
|
4
|
+
export type PopoverContext = {
|
|
5
|
+
/** Whether the popover is open */
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
/** Reference to the trigger element */
|
|
8
|
+
triggerRef: HTMLElement | null;
|
|
9
|
+
/** Set the trigger ref (used by Trigger component) */
|
|
10
|
+
setTriggerRef: (el: HTMLElement | null) => void;
|
|
11
|
+
/** Toggle popover open state */
|
|
12
|
+
toggle: () => void;
|
|
13
|
+
/** Open the popover */
|
|
14
|
+
open: () => void;
|
|
15
|
+
/** Close the popover and return focus to trigger */
|
|
16
|
+
close: () => void;
|
|
17
|
+
/** Called when popover open state changes */
|
|
18
|
+
onOpenChange: (open: boolean) => void;
|
|
19
|
+
};
|
|
20
|
+
export declare function setPopoverContext(ctx: PopoverContext): void;
|
|
21
|
+
export declare function getPopoverContext(): PopoverContext | undefined;
|
|
22
|
+
export type PopoverTriggerContext = PopoverContext;
|
|
23
|
+
export declare const setPopoverTriggerContext: typeof setPopoverContext;
|
|
24
|
+
export declare const getPopoverTriggerContext: typeof getPopoverContext;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { setContext, getContext } from 'svelte';
|
|
2
|
+
const POPOVER_CONTEXT_KEY = Symbol('popover');
|
|
3
|
+
export function setPopoverContext(ctx) {
|
|
4
|
+
setContext(POPOVER_CONTEXT_KEY, ctx);
|
|
5
|
+
}
|
|
6
|
+
export function getPopoverContext() {
|
|
7
|
+
return getContext(POPOVER_CONTEXT_KEY);
|
|
8
|
+
}
|
|
9
|
+
export const setPopoverTriggerContext = setPopoverContext;
|
|
10
|
+
export const getPopoverTriggerContext = getPopoverContext;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { setPopoverContext, type PopoverContext } from './context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Popover.Root - State management wrapper for Popover components.
|
|
7
|
+
* Provides context for Trigger and Content children.
|
|
8
|
+
*/
|
|
9
|
+
type PopoverRootProps = {
|
|
10
|
+
/** Controlled open state. */
|
|
11
|
+
open?: boolean;
|
|
12
|
+
/** Initial open state for uncontrolled mode. */
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
/** Callback when open state changes. */
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
/** Reference to the trigger element. Can be set manually or via Popover.Trigger. */
|
|
17
|
+
triggerRef?: HTMLElement | null;
|
|
18
|
+
/** Children (Trigger and Content) */
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
open = $bindable(),
|
|
24
|
+
defaultOpen = false,
|
|
25
|
+
onOpenChange,
|
|
26
|
+
triggerRef = $bindable<HTMLElement | null>(null),
|
|
27
|
+
children
|
|
28
|
+
}: PopoverRootProps = $props();
|
|
29
|
+
|
|
30
|
+
// Use function to capture initial value only (not reactive)
|
|
31
|
+
let isOpenInternal = $state((() => defaultOpen)());
|
|
32
|
+
|
|
33
|
+
const isControlled = $derived(open !== undefined);
|
|
34
|
+
const isOpen = $derived(isControlled ? open! : isOpenInternal);
|
|
35
|
+
|
|
36
|
+
function setOpen(value: boolean) {
|
|
37
|
+
if (isControlled) {
|
|
38
|
+
onOpenChange?.(value);
|
|
39
|
+
} else {
|
|
40
|
+
isOpenInternal = value;
|
|
41
|
+
onOpenChange?.(value);
|
|
42
|
+
}
|
|
43
|
+
// Sync bindable prop
|
|
44
|
+
open = value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function toggle() {
|
|
48
|
+
setOpen(!isOpen);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function openPopover() {
|
|
52
|
+
setOpen(true);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function closePopover() {
|
|
56
|
+
setOpen(false);
|
|
57
|
+
triggerRef?.focus();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function setTriggerRef(el: HTMLElement | null) {
|
|
61
|
+
triggerRef = el;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleOpenChange(newOpen: boolean) {
|
|
65
|
+
setOpen(newOpen);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ctx: PopoverContext = {
|
|
69
|
+
get isOpen() {
|
|
70
|
+
return isOpen;
|
|
71
|
+
},
|
|
72
|
+
get triggerRef() {
|
|
73
|
+
return triggerRef ?? null;
|
|
74
|
+
},
|
|
75
|
+
setTriggerRef,
|
|
76
|
+
toggle,
|
|
77
|
+
open: openPopover,
|
|
78
|
+
close: closePopover,
|
|
79
|
+
onOpenChange: handleOpenChange
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
setPopoverContext(ctx);
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
{#if children}
|
|
86
|
+
{@render children()}
|
|
87
|
+
{/if}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* Popover.Root - State management wrapper for Popover components.
|
|
4
|
+
* Provides context for Trigger and Content children.
|
|
5
|
+
*/
|
|
6
|
+
type PopoverRootProps = {
|
|
7
|
+
/** Controlled open state. */
|
|
8
|
+
open?: boolean;
|
|
9
|
+
/** Initial open state for uncontrolled mode. */
|
|
10
|
+
defaultOpen?: boolean;
|
|
11
|
+
/** Callback when open state changes. */
|
|
12
|
+
onOpenChange?: (open: boolean) => void;
|
|
13
|
+
/** Reference to the trigger element. Can be set manually or via Popover.Trigger. */
|
|
14
|
+
triggerRef?: HTMLElement | null;
|
|
15
|
+
/** Children (Trigger and Content) */
|
|
16
|
+
children?: Snippet;
|
|
17
|
+
};
|
|
18
|
+
declare const PopoverRoot: import("svelte").Component<PopoverRootProps, {}, "open" | "triggerRef">;
|
|
19
|
+
type PopoverRoot = ReturnType<typeof PopoverRoot>;
|
|
20
|
+
export default PopoverRoot;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Popover } from '../index';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
open?: boolean;
|
|
6
|
+
defaultOpen?: boolean;
|
|
7
|
+
isNonModal?: boolean;
|
|
8
|
+
shouldCloseOnInteractOutside?: boolean;
|
|
9
|
+
shouldCloseOnEscape?: boolean;
|
|
10
|
+
onOpenChange?: (open: boolean) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
open,
|
|
15
|
+
defaultOpen = false,
|
|
16
|
+
isNonModal = false,
|
|
17
|
+
shouldCloseOnInteractOutside = true,
|
|
18
|
+
shouldCloseOnEscape = true,
|
|
19
|
+
onOpenChange
|
|
20
|
+
}: Props = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<Popover.Root {open} {defaultOpen} {onOpenChange}>
|
|
24
|
+
<Popover.Trigger>
|
|
25
|
+
<button type="button">Open Popover</button>
|
|
26
|
+
</Popover.Trigger>
|
|
27
|
+
|
|
28
|
+
<Popover.Content
|
|
29
|
+
class="popover-content"
|
|
30
|
+
{isNonModal}
|
|
31
|
+
{shouldCloseOnInteractOutside}
|
|
32
|
+
{shouldCloseOnEscape}
|
|
33
|
+
>
|
|
34
|
+
<div class="popover-body">
|
|
35
|
+
<h3>Popover Title</h3>
|
|
36
|
+
<p>Popover content goes here.</p>
|
|
37
|
+
<button type="button" class="close-btn">Close</button>
|
|
38
|
+
</div>
|
|
39
|
+
</Popover.Content>
|
|
40
|
+
</Popover.Root>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
open?: boolean;
|
|
3
|
+
defaultOpen?: boolean;
|
|
4
|
+
isNonModal?: boolean;
|
|
5
|
+
shouldCloseOnInteractOutside?: boolean;
|
|
6
|
+
shouldCloseOnEscape?: boolean;
|
|
7
|
+
onOpenChange?: (open: boolean) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const PopoverTest: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type PopoverTest = ReturnType<typeof PopoverTest>;
|
|
11
|
+
export default PopoverTest;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { getPopoverTriggerContext } from '../root/context';
|
|
4
|
+
|
|
5
|
+
type PopoverTriggerButtonProps = {
|
|
6
|
+
/** Button content */
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
/** Additional class */
|
|
9
|
+
class?: string;
|
|
10
|
+
/** Any other props */
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let { children, class: className = '', ...restProps }: PopoverTriggerButtonProps = $props();
|
|
15
|
+
|
|
16
|
+
let buttonRef: HTMLButtonElement | null = $state(null);
|
|
17
|
+
const ctx = getPopoverTriggerContext();
|
|
18
|
+
|
|
19
|
+
$effect(() => {
|
|
20
|
+
if (buttonRef && ctx) {
|
|
21
|
+
ctx.setTriggerRef(buttonRef);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function handleClick() {
|
|
26
|
+
ctx?.toggle();
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<button
|
|
31
|
+
bind:this={buttonRef}
|
|
32
|
+
class={className}
|
|
33
|
+
type="button"
|
|
34
|
+
aria-expanded={ctx?.isOpen ?? false}
|
|
35
|
+
aria-haspopup="dialog"
|
|
36
|
+
onclick={handleClick}
|
|
37
|
+
{...restProps}
|
|
38
|
+
>
|
|
39
|
+
{#if children}
|
|
40
|
+
{@render children()}
|
|
41
|
+
{/if}
|
|
42
|
+
</button>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type PopoverTriggerButtonProps = {
|
|
3
|
+
/** Button content */
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
/** Additional class */
|
|
6
|
+
class?: string;
|
|
7
|
+
/** Any other props */
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
declare const PopoverTriggerButton: import("svelte").Component<PopoverTriggerButtonProps, {}, "">;
|
|
11
|
+
type PopoverTriggerButton = ReturnType<typeof PopoverTriggerButton>;
|
|
12
|
+
export default PopoverTriggerButton;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Dialog } from '../../dialog';
|
|
3
|
+
import { Popover } from '../../popover';
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<Dialog.Root>
|
|
7
|
+
{#snippet children({ close })}
|
|
8
|
+
<Dialog.Trigger>
|
|
9
|
+
<button type="button">Open Dialog</button>
|
|
10
|
+
</Dialog.Trigger>
|
|
11
|
+
|
|
12
|
+
<Dialog.Portal>
|
|
13
|
+
<Dialog.Overlay />
|
|
14
|
+
<Dialog.Content>
|
|
15
|
+
<Popover.Root>
|
|
16
|
+
<Popover.Trigger>
|
|
17
|
+
<button type="button">Open Nested Popover</button>
|
|
18
|
+
</Popover.Trigger>
|
|
19
|
+
|
|
20
|
+
<Popover.Content isNonModal={true} class="nested-popover-content">
|
|
21
|
+
<p>Nested popover content</p>
|
|
22
|
+
</Popover.Content>
|
|
23
|
+
</Popover.Root>
|
|
24
|
+
|
|
25
|
+
<button type="button" onclick={close}>Close Dialog</button>
|
|
26
|
+
</Dialog.Content>
|
|
27
|
+
</Dialog.Portal>
|
|
28
|
+
{/snippet}
|
|
29
|
+
</Dialog.Root>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const PopoverTriggerInDialogTest: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type PopoverTriggerInDialogTest = InstanceType<typeof PopoverTriggerInDialogTest>;
|
|
18
|
+
export default PopoverTriggerInDialogTest;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { onMount, onDestroy } from 'svelte';
|
|
4
|
+
import { getPopoverContext } from '../root/context';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Popover.Trigger - Wrapper that auto-wires a trigger element.
|
|
8
|
+
* Must be used inside a Popover.Root.
|
|
9
|
+
*/
|
|
10
|
+
type PopoverTriggerProps = {
|
|
11
|
+
/** Children (trigger button) */
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let { children }: PopoverTriggerProps = $props();
|
|
16
|
+
|
|
17
|
+
const ctx = getPopoverContext();
|
|
18
|
+
|
|
19
|
+
if (!ctx) {
|
|
20
|
+
throw new Error('Popover.Trigger must be used inside a Popover.Root');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// After the throw, TypeScript knows ctx is defined
|
|
24
|
+
const popoverCtx = ctx;
|
|
25
|
+
|
|
26
|
+
let wrapperRef: HTMLElement | null = $state(null);
|
|
27
|
+
|
|
28
|
+
function handleClick(event: MouseEvent) {
|
|
29
|
+
const target = event.target as HTMLElement;
|
|
30
|
+
const button = target.closest('button, [role="button"]') as HTMLElement | null;
|
|
31
|
+
|
|
32
|
+
if (button && wrapperRef?.contains(button)) {
|
|
33
|
+
// Set trigger ref if not already set
|
|
34
|
+
if (!popoverCtx.triggerRef) {
|
|
35
|
+
popoverCtx.setTriggerRef(button);
|
|
36
|
+
}
|
|
37
|
+
popoverCtx.toggle();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onMount(() => {
|
|
42
|
+
if (wrapperRef) {
|
|
43
|
+
// Find and set up the trigger button
|
|
44
|
+
const firstButton = wrapperRef.querySelector('button, [role="button"]') as HTMLElement | null;
|
|
45
|
+
if (firstButton) {
|
|
46
|
+
popoverCtx.setTriggerRef(firstButton);
|
|
47
|
+
firstButton.setAttribute('aria-haspopup', 'dialog');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Add click listener imperatively to avoid a11y linter warnings
|
|
52
|
+
// Buttons inside handle keyboard events natively (Enter/Space trigger click)
|
|
53
|
+
wrapperRef?.addEventListener('click', handleClick);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
onDestroy(() => {
|
|
57
|
+
wrapperRef?.removeEventListener('click', handleClick);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
$effect(() => {
|
|
61
|
+
if (popoverCtx.triggerRef) {
|
|
62
|
+
popoverCtx.triggerRef.setAttribute('aria-expanded', String(popoverCtx.isOpen));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<div bind:this={wrapperRef} style="display: contents;">
|
|
68
|
+
{#if children}
|
|
69
|
+
{@render children()}
|
|
70
|
+
{/if}
|
|
71
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* Popover.Trigger - Wrapper that auto-wires a trigger element.
|
|
4
|
+
* Must be used inside a Popover.Root.
|
|
5
|
+
*/
|
|
6
|
+
type PopoverTriggerProps = {
|
|
7
|
+
/** Children (trigger button) */
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
};
|
|
10
|
+
declare const PopoverTrigger: import("svelte").Component<PopoverTriggerProps, {}, "">;
|
|
11
|
+
type PopoverTrigger = ReturnType<typeof PopoverTrigger>;
|
|
12
|
+
export default PopoverTrigger;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Portal } from './portal.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Portal } from './portal.svelte';
|