bits-ui 2.15.3 → 2.15.5
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/bits/accordion/accordion.svelte.d.ts +2 -1
- package/dist/bits/accordion/accordion.svelte.js +1 -1
- package/dist/bits/accordion/components/accordion-trigger.svelte +2 -0
- package/dist/bits/context-menu/components/context-menu-content-static.svelte +11 -6
- package/dist/bits/context-menu/components/context-menu-content.svelte +11 -6
- package/dist/bits/dialog/dialog.svelte.d.ts +1 -0
- package/dist/bits/dialog/dialog.svelte.js +3 -0
- package/dist/bits/dropdown-menu/components/dropdown-menu-content-static.svelte +11 -6
- package/dist/bits/dropdown-menu/components/dropdown-menu-content.svelte +11 -6
- package/dist/bits/link-preview/components/link-preview-content-static.svelte +15 -10
- package/dist/bits/link-preview/components/link-preview-content.svelte +15 -10
- package/dist/bits/menu/components/menu-content-static.svelte +11 -12
- package/dist/bits/menu/components/menu-content.svelte +11 -12
- package/dist/bits/menu/components/menu-sub-content-static.svelte +13 -6
- package/dist/bits/menu/components/menu-sub-content.svelte +13 -6
- package/dist/bits/menu/menu.svelte.d.ts +1 -0
- package/dist/bits/menu/menu.svelte.js +2 -0
- package/dist/bits/pin-input/pin-input.svelte.js +2 -2
- package/dist/bits/popover/components/popover-content-static.svelte +13 -6
- package/dist/bits/popover/components/popover-content.svelte +11 -6
- package/dist/bits/popover/popover.svelte.d.ts +2 -0
- package/dist/bits/popover/popover.svelte.js +14 -1
- package/dist/bits/scroll-area/scroll-area.svelte.d.ts +2 -0
- package/dist/bits/scroll-area/scroll-area.svelte.js +14 -4
- package/dist/bits/select/components/select-content-static.svelte +3 -2
- package/dist/bits/select/components/select-content.svelte +3 -2
- package/dist/bits/tooltip/components/tooltip-content-static.svelte +15 -10
- package/dist/bits/tooltip/components/tooltip-content.svelte +15 -10
- package/dist/bits/tooltip/components/tooltip-trigger.svelte +2 -0
- package/dist/bits/tooltip/tooltip.svelte.d.ts +2 -1
- package/dist/bits/tooltip/tooltip.svelte.js +1 -1
- package/dist/bits/utilities/focus-scope/focus-scope.svelte.js +1 -1
- package/dist/bits/utilities/presence-layer/presence.svelte.d.ts +9 -1
- package/dist/bits/utilities/presence-layer/presence.svelte.js +52 -10
- package/package.json +3 -3
|
@@ -26,6 +26,7 @@ interface AccordionItemStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
|
|
26
26
|
}
|
|
27
27
|
interface AccordionTriggerStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
|
28
28
|
disabled: boolean | null | undefined;
|
|
29
|
+
tabindex: number;
|
|
29
30
|
}> {
|
|
30
31
|
}
|
|
31
32
|
interface AccordionContentStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
|
@@ -111,7 +112,7 @@ export declare class AccordionTriggerState {
|
|
|
111
112
|
readonly "data-disabled": "" | undefined;
|
|
112
113
|
readonly "data-state": "open" | "closed";
|
|
113
114
|
readonly "data-orientation": Orientation;
|
|
114
|
-
readonly tabindex:
|
|
115
|
+
readonly tabindex: number;
|
|
115
116
|
readonly onclick: (e: BitsMouseEvent) => void;
|
|
116
117
|
readonly onkeydown: (e: BitsKeyboardEvent) => void;
|
|
117
118
|
};
|
|
@@ -155,7 +155,7 @@ export class AccordionTriggerState {
|
|
|
155
155
|
"data-state": getDataOpenClosed(this.itemState.isActive),
|
|
156
156
|
"data-orientation": this.#root.opts.orientation.current,
|
|
157
157
|
[accordionAttrs.trigger]: "",
|
|
158
|
-
tabindex:
|
|
158
|
+
tabindex: this.opts.tabindex.current,
|
|
159
159
|
onclick: this.onclick,
|
|
160
160
|
onkeydown: this.onkeydown,
|
|
161
161
|
...this.attachment,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
disabled = false,
|
|
11
11
|
ref = $bindable(null),
|
|
12
12
|
id = createId(uid),
|
|
13
|
+
tabindex = 0,
|
|
13
14
|
children,
|
|
14
15
|
child,
|
|
15
16
|
...restProps
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
const triggerState = AccordionTriggerState.create({
|
|
19
20
|
disabled: boxWith(() => disabled),
|
|
20
21
|
id: boxWith(() => id),
|
|
22
|
+
tabindex: boxWith(() => tabindex ?? 0),
|
|
21
23
|
ref: boxWith(
|
|
22
24
|
() => ref,
|
|
23
25
|
(v) => (ref = v)
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// the default menu behavior of handling outside interactions on the trigger
|
|
22
22
|
onEscapeKeydown = noop,
|
|
23
23
|
forceMount = false,
|
|
24
|
+
style,
|
|
24
25
|
...restProps
|
|
25
26
|
}: ContextMenuContentStaticProps = $props();
|
|
26
27
|
|
|
@@ -82,9 +83,11 @@
|
|
|
82
83
|
shouldRender={contentState.shouldRender}
|
|
83
84
|
>
|
|
84
85
|
{#snippet popper({ props })}
|
|
85
|
-
{@const finalProps = mergeProps(
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
{@const finalProps = mergeProps(
|
|
87
|
+
props,
|
|
88
|
+
{ style: getFloatingContentCSSVars("context-menu") },
|
|
89
|
+
{ style }
|
|
90
|
+
)}
|
|
88
91
|
{#if child}
|
|
89
92
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
90
93
|
{:else}
|
|
@@ -115,9 +118,11 @@
|
|
|
115
118
|
shouldRender={contentState.shouldRender}
|
|
116
119
|
>
|
|
117
120
|
{#snippet popper({ props })}
|
|
118
|
-
{@const finalProps = mergeProps(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
{@const finalProps = mergeProps(
|
|
122
|
+
props,
|
|
123
|
+
{ style: getFloatingContentCSSVars("context-menu") },
|
|
124
|
+
{ style }
|
|
125
|
+
)}
|
|
121
126
|
{#if child}
|
|
122
127
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
123
128
|
{:else}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
onEscapeKeydown = noop,
|
|
27
27
|
forceMount = false,
|
|
28
28
|
trapFocus = false,
|
|
29
|
+
style,
|
|
29
30
|
...restProps
|
|
30
31
|
}: ContextMenuContentProps = $props();
|
|
31
32
|
|
|
@@ -95,9 +96,11 @@
|
|
|
95
96
|
enabled={contentState.parentMenu.opts.open.current}
|
|
96
97
|
>
|
|
97
98
|
{#snippet popper({ props, wrapperProps })}
|
|
98
|
-
{@const finalProps = mergeProps(
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
{@const finalProps = mergeProps(
|
|
100
|
+
props,
|
|
101
|
+
{ style: getFloatingContentCSSVars("context-menu") },
|
|
102
|
+
{ style }
|
|
103
|
+
)}
|
|
101
104
|
{#if child}
|
|
102
105
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
103
106
|
{:else}
|
|
@@ -116,9 +119,11 @@
|
|
|
116
119
|
open={contentState.parentMenu.opts.open.current}
|
|
117
120
|
>
|
|
118
121
|
{#snippet popper({ props, wrapperProps })}
|
|
119
|
-
{@const finalProps = mergeProps(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
{@const finalProps = mergeProps(
|
|
123
|
+
props,
|
|
124
|
+
{ style: getFloatingContentCSSVars("context-menu") },
|
|
125
|
+
{ style }
|
|
126
|
+
)}
|
|
122
127
|
{#if child}
|
|
123
128
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
124
129
|
{:else}
|
|
@@ -148,6 +148,7 @@ export declare class DialogContentState {
|
|
|
148
148
|
readonly outline: "none" | undefined;
|
|
149
149
|
readonly "--bits-dialog-depth": number;
|
|
150
150
|
readonly "--bits-dialog-nested-count": number;
|
|
151
|
+
readonly contain: "layout style paint";
|
|
151
152
|
};
|
|
152
153
|
readonly tabindex: -1 | undefined;
|
|
153
154
|
readonly "data-nested-open": "" | undefined;
|
|
@@ -272,6 +272,9 @@ export class DialogContentState {
|
|
|
272
272
|
outline: this.root.opts.variant.current === "alert-dialog" ? "none" : undefined,
|
|
273
273
|
"--bits-dialog-depth": this.root.depth,
|
|
274
274
|
"--bits-dialog-nested-count": this.root.nestedOpenCount,
|
|
275
|
+
// CSS containment isolates style/layout/paint calculations from the rest of the page,
|
|
276
|
+
// improving performance when there's a large DOM behind the dialog
|
|
277
|
+
contain: "layout style paint",
|
|
275
278
|
},
|
|
276
279
|
tabindex: this.root.opts.variant.current === "alert-dialog" ? -1 : undefined,
|
|
277
280
|
"data-nested-open": boolToEmptyStrOrUndef(this.root.nestedOpenCount > 0),
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
onEscapeKeydown = noop,
|
|
21
21
|
onCloseAutoFocus = noop,
|
|
22
22
|
forceMount = false,
|
|
23
|
+
style,
|
|
23
24
|
...restProps
|
|
24
25
|
}: DropdownMenuContentStaticProps = $props();
|
|
25
26
|
|
|
@@ -65,9 +66,11 @@
|
|
|
65
66
|
shouldRender={contentState.shouldRender}
|
|
66
67
|
>
|
|
67
68
|
{#snippet popper({ props })}
|
|
68
|
-
{@const finalProps = mergeProps(
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
{@const finalProps = mergeProps(
|
|
70
|
+
props,
|
|
71
|
+
{ style: getFloatingContentCSSVars("dropdown-menu") },
|
|
72
|
+
{ style }
|
|
73
|
+
)}
|
|
71
74
|
{#if child}
|
|
72
75
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
73
76
|
{:else}
|
|
@@ -93,9 +96,11 @@
|
|
|
93
96
|
shouldRender={contentState.shouldRender}
|
|
94
97
|
>
|
|
95
98
|
{#snippet popper({ props })}
|
|
96
|
-
{@const finalProps = mergeProps(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
{@const finalProps = mergeProps(
|
|
100
|
+
props,
|
|
101
|
+
{ style: getFloatingContentCSSVars("dropdown-menu") },
|
|
102
|
+
{ style }
|
|
103
|
+
)}
|
|
99
104
|
{#if child}
|
|
100
105
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
101
106
|
{:else}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
onCloseAutoFocus = noop,
|
|
22
22
|
forceMount = false,
|
|
23
23
|
trapFocus = false,
|
|
24
|
+
style,
|
|
24
25
|
...restProps
|
|
25
26
|
}: DropdownMenuContentProps = $props();
|
|
26
27
|
|
|
@@ -71,9 +72,11 @@
|
|
|
71
72
|
shouldRender={contentState.shouldRender}
|
|
72
73
|
>
|
|
73
74
|
{#snippet popper({ props, wrapperProps })}
|
|
74
|
-
{@const finalProps = mergeProps(
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
{@const finalProps = mergeProps(
|
|
76
|
+
props,
|
|
77
|
+
{ style: getFloatingContentCSSVars("dropdown-menu") },
|
|
78
|
+
{ style }
|
|
79
|
+
)}
|
|
77
80
|
{#if child}
|
|
78
81
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
79
82
|
{:else}
|
|
@@ -100,9 +103,11 @@
|
|
|
100
103
|
shouldRender={contentState.shouldRender}
|
|
101
104
|
>
|
|
102
105
|
{#snippet popper({ props, wrapperProps })}
|
|
103
|
-
{@const finalProps = mergeProps(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
{@const finalProps = mergeProps(
|
|
107
|
+
props,
|
|
108
|
+
{ style: getFloatingContentCSSVars("dropdown-menu") },
|
|
109
|
+
{ style }
|
|
110
|
+
)}
|
|
106
111
|
{#if child}
|
|
107
112
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
108
113
|
{:else}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
onInteractOutside = noop,
|
|
20
20
|
onEscapeKeydown = noop,
|
|
21
21
|
forceMount = false,
|
|
22
|
+
style,
|
|
22
23
|
...restProps
|
|
23
24
|
}: LinkPreviewContentStaticProps = $props();
|
|
24
25
|
|
|
@@ -50,13 +51,15 @@
|
|
|
50
51
|
shouldRender={contentState.shouldRender}
|
|
51
52
|
>
|
|
52
53
|
{#snippet popper({ props })}
|
|
53
|
-
{@const
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
{@const finalProps = mergeProps(
|
|
55
|
+
props,
|
|
56
|
+
{ style: getFloatingContentCSSVars("link-preview") },
|
|
57
|
+
{ style }
|
|
58
|
+
)}
|
|
56
59
|
{#if child}
|
|
57
|
-
{@render child({ props:
|
|
60
|
+
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
58
61
|
{:else}
|
|
59
|
-
<div {...
|
|
62
|
+
<div {...finalProps}>
|
|
60
63
|
{@render children?.()}
|
|
61
64
|
</div>
|
|
62
65
|
{/if}
|
|
@@ -78,13 +81,15 @@
|
|
|
78
81
|
shouldRender={contentState.shouldRender}
|
|
79
82
|
>
|
|
80
83
|
{#snippet popper({ props })}
|
|
81
|
-
{@const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
{@const finalProps = mergeProps(
|
|
85
|
+
props,
|
|
86
|
+
{ style: getFloatingContentCSSVars("link-preview") },
|
|
87
|
+
{ style }
|
|
88
|
+
)}
|
|
84
89
|
{#if child}
|
|
85
|
-
{@render child({ props:
|
|
90
|
+
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
86
91
|
{:else}
|
|
87
|
-
<div {...
|
|
92
|
+
<div {...finalProps}>
|
|
88
93
|
{@render children?.()}
|
|
89
94
|
</div>
|
|
90
95
|
{/if}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
onInteractOutside = noop,
|
|
28
28
|
onEscapeKeydown = noop,
|
|
29
29
|
forceMount = false,
|
|
30
|
+
style,
|
|
30
31
|
...restProps
|
|
31
32
|
}: LinkPreviewContentProps = $props();
|
|
32
33
|
|
|
@@ -68,14 +69,16 @@
|
|
|
68
69
|
shouldRender={contentState.shouldRender}
|
|
69
70
|
>
|
|
70
71
|
{#snippet popper({ props, wrapperProps })}
|
|
71
|
-
{@const
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
{@const finalProps = mergeProps(
|
|
73
|
+
props,
|
|
74
|
+
{ style: getFloatingContentCSSVars("link-preview") },
|
|
75
|
+
{ style }
|
|
76
|
+
)}
|
|
74
77
|
{#if child}
|
|
75
|
-
{@render child({ props:
|
|
78
|
+
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
76
79
|
{:else}
|
|
77
80
|
<div {...wrapperProps}>
|
|
78
|
-
<div {...
|
|
81
|
+
<div {...finalProps}>
|
|
79
82
|
{@render children?.()}
|
|
80
83
|
</div>
|
|
81
84
|
</div>
|
|
@@ -97,14 +100,16 @@
|
|
|
97
100
|
shouldRender={contentState.shouldRender}
|
|
98
101
|
>
|
|
99
102
|
{#snippet popper({ props, wrapperProps })}
|
|
100
|
-
{@const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
{@const finalProps = mergeProps(
|
|
104
|
+
props,
|
|
105
|
+
{ style: getFloatingContentCSSVars("link-preview") },
|
|
106
|
+
{ style }
|
|
107
|
+
)}
|
|
103
108
|
{#if child}
|
|
104
|
-
{@render child({ props:
|
|
109
|
+
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
105
110
|
{:else}
|
|
106
111
|
<div {...wrapperProps}>
|
|
107
|
-
<div {...
|
|
112
|
+
<div {...finalProps}>
|
|
108
113
|
{@render children?.()}
|
|
109
114
|
</div>
|
|
110
115
|
</div>
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
onEscapeKeydown = noop,
|
|
21
21
|
onCloseAutoFocus: onCloseAutoFocusProp = noop,
|
|
22
22
|
forceMount = false,
|
|
23
|
+
style,
|
|
23
24
|
...restProps
|
|
24
25
|
}: MenuContentStaticProps = $props();
|
|
25
26
|
|
|
@@ -68,12 +69,11 @@
|
|
|
68
69
|
shouldRender={contentState.shouldRender}
|
|
69
70
|
>
|
|
70
71
|
{#snippet popper({ props })}
|
|
71
|
-
{@const finalProps = mergeProps(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
})}
|
|
72
|
+
{@const finalProps = mergeProps(
|
|
73
|
+
props,
|
|
74
|
+
{ style: { outline: "none", ...getFloatingContentCSSVars("menu") } },
|
|
75
|
+
{ style }
|
|
76
|
+
)}
|
|
77
77
|
{#if child}
|
|
78
78
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
79
79
|
{:else}
|
|
@@ -99,12 +99,11 @@
|
|
|
99
99
|
shouldRender={contentState.shouldRender}
|
|
100
100
|
>
|
|
101
101
|
{#snippet popper({ props })}
|
|
102
|
-
{@const finalProps = mergeProps(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
})}
|
|
102
|
+
{@const finalProps = mergeProps(
|
|
103
|
+
props,
|
|
104
|
+
{ style: { outline: "none", ...getFloatingContentCSSVars("menu") } },
|
|
105
|
+
{ style }
|
|
106
|
+
)}
|
|
108
107
|
{#if child}
|
|
109
108
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
110
109
|
{:else}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
onEscapeKeydown = noop,
|
|
21
21
|
onCloseAutoFocus: onCloseAutoFocusProp = noop,
|
|
22
22
|
forceMount = false,
|
|
23
|
+
style,
|
|
23
24
|
...restProps
|
|
24
25
|
}: MenuContentProps = $props();
|
|
25
26
|
|
|
@@ -72,12 +73,11 @@
|
|
|
72
73
|
shouldRender={contentState.shouldRender}
|
|
73
74
|
>
|
|
74
75
|
{#snippet popper({ props, wrapperProps })}
|
|
75
|
-
{@const finalProps = mergeProps(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})}
|
|
76
|
+
{@const finalProps = mergeProps(
|
|
77
|
+
props,
|
|
78
|
+
{ style: { outline: "none", ...getFloatingContentCSSVars("menu") } },
|
|
79
|
+
{ style }
|
|
80
|
+
)}
|
|
81
81
|
{#if child}
|
|
82
82
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
83
83
|
{:else}
|
|
@@ -104,12 +104,11 @@
|
|
|
104
104
|
shouldRender={contentState.shouldRender}
|
|
105
105
|
>
|
|
106
106
|
{#snippet popper({ props, wrapperProps })}
|
|
107
|
-
{@const finalProps = mergeProps(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
})}
|
|
107
|
+
{@const finalProps = mergeProps(
|
|
108
|
+
props,
|
|
109
|
+
{ style: { outline: "none", ...getFloatingContentCSSVars("menu") } },
|
|
110
|
+
{ style }
|
|
111
|
+
)}
|
|
113
112
|
{#if child}
|
|
114
113
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
115
114
|
{:else}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
onCloseAutoFocus: onCloseAutoFocusProp = noop,
|
|
28
28
|
onFocusOutside = noop,
|
|
29
29
|
trapFocus = false,
|
|
30
|
+
style,
|
|
30
31
|
...restProps
|
|
31
32
|
}: MenuSubContentStaticProps = $props();
|
|
32
33
|
|
|
@@ -123,9 +124,12 @@
|
|
|
123
124
|
shouldRender={subContentState.shouldRender}
|
|
124
125
|
>
|
|
125
126
|
{#snippet popper({ props })}
|
|
126
|
-
{@const finalProps = mergeProps(
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
{@const finalProps = mergeProps(
|
|
128
|
+
props,
|
|
129
|
+
mergedProps,
|
|
130
|
+
{ style: getFloatingContentCSSVars("menu") },
|
|
131
|
+
{ style }
|
|
132
|
+
)}
|
|
129
133
|
{#if child}
|
|
130
134
|
{@render child({ props: finalProps, ...subContentState.snippetProps })}
|
|
131
135
|
{:else}
|
|
@@ -154,9 +158,12 @@
|
|
|
154
158
|
shouldRender={subContentState.shouldRender}
|
|
155
159
|
>
|
|
156
160
|
{#snippet popper({ props })}
|
|
157
|
-
{@const finalProps = mergeProps(
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
{@const finalProps = mergeProps(
|
|
162
|
+
props,
|
|
163
|
+
mergedProps,
|
|
164
|
+
{ style: getFloatingContentCSSVars("menu") },
|
|
165
|
+
{ style }
|
|
166
|
+
)}
|
|
160
167
|
{#if child}
|
|
161
168
|
{@render child({ props: finalProps, ...subContentState.snippetProps })}
|
|
162
169
|
{:else}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
onFocusOutside = noop,
|
|
29
29
|
side = "right",
|
|
30
30
|
trapFocus = false,
|
|
31
|
+
style,
|
|
31
32
|
...restProps
|
|
32
33
|
}: MenuSubContentProps = $props();
|
|
33
34
|
|
|
@@ -124,9 +125,12 @@
|
|
|
124
125
|
shouldRender={subContentState.shouldRender}
|
|
125
126
|
>
|
|
126
127
|
{#snippet popper({ props, wrapperProps })}
|
|
127
|
-
{@const finalProps = mergeProps(
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
{@const finalProps = mergeProps(
|
|
129
|
+
props,
|
|
130
|
+
mergedProps,
|
|
131
|
+
{ style: getFloatingContentCSSVars("menu") },
|
|
132
|
+
{ style }
|
|
133
|
+
)}
|
|
130
134
|
{#if child}
|
|
131
135
|
{@render child({
|
|
132
136
|
props: finalProps,
|
|
@@ -160,9 +164,12 @@
|
|
|
160
164
|
shouldRender={subContentState.shouldRender}
|
|
161
165
|
>
|
|
162
166
|
{#snippet popper({ props, wrapperProps })}
|
|
163
|
-
{@const finalProps = mergeProps(
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
{@const finalProps = mergeProps(
|
|
168
|
+
props,
|
|
169
|
+
mergedProps,
|
|
170
|
+
{ style: getFloatingContentCSSVars("menu") },
|
|
171
|
+
{ style }
|
|
172
|
+
)}
|
|
166
173
|
{#if child}
|
|
167
174
|
{@render child({
|
|
168
175
|
props: finalProps,
|
|
@@ -324,6 +324,8 @@ export class MenuContentState {
|
|
|
324
324
|
dir: this.parentMenu.root.opts.dir.current,
|
|
325
325
|
style: {
|
|
326
326
|
pointerEvents: "auto",
|
|
327
|
+
// CSS containment isolates style/layout/paint calculations from the rest of the page
|
|
328
|
+
contain: "layout style paint",
|
|
327
329
|
},
|
|
328
330
|
...this.attachment,
|
|
329
331
|
}));
|
|
@@ -249,13 +249,13 @@ export class PinInputRootState {
|
|
|
249
249
|
else if (maxLength > 1 && val.length > 1) {
|
|
250
250
|
let offset = 0;
|
|
251
251
|
if (prev[0] !== null && prev[1] !== null) {
|
|
252
|
-
direction = c < prev[
|
|
252
|
+
direction = c < prev[1] ? "backward" : "forward";
|
|
253
253
|
const wasPreviouslyInserting = prev[0] === prev[1] && prev[0] < maxLength;
|
|
254
254
|
if (direction === "backward" && !wasPreviouslyInserting) {
|
|
255
255
|
offset = -1;
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
-
start = offset
|
|
258
|
+
start = offset + c;
|
|
259
259
|
end = offset + c + 1;
|
|
260
260
|
}
|
|
261
261
|
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
onInteractOutside = noop,
|
|
22
22
|
trapFocus = true,
|
|
23
23
|
preventScroll = false,
|
|
24
|
+
style,
|
|
24
25
|
...restProps
|
|
25
26
|
}: PopoverContentStaticProps = $props();
|
|
26
27
|
|
|
@@ -54,9 +55,13 @@
|
|
|
54
55
|
shouldRender={contentState.shouldRender}
|
|
55
56
|
>
|
|
56
57
|
{#snippet popper({ props })}
|
|
57
|
-
{@const finalProps = mergeProps(
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
{@const finalProps = mergeProps(
|
|
59
|
+
props,
|
|
60
|
+
{
|
|
61
|
+
style: getFloatingContentCSSVars("popover"),
|
|
62
|
+
},
|
|
63
|
+
{ style }
|
|
64
|
+
)}
|
|
60
65
|
{#if child}
|
|
61
66
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
62
67
|
{:else}
|
|
@@ -82,9 +87,11 @@
|
|
|
82
87
|
shouldRender={contentState.shouldRender}
|
|
83
88
|
>
|
|
84
89
|
{#snippet popper({ props })}
|
|
85
|
-
{@const finalProps = mergeProps(
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
{@const finalProps = mergeProps(
|
|
91
|
+
props,
|
|
92
|
+
{ style: getFloatingContentCSSVars("popover") },
|
|
93
|
+
{ style }
|
|
94
|
+
)}
|
|
88
95
|
{#if child}
|
|
89
96
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
90
97
|
{:else}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
trapFocus = true,
|
|
24
24
|
preventScroll = false,
|
|
25
25
|
customAnchor = null,
|
|
26
|
+
style,
|
|
26
27
|
...restProps
|
|
27
28
|
}: PopoverContentProps = $props();
|
|
28
29
|
|
|
@@ -68,9 +69,11 @@
|
|
|
68
69
|
shouldRender={contentState.shouldRender}
|
|
69
70
|
>
|
|
70
71
|
{#snippet popper({ props, wrapperProps })}
|
|
71
|
-
{@const finalProps = mergeProps(
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
{@const finalProps = mergeProps(
|
|
73
|
+
props,
|
|
74
|
+
{ style: getFloatingContentCSSVars("popover") },
|
|
75
|
+
{ style }
|
|
76
|
+
)}
|
|
74
77
|
{#if child}
|
|
75
78
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
76
79
|
{:else}
|
|
@@ -99,9 +102,11 @@
|
|
|
99
102
|
shouldRender={contentState.shouldRender}
|
|
100
103
|
>
|
|
101
104
|
{#snippet popper({ props, wrapperProps })}
|
|
102
|
-
{@const finalProps = mergeProps(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
{@const finalProps = mergeProps(
|
|
106
|
+
props,
|
|
107
|
+
{ style: getFloatingContentCSSVars("popover") },
|
|
108
|
+
{ style }
|
|
109
|
+
)}
|
|
105
110
|
{#if child}
|
|
106
111
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
107
112
|
{:else}
|
|
@@ -19,6 +19,7 @@ export declare class PopoverRootState {
|
|
|
19
19
|
overlayPresence: PresenceManager;
|
|
20
20
|
openedViaHover: boolean;
|
|
21
21
|
hasInteractedWithContent: boolean;
|
|
22
|
+
hoverCooldown: boolean;
|
|
22
23
|
closeDelay: number;
|
|
23
24
|
constructor(opts: PopoverRootStateOpts);
|
|
24
25
|
setDomContext(ctx: DOMContext): void;
|
|
@@ -91,6 +92,7 @@ export declare class PopoverContentState {
|
|
|
91
92
|
readonly "data-state": "open" | "closed";
|
|
92
93
|
readonly style: {
|
|
93
94
|
readonly pointerEvents: "auto";
|
|
95
|
+
readonly contain: "layout style paint";
|
|
94
96
|
};
|
|
95
97
|
readonly onpointerdown: (_: BitsPointerEvent) => void;
|
|
96
98
|
readonly onfocusin: (e: BitsFocusEvent) => void;
|
|
@@ -24,6 +24,7 @@ export class PopoverRootState {
|
|
|
24
24
|
// hover tracking state
|
|
25
25
|
openedViaHover = $state(false);
|
|
26
26
|
hasInteractedWithContent = $state(false);
|
|
27
|
+
hoverCooldown = $state(false);
|
|
27
28
|
closeDelay = $state(0);
|
|
28
29
|
#closeTimeout = null;
|
|
29
30
|
#domContext = null;
|
|
@@ -159,7 +160,7 @@ export class PopoverTriggerState {
|
|
|
159
160
|
this.#isHovering = true;
|
|
160
161
|
this.#clearCloseTimeout();
|
|
161
162
|
this.root.cancelDelayedClose();
|
|
162
|
-
if (this.root.opts.open.current)
|
|
163
|
+
if (this.root.opts.open.current || this.root.hoverCooldown)
|
|
163
164
|
return;
|
|
164
165
|
const delay = this.opts.openDelay.current;
|
|
165
166
|
if (delay <= 0) {
|
|
@@ -181,6 +182,7 @@ export class PopoverTriggerState {
|
|
|
181
182
|
return;
|
|
182
183
|
this.#isHovering = false;
|
|
183
184
|
this.#clearOpenTimeout();
|
|
185
|
+
this.root.hoverCooldown = false;
|
|
184
186
|
// let GraceArea handle the close - it will call handleHoverClose via onPointerExit
|
|
185
187
|
// we just need to stop any pending open timer
|
|
186
188
|
}
|
|
@@ -196,6 +198,15 @@ export class PopoverTriggerState {
|
|
|
196
198
|
this.root.hasInteractedWithContent = true;
|
|
197
199
|
return;
|
|
198
200
|
}
|
|
201
|
+
// if closing while hovering with openOnHover enabled, set cooldown to prevent
|
|
202
|
+
// immediate re-open via hover
|
|
203
|
+
if (this.#isHovering && this.opts.openOnHover.current && this.root.opts.open.current) {
|
|
204
|
+
this.root.hoverCooldown = true;
|
|
205
|
+
}
|
|
206
|
+
// if clicking to open while in cooldown, reset cooldown (explicit open)
|
|
207
|
+
if (this.root.hoverCooldown && !this.root.opts.open.current) {
|
|
208
|
+
this.root.hoverCooldown = false;
|
|
209
|
+
}
|
|
199
210
|
this.root.toggleOpen();
|
|
200
211
|
}
|
|
201
212
|
onkeydown(e) {
|
|
@@ -317,6 +328,8 @@ export class PopoverContentState {
|
|
|
317
328
|
[popoverAttrs.content]: "",
|
|
318
329
|
style: {
|
|
319
330
|
pointerEvents: "auto",
|
|
331
|
+
// CSS containment isolates style/layout/paint calculations from the rest of the page
|
|
332
|
+
contain: "layout style paint",
|
|
320
333
|
},
|
|
321
334
|
onpointerdown: this.onpointerdown,
|
|
322
335
|
onfocusin: this.onfocusin,
|
|
@@ -265,6 +265,7 @@ export declare class ScrollAreaScrollbarYState implements ScrollbarAxisState {
|
|
|
265
265
|
}
|
|
266
266
|
type ScrollbarAxis = ScrollAreaScrollbarXState | ScrollAreaScrollbarYState;
|
|
267
267
|
export declare class ScrollAreaScrollbarSharedState {
|
|
268
|
+
#private;
|
|
268
269
|
static create(): ScrollAreaScrollbarSharedState;
|
|
269
270
|
readonly scrollbarState: ScrollbarAxis;
|
|
270
271
|
readonly root: ScrollAreaRootState;
|
|
@@ -286,6 +287,7 @@ export declare class ScrollAreaScrollbarSharedState {
|
|
|
286
287
|
onpointerdown(e: BitsPointerEvent): void;
|
|
287
288
|
onpointermove(e: BitsPointerEvent): void;
|
|
288
289
|
onpointerup(e: BitsPointerEvent): void;
|
|
290
|
+
onlostpointercapture(_: BitsPointerEvent): void;
|
|
289
291
|
readonly props: never;
|
|
290
292
|
}
|
|
291
293
|
interface ScrollAreaThumbImplStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
|
@@ -571,6 +571,7 @@ export class ScrollAreaScrollbarSharedState {
|
|
|
571
571
|
this.onpointerdown = this.onpointerdown.bind(this);
|
|
572
572
|
this.onpointermove = this.onpointermove.bind(this);
|
|
573
573
|
this.onpointerup = this.onpointerup.bind(this);
|
|
574
|
+
this.onlostpointercapture = this.onlostpointercapture.bind(this);
|
|
574
575
|
}
|
|
575
576
|
handleDragScroll(e) {
|
|
576
577
|
if (!this.rect)
|
|
@@ -579,6 +580,14 @@ export class ScrollAreaScrollbarSharedState {
|
|
|
579
580
|
const y = e.clientY - this.rect.top;
|
|
580
581
|
this.scrollbarState.onDragScroll({ x, y });
|
|
581
582
|
}
|
|
583
|
+
#cleanupPointerState() {
|
|
584
|
+
if (this.rect === null)
|
|
585
|
+
return;
|
|
586
|
+
this.root.domContext.getDocument().body.style.webkitUserSelect = this.prevWebkitUserSelect;
|
|
587
|
+
if (this.root.viewportNode)
|
|
588
|
+
this.root.viewportNode.style.scrollBehavior = "";
|
|
589
|
+
this.rect = null;
|
|
590
|
+
}
|
|
582
591
|
onpointerdown(e) {
|
|
583
592
|
if (e.button !== 0)
|
|
584
593
|
return;
|
|
@@ -601,10 +610,10 @@ export class ScrollAreaScrollbarSharedState {
|
|
|
601
610
|
if (target.hasPointerCapture(e.pointerId)) {
|
|
602
611
|
target.releasePointerCapture(e.pointerId);
|
|
603
612
|
}
|
|
604
|
-
this
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
this
|
|
613
|
+
this.#cleanupPointerState();
|
|
614
|
+
}
|
|
615
|
+
onlostpointercapture(_) {
|
|
616
|
+
this.#cleanupPointerState();
|
|
608
617
|
}
|
|
609
618
|
props = $derived.by(() => mergeProps({
|
|
610
619
|
...this.scrollbarState.props,
|
|
@@ -616,6 +625,7 @@ export class ScrollAreaScrollbarSharedState {
|
|
|
616
625
|
onpointerdown: this.onpointerdown,
|
|
617
626
|
onpointermove: this.onpointermove,
|
|
618
627
|
onpointerup: this.onpointerup,
|
|
628
|
+
onlostpointercapture: this.onlostpointercapture,
|
|
619
629
|
}));
|
|
620
630
|
}
|
|
621
631
|
export class ScrollAreaThumbImplState {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
children,
|
|
19
19
|
child,
|
|
20
20
|
preventScroll = false,
|
|
21
|
+
style,
|
|
21
22
|
...restProps
|
|
22
23
|
}: SelectContentStaticProps = $props();
|
|
23
24
|
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
shouldRender={contentState.shouldRender}
|
|
48
49
|
>
|
|
49
50
|
{#snippet popper({ props })}
|
|
50
|
-
{@const finalProps = mergeProps(props, { style: contentState.props.style })}
|
|
51
|
+
{@const finalProps = mergeProps(props, { style: contentState.props.style }, { style })}
|
|
51
52
|
{#if child}
|
|
52
53
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
53
54
|
{:else}
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
shouldRender={contentState.shouldRender}
|
|
71
72
|
>
|
|
72
73
|
{#snippet popper({ props })}
|
|
73
|
-
{@const finalProps = mergeProps(props, { style: contentState.props.style })}
|
|
74
|
+
{@const finalProps = mergeProps(props, { style: contentState.props.style }, { style })}
|
|
74
75
|
{#if child}
|
|
75
76
|
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
76
77
|
{:else}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
children,
|
|
20
20
|
child,
|
|
21
21
|
preventScroll = false,
|
|
22
|
+
style,
|
|
22
23
|
...restProps
|
|
23
24
|
}: SelectContentProps = $props();
|
|
24
25
|
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
shouldRender={contentState.shouldRender}
|
|
49
50
|
>
|
|
50
51
|
{#snippet popper({ props, wrapperProps })}
|
|
51
|
-
{@const finalProps = mergeProps(props, { style: contentState.props.style })}
|
|
52
|
+
{@const finalProps = mergeProps(props, { style: contentState.props.style }, { style })}
|
|
52
53
|
{#if child}
|
|
53
54
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
54
55
|
{:else}
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
shouldRender={contentState.shouldRender}
|
|
74
75
|
>
|
|
75
76
|
{#snippet popper({ props, wrapperProps })}
|
|
76
|
-
{@const finalProps = mergeProps(props, { style: contentState.props.style })}
|
|
77
|
+
{@const finalProps = mergeProps(props, { style: contentState.props.style }, { style })}
|
|
77
78
|
{#if child}
|
|
78
79
|
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
79
80
|
{:else}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
onInteractOutside = noop,
|
|
19
19
|
onEscapeKeydown = noop,
|
|
20
20
|
forceMount = false,
|
|
21
|
+
style,
|
|
21
22
|
...restProps
|
|
22
23
|
}: TooltipContentStaticProps = $props();
|
|
23
24
|
|
|
@@ -50,13 +51,15 @@
|
|
|
50
51
|
shouldRender={contentState.shouldRender}
|
|
51
52
|
>
|
|
52
53
|
{#snippet popper({ props })}
|
|
53
|
-
{@const
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
{@const finalProps = mergeProps(
|
|
55
|
+
props,
|
|
56
|
+
{ style: getFloatingContentCSSVars("tooltip") },
|
|
57
|
+
{ style }
|
|
58
|
+
)}
|
|
56
59
|
{#if child}
|
|
57
|
-
{@render child({ props:
|
|
60
|
+
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
58
61
|
{:else}
|
|
59
|
-
<div {...
|
|
62
|
+
<div {...finalProps}>
|
|
60
63
|
{@render children?.()}
|
|
61
64
|
</div>
|
|
62
65
|
{/if}
|
|
@@ -78,13 +81,15 @@
|
|
|
78
81
|
shouldRender={contentState.shouldRender}
|
|
79
82
|
>
|
|
80
83
|
{#snippet popper({ props })}
|
|
81
|
-
{@const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
{@const finalProps = mergeProps(
|
|
85
|
+
props,
|
|
86
|
+
{ style: getFloatingContentCSSVars("tooltip") },
|
|
87
|
+
{ style }
|
|
88
|
+
)}
|
|
84
89
|
{#if child}
|
|
85
|
-
{@render child({ props:
|
|
90
|
+
{@render child({ props: finalProps, ...contentState.snippetProps })}
|
|
86
91
|
{:else}
|
|
87
|
-
<div {...
|
|
92
|
+
<div {...finalProps}>
|
|
88
93
|
{@render children?.()}
|
|
89
94
|
</div>
|
|
90
95
|
{/if}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
onInteractOutside = noop,
|
|
28
28
|
onEscapeKeydown = noop,
|
|
29
29
|
forceMount = false,
|
|
30
|
+
style,
|
|
30
31
|
...restProps
|
|
31
32
|
}: TooltipContentProps = $props();
|
|
32
33
|
|
|
@@ -71,14 +72,16 @@
|
|
|
71
72
|
contentPointerEvents={contentState.root.disableHoverableContent ? "none" : "auto"}
|
|
72
73
|
>
|
|
73
74
|
{#snippet popper({ props, wrapperProps })}
|
|
74
|
-
{@const
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
{@const finalProps = mergeProps(
|
|
76
|
+
props,
|
|
77
|
+
{ style: getFloatingContentCSSVars("tooltip") },
|
|
78
|
+
{ style }
|
|
79
|
+
)}
|
|
77
80
|
{#if child}
|
|
78
|
-
{@render child({ props:
|
|
81
|
+
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
79
82
|
{:else}
|
|
80
83
|
<div {...wrapperProps}>
|
|
81
|
-
<div {...
|
|
84
|
+
<div {...finalProps}>
|
|
82
85
|
{@render children?.()}
|
|
83
86
|
</div>
|
|
84
87
|
</div>
|
|
@@ -101,14 +104,16 @@
|
|
|
101
104
|
contentPointerEvents={contentState.root.disableHoverableContent ? "none" : "auto"}
|
|
102
105
|
>
|
|
103
106
|
{#snippet popper({ props, wrapperProps })}
|
|
104
|
-
{@const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
{@const finalProps = mergeProps(
|
|
108
|
+
props,
|
|
109
|
+
{ style: getFloatingContentCSSVars("tooltip") },
|
|
110
|
+
{ style }
|
|
111
|
+
)}
|
|
107
112
|
{#if child}
|
|
108
|
-
{@render child({ props:
|
|
113
|
+
{@render child({ props: finalProps, wrapperProps, ...contentState.snippetProps })}
|
|
109
114
|
{:else}
|
|
110
115
|
<div {...wrapperProps}>
|
|
111
|
-
<div {...
|
|
116
|
+
<div {...finalProps}>
|
|
112
117
|
{@render children?.()}
|
|
113
118
|
</div>
|
|
114
119
|
</div>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
id = createId(uid),
|
|
14
14
|
disabled = false,
|
|
15
15
|
type = "button",
|
|
16
|
+
tabindex = 0,
|
|
16
17
|
ref = $bindable(null),
|
|
17
18
|
...restProps
|
|
18
19
|
}: TooltipTriggerProps = $props();
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
const triggerState = TooltipTriggerState.create({
|
|
21
22
|
id: boxWith(() => id),
|
|
22
23
|
disabled: boxWith(() => disabled ?? false),
|
|
24
|
+
tabindex: boxWith(() => tabindex ?? 0),
|
|
23
25
|
ref: boxWith(
|
|
24
26
|
() => ref,
|
|
25
27
|
(v) => (ref = v)
|
|
@@ -56,6 +56,7 @@ export declare class TooltipRootState {
|
|
|
56
56
|
}
|
|
57
57
|
interface TooltipTriggerStateOpts extends WithRefOpts, ReadableBoxedValues<{
|
|
58
58
|
disabled: boolean;
|
|
59
|
+
tabindex: number;
|
|
59
60
|
}> {
|
|
60
61
|
}
|
|
61
62
|
export declare class TooltipTriggerState {
|
|
@@ -73,7 +74,7 @@ export declare class TooltipTriggerState {
|
|
|
73
74
|
readonly "data-state": "closed" | "delayed-open" | "instant-open";
|
|
74
75
|
readonly "data-disabled": "" | undefined;
|
|
75
76
|
readonly "data-delay-duration": `${number}`;
|
|
76
|
-
readonly tabindex:
|
|
77
|
+
readonly tabindex: number | undefined;
|
|
77
78
|
readonly disabled: boolean;
|
|
78
79
|
readonly onpointerup: PointerEventHandler<HTMLElement>;
|
|
79
80
|
readonly onpointerdown: PointerEventHandler<HTMLElement>;
|
|
@@ -255,7 +255,7 @@ export class TooltipTriggerState {
|
|
|
255
255
|
"data-disabled": boolToEmptyStrOrUndef(this.#isDisabled),
|
|
256
256
|
"data-delay-duration": `${this.root.delayDuration}`,
|
|
257
257
|
[tooltipAttrs.trigger]: "",
|
|
258
|
-
tabindex: this.#isDisabled ? undefined :
|
|
258
|
+
tabindex: this.#isDisabled ? undefined : this.opts.tabindex.current,
|
|
259
259
|
disabled: this.opts.disabled.current,
|
|
260
260
|
onpointerup: this.#onpointerup,
|
|
261
261
|
onpointerdown: this.#onpointerdown,
|
|
@@ -127,7 +127,7 @@ export class FocusScope {
|
|
|
127
127
|
if (!this.#manager.isActiveScope(this))
|
|
128
128
|
return;
|
|
129
129
|
const tabbables = this.#getTabbables();
|
|
130
|
-
if (tabbables.length
|
|
130
|
+
if (tabbables.length === 0)
|
|
131
131
|
return;
|
|
132
132
|
const first = tabbables[0];
|
|
133
133
|
const last = tabbables[tabbables.length - 1];
|
|
@@ -7,6 +7,14 @@ export interface PresenceOptions extends ReadableBoxedValues<{
|
|
|
7
7
|
}> {
|
|
8
8
|
}
|
|
9
9
|
type PresenceStatus = "unmounted" | "mounted" | "unmountSuspended";
|
|
10
|
+
/**
|
|
11
|
+
* Cached style properties to avoid storing live CSSStyleDeclaration
|
|
12
|
+
* which triggers style recalculations when accessed.
|
|
13
|
+
*/
|
|
14
|
+
interface CachedStyles {
|
|
15
|
+
display: string;
|
|
16
|
+
animationName: string;
|
|
17
|
+
}
|
|
10
18
|
declare const presenceMachine: {
|
|
11
19
|
readonly mounted: {
|
|
12
20
|
readonly UNMOUNT: "unmounted";
|
|
@@ -24,7 +32,7 @@ type PresenceMachine = StateMachine<typeof presenceMachine>;
|
|
|
24
32
|
export declare class Presence {
|
|
25
33
|
readonly opts: PresenceOptions;
|
|
26
34
|
prevAnimationNameState: string;
|
|
27
|
-
styles:
|
|
35
|
+
styles: CachedStyles;
|
|
28
36
|
initialStatus: PresenceStatus;
|
|
29
37
|
previousPresent: Previous<boolean>;
|
|
30
38
|
machine: PresenceMachine;
|
|
@@ -2,6 +2,12 @@ import { executeCallbacks } from "svelte-toolbelt";
|
|
|
2
2
|
import { Previous, watch } from "runed";
|
|
3
3
|
import { on } from "svelte/events";
|
|
4
4
|
import { StateMachine } from "../../../internal/state-machine.js";
|
|
5
|
+
/**
|
|
6
|
+
* Cache for animation names with TTL to reduce getComputedStyle calls.
|
|
7
|
+
* Uses WeakMap to avoid memory leaks when elements are removed.
|
|
8
|
+
*/
|
|
9
|
+
const animationNameCache = new WeakMap();
|
|
10
|
+
const ANIMATION_NAME_CACHE_TTL_MS = 16; // One frame at 60fps
|
|
5
11
|
const presenceMachine = {
|
|
6
12
|
mounted: {
|
|
7
13
|
UNMOUNT: "unmounted",
|
|
@@ -18,7 +24,7 @@ const presenceMachine = {
|
|
|
18
24
|
export class Presence {
|
|
19
25
|
opts;
|
|
20
26
|
prevAnimationNameState = $state("none");
|
|
21
|
-
styles = $state({});
|
|
27
|
+
styles = $state({ display: "", animationName: "none" });
|
|
22
28
|
initialStatus;
|
|
23
29
|
previousPresent;
|
|
24
30
|
machine;
|
|
@@ -43,7 +49,8 @@ export class Presence {
|
|
|
43
49
|
handleAnimationEnd(event) {
|
|
44
50
|
if (!this.opts.ref.current)
|
|
45
51
|
return;
|
|
46
|
-
|
|
52
|
+
// Use cached animation name from styles when available to avoid getComputedStyle
|
|
53
|
+
const currAnimationName = this.styles.animationName || getAnimationName(this.opts.ref.current);
|
|
47
54
|
const isCurrentAnimation = currAnimationName.includes(event.animationName) || currAnimationName === "none";
|
|
48
55
|
if (event.target === this.opts.ref.current && isCurrentAnimation) {
|
|
49
56
|
this.machine.dispatch("ANIMATION_END");
|
|
@@ -53,7 +60,11 @@ export class Presence {
|
|
|
53
60
|
if (!this.opts.ref.current)
|
|
54
61
|
return;
|
|
55
62
|
if (event.target === this.opts.ref.current) {
|
|
56
|
-
|
|
63
|
+
// Force refresh cache on animation start to get accurate animation name
|
|
64
|
+
const animationName = getAnimationName(this.opts.ref.current, true);
|
|
65
|
+
this.prevAnimationNameState = animationName;
|
|
66
|
+
// Update styles cache for subsequent reads
|
|
67
|
+
this.styles.animationName = animationName;
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
70
|
isPresent = $derived.by(() => {
|
|
@@ -68,7 +79,10 @@ function watchPresenceChange(state) {
|
|
|
68
79
|
if (!hasPresentChanged)
|
|
69
80
|
return;
|
|
70
81
|
const prevAnimationName = state.prevAnimationNameState;
|
|
71
|
-
|
|
82
|
+
// Force refresh on state change to get accurate current animation
|
|
83
|
+
const currAnimationName = getAnimationName(state.opts.ref.current, true);
|
|
84
|
+
// Update styles cache for subsequent reads
|
|
85
|
+
state.styles.animationName = currAnimationName;
|
|
72
86
|
if (state.present.current) {
|
|
73
87
|
state.machine.dispatch("MOUNT");
|
|
74
88
|
}
|
|
@@ -98,19 +112,47 @@ function watchStatusChange(state) {
|
|
|
98
112
|
watch(() => state.machine.state.current, () => {
|
|
99
113
|
if (!state.opts.ref.current)
|
|
100
114
|
return;
|
|
101
|
-
|
|
102
|
-
state.
|
|
103
|
-
state.
|
|
115
|
+
// Use cached animation name first, only force refresh if needed for mounted state
|
|
116
|
+
const currAnimationName = state.machine.state.current === "mounted"
|
|
117
|
+
? getAnimationName(state.opts.ref.current, true)
|
|
118
|
+
: "none";
|
|
119
|
+
state.prevAnimationNameState = currAnimationName;
|
|
120
|
+
// Update styles cache
|
|
121
|
+
state.styles.animationName = currAnimationName;
|
|
104
122
|
});
|
|
105
123
|
}
|
|
106
124
|
function watchRefChange(state) {
|
|
107
125
|
watch(() => state.opts.ref.current, () => {
|
|
108
126
|
if (!state.opts.ref.current)
|
|
109
127
|
return;
|
|
110
|
-
|
|
128
|
+
// Snapshot only needed style properties instead of storing live CSSStyleDeclaration
|
|
129
|
+
// This avoids triggering style recalculations when accessing the cached object
|
|
130
|
+
const computed = getComputedStyle(state.opts.ref.current);
|
|
131
|
+
state.styles = {
|
|
132
|
+
display: computed.display,
|
|
133
|
+
animationName: computed.animationName || "none",
|
|
134
|
+
};
|
|
111
135
|
return executeCallbacks(on(state.opts.ref.current, "animationstart", state.handleAnimationStart), on(state.opts.ref.current, "animationcancel", state.handleAnimationEnd), on(state.opts.ref.current, "animationend", state.handleAnimationEnd));
|
|
112
136
|
});
|
|
113
137
|
}
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Gets the animation name from computed styles with optional caching.
|
|
140
|
+
*
|
|
141
|
+
* @param node - The HTML element to get animation name from
|
|
142
|
+
* @param forceRefresh - If true, bypasses the cache and forces a fresh getComputedStyle call
|
|
143
|
+
* @returns The animation name or "none" if not animating
|
|
144
|
+
*/
|
|
145
|
+
function getAnimationName(node, forceRefresh = false) {
|
|
146
|
+
if (!node)
|
|
147
|
+
return "none";
|
|
148
|
+
const now = performance.now();
|
|
149
|
+
const cached = animationNameCache.get(node);
|
|
150
|
+
// Return cached value if still valid and not forced to refresh
|
|
151
|
+
if (!forceRefresh && cached && now - cached.timestamp < ANIMATION_NAME_CACHE_TTL_MS) {
|
|
152
|
+
return cached.value;
|
|
153
|
+
}
|
|
154
|
+
// Compute and cache the new value
|
|
155
|
+
const value = getComputedStyle(node).animationName || "none";
|
|
156
|
+
animationNameCache.set(node, { value, timestamp: now });
|
|
157
|
+
return value;
|
|
116
158
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bits-ui",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "github:huntabyte/bits-ui",
|
|
6
6
|
"funding": "https://github.com/sponsors/huntabyte",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@internationalized/date": "^3.8.2",
|
|
23
|
-
"@sveltejs/kit": "
|
|
23
|
+
"@sveltejs/kit": "2.49.5",
|
|
24
24
|
"@sveltejs/package": "2.5.0",
|
|
25
25
|
"@sveltejs/vite-plugin-svelte": "^6.2.0",
|
|
26
26
|
"@types/node": "^20.19.16",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"csstype": "^3.1.3",
|
|
29
29
|
"jsdom": "^24.1.3",
|
|
30
30
|
"publint": "^0.2.12",
|
|
31
|
-
"svelte": "5.
|
|
31
|
+
"svelte": "5.46.4",
|
|
32
32
|
"svelte-check": "^4.3.1",
|
|
33
33
|
"typescript": "^5.9.2",
|
|
34
34
|
"vite": "^7.1.5",
|