bits-ui 1.5.3 → 1.6.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/bits/command/command.svelte.js +3 -2
- package/dist/bits/date-field/date-field.svelte.d.ts +48 -163
- package/dist/bits/date-field/date-field.svelte.js +371 -1202
- package/dist/bits/navigation-menu/components/navigation-menu-content.svelte +11 -13
- package/dist/bits/navigation-menu/navigation-menu.svelte.js +5 -4
- package/dist/bits/slider/components/slider-thumb.svelte +7 -2
- package/dist/bits/slider/slider.svelte.d.ts +4 -0
- package/dist/bits/slider/slider.svelte.js +7 -0
- package/dist/bits/slider/types.d.ts +4 -1
- package/dist/bits/tooltip/tooltip.svelte.d.ts +5 -5
- package/dist/bits/tooltip/tooltip.svelte.js +45 -19
- package/dist/internal/use-grace-area.svelte.d.ts +1 -0
- package/dist/internal/use-grace-area.svelte.js +1 -1
- package/package.json +3 -1
|
@@ -28,16 +28,14 @@
|
|
|
28
28
|
const mergedProps = $derived(mergeProps(restProps, contentState.props));
|
|
29
29
|
</script>
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</Portal>
|
|
43
|
-
{/if}
|
|
31
|
+
<Portal
|
|
32
|
+
to={contentState.context.viewportRef.current || undefined}
|
|
33
|
+
disabled={!contentState.context.viewportRef.current}
|
|
34
|
+
>
|
|
35
|
+
<PresenceLayer {id} present={forceMount || contentState.open || contentState.isLastActiveValue}>
|
|
36
|
+
{#snippet presence()}
|
|
37
|
+
<NavigationMenuContentImpl {...mergedProps} {children} {child} />
|
|
38
|
+
<Mounted bind:mounted={contentState.mounted} />
|
|
39
|
+
{/snippet}
|
|
40
|
+
</PresenceLayer>
|
|
41
|
+
</Portal>
|
|
@@ -173,7 +173,7 @@ class NavigationMenuListState {
|
|
|
173
173
|
});
|
|
174
174
|
this.rovingFocusGroup = useRovingFocus({
|
|
175
175
|
rootNodeId: opts.id,
|
|
176
|
-
candidateSelector:
|
|
176
|
+
candidateSelector: `[${NAVIGATION_MENU_TRIGGER_ATTR}]:not([data-disabled]), [${NAVIGATION_MENU_LINK_ATTR}]:not([data-disabled])`,
|
|
177
177
|
loop: box.with(() => false),
|
|
178
178
|
orientation: this.context.opts.orientation,
|
|
179
179
|
});
|
|
@@ -297,13 +297,14 @@ class NavigationMenuTriggerState {
|
|
|
297
297
|
// if opened via pointer move, we prevent the click event
|
|
298
298
|
if (this.hasPointerMoveOpened.current)
|
|
299
299
|
return;
|
|
300
|
-
|
|
300
|
+
const shouldClose = this.open && this.context.opts.isRootMenu;
|
|
301
|
+
if (shouldClose) {
|
|
301
302
|
this.context.onItemSelect("");
|
|
302
303
|
}
|
|
303
|
-
else {
|
|
304
|
+
else if (!this.open) {
|
|
304
305
|
this.context.onItemSelect(this.itemContext.opts.value.current);
|
|
305
306
|
}
|
|
306
|
-
this.wasClickClose =
|
|
307
|
+
this.wasClickClose = shouldClose;
|
|
307
308
|
};
|
|
308
309
|
onkeydown = (e) => {
|
|
309
310
|
const verticalEntryKey = this.context.opts.dir.current === "rtl" ? kbd.ARROW_LEFT : kbd.ARROW_RIGHT;
|
|
@@ -28,9 +28,14 @@
|
|
|
28
28
|
</script>
|
|
29
29
|
|
|
30
30
|
{#if child}
|
|
31
|
-
{@render child({
|
|
31
|
+
{@render child({
|
|
32
|
+
active: thumbState.root.isThumbActive(thumbState.opts.index.current),
|
|
33
|
+
props: mergedProps,
|
|
34
|
+
})}
|
|
32
35
|
{:else}
|
|
33
36
|
<span {...mergedProps}>
|
|
34
|
-
{@render children?.(
|
|
37
|
+
{@render children?.({
|
|
38
|
+
active: thumbState.root.isThumbActive(thumbState.opts.index.current),
|
|
39
|
+
})}
|
|
35
40
|
</span>
|
|
36
41
|
{/if}
|
|
@@ -17,6 +17,7 @@ declare class SliderBaseRootState {
|
|
|
17
17
|
isActive: boolean;
|
|
18
18
|
direction: "rl" | "lr" | "tb" | "bt";
|
|
19
19
|
constructor(opts: SliderBaseRootStateProps);
|
|
20
|
+
isThumbActive(_index: number): boolean;
|
|
20
21
|
getAllThumbs: () => HTMLElement[];
|
|
21
22
|
getThumbScale: () => [number, number];
|
|
22
23
|
getPositionFromValue: (thumbValue: number) => number;
|
|
@@ -90,6 +91,7 @@ declare class SliderMultiRootState extends SliderBaseRootState {
|
|
|
90
91
|
} | null;
|
|
91
92
|
currentThumbIdx: number;
|
|
92
93
|
constructor(opts: SliderMultiRootStateProps);
|
|
94
|
+
isThumbActive(index: number): boolean;
|
|
93
95
|
applyPosition({ clientXY, activeThumbIdx, start, end, }: {
|
|
94
96
|
clientXY: number;
|
|
95
97
|
activeThumbIdx: number;
|
|
@@ -1807,6 +1809,7 @@ declare class SliderThumbState {
|
|
|
1807
1809
|
props: {
|
|
1808
1810
|
readonly id: string;
|
|
1809
1811
|
readonly onkeydown: (e: BitsKeyboardEvent) => void;
|
|
1812
|
+
readonly "data-active": "" | undefined;
|
|
1810
1813
|
readonly role: "slider";
|
|
1811
1814
|
readonly "aria-valuemin": number;
|
|
1812
1815
|
readonly "aria-valuemax": number;
|
|
@@ -1820,6 +1823,7 @@ declare class SliderThumbState {
|
|
|
1820
1823
|
} | {
|
|
1821
1824
|
readonly id: string;
|
|
1822
1825
|
readonly onkeydown: (e: BitsKeyboardEvent) => void;
|
|
1826
|
+
readonly "data-active": "" | undefined;
|
|
1823
1827
|
readonly role: "slider";
|
|
1824
1828
|
readonly "aria-valuemin": number;
|
|
1825
1829
|
readonly "aria-valuemax": number;
|
|
@@ -31,6 +31,9 @@ class SliderBaseRootState {
|
|
|
31
31
|
this.opts = opts;
|
|
32
32
|
useRefById(opts);
|
|
33
33
|
}
|
|
34
|
+
isThumbActive(_index) {
|
|
35
|
+
return this.isActive;
|
|
36
|
+
}
|
|
34
37
|
#touchAction = $derived.by(() => {
|
|
35
38
|
if (this.opts.disabled.current)
|
|
36
39
|
return undefined;
|
|
@@ -284,6 +287,9 @@ class SliderMultiRootState extends SliderBaseRootState {
|
|
|
284
287
|
}
|
|
285
288
|
});
|
|
286
289
|
}
|
|
290
|
+
isThumbActive(index) {
|
|
291
|
+
return this.isActive && this.activeThumb?.idx === index;
|
|
292
|
+
}
|
|
287
293
|
applyPosition({ clientXY, activeThumbIdx, start, end, }) {
|
|
288
294
|
const min = this.opts.min.current;
|
|
289
295
|
const max = this.opts.max.current;
|
|
@@ -658,6 +664,7 @@ class SliderThumbState {
|
|
|
658
664
|
...this.root.thumbsPropsArr[this.opts.index.current],
|
|
659
665
|
id: this.opts.id.current,
|
|
660
666
|
onkeydown: this.onkeydown,
|
|
667
|
+
"data-active": this.root.isThumbActive(this.opts.index.current) ? "" : undefined,
|
|
661
668
|
}));
|
|
662
669
|
}
|
|
663
670
|
class SliderTickState {
|
|
@@ -106,6 +106,9 @@ export type SliderMultipleRootProps = SliderMultiRootPropsWithoutHTML & Without<
|
|
|
106
106
|
export type SliderRootProps = SliderRootPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderRootPropsWithoutHTML>;
|
|
107
107
|
export type SliderRangePropsWithoutHTML = WithChild;
|
|
108
108
|
export type SliderRangeProps = SliderRangePropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderRangePropsWithoutHTML>;
|
|
109
|
+
export type SliderThumbSnippetProps = {
|
|
110
|
+
active: boolean;
|
|
111
|
+
};
|
|
109
112
|
export type SliderThumbPropsWithoutHTML = WithChild<{
|
|
110
113
|
/**
|
|
111
114
|
* Whether the thumb is disabled or not.
|
|
@@ -118,7 +121,7 @@ export type SliderThumbPropsWithoutHTML = WithChild<{
|
|
|
118
121
|
* `Slider.Root` component.
|
|
119
122
|
*/
|
|
120
123
|
index: number;
|
|
121
|
-
}>;
|
|
124
|
+
}, SliderThumbSnippetProps>;
|
|
122
125
|
export type SliderThumbProps = SliderThumbPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderThumbPropsWithoutHTML>;
|
|
123
126
|
export type SliderTickPropsWithoutHTML = WithChild<{
|
|
124
127
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
|
|
2
2
|
import type { WithRefProps } from "../../internal/types.js";
|
|
3
|
-
import {
|
|
4
|
-
export declare const TooltipOpenEvent: CustomEventDispatcher<unknown>;
|
|
3
|
+
import type { PointerEventHandler } from "svelte/elements";
|
|
5
4
|
type TooltipProviderStateProps = ReadableBoxedValues<{
|
|
6
5
|
delayDuration: number;
|
|
7
6
|
disableHoverableContent: boolean;
|
|
@@ -16,8 +15,9 @@ declare class TooltipProviderState {
|
|
|
16
15
|
isOpenDelayed: boolean;
|
|
17
16
|
isPointerInTransit: import("svelte-toolbelt").WritableBox<boolean>;
|
|
18
17
|
constructor(opts: TooltipProviderStateProps);
|
|
19
|
-
onOpen: () => void;
|
|
20
|
-
onClose: () => void;
|
|
18
|
+
onOpen: (tooltip: TooltipRootState) => void;
|
|
19
|
+
onClose: (tooltip: TooltipRootState) => void;
|
|
20
|
+
isTooltipOpen: (tooltip: TooltipRootState) => boolean;
|
|
21
21
|
}
|
|
22
22
|
type TooltipRootStateProps = ReadableBoxedValues<{
|
|
23
23
|
delayDuration: number | undefined;
|
|
@@ -66,7 +66,7 @@ declare class TooltipTriggerState {
|
|
|
66
66
|
disabled: boolean;
|
|
67
67
|
onpointerup: () => void;
|
|
68
68
|
onpointerdown: () => void;
|
|
69
|
-
onpointermove:
|
|
69
|
+
onpointermove: PointerEventHandler<HTMLElement>;
|
|
70
70
|
onpointerleave: () => void;
|
|
71
71
|
onfocus: (e: FocusEvent & {
|
|
72
72
|
currentTarget: HTMLElement;
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import { box,
|
|
1
|
+
import { box, onMountEffect, useRefById } from "svelte-toolbelt";
|
|
2
2
|
import { on } from "svelte/events";
|
|
3
3
|
import { Context, watch } from "runed";
|
|
4
4
|
import { useTimeoutFn } from "../../internal/use-timeout-fn.svelte.js";
|
|
5
5
|
import { isElement, isFocusVisible } from "../../internal/is.js";
|
|
6
6
|
import { useGraceArea } from "../../internal/use-grace-area.svelte.js";
|
|
7
7
|
import { getDataDisabled } from "../../internal/attrs.js";
|
|
8
|
-
import { CustomEventDispatcher } from "../../internal/events.js";
|
|
9
8
|
const TOOLTIP_CONTENT_ATTR = "data-tooltip-content";
|
|
10
9
|
const TOOLTIP_TRIGGER_ATTR = "data-tooltip-trigger";
|
|
11
|
-
export const TooltipOpenEvent = new CustomEventDispatcher("bits.tooltip.open", {
|
|
12
|
-
bubbles: false,
|
|
13
|
-
cancelable: false,
|
|
14
|
-
});
|
|
15
10
|
class TooltipProviderState {
|
|
16
11
|
opts;
|
|
17
12
|
isOpenDelayed = $state(true);
|
|
18
13
|
isPointerInTransit = box(false);
|
|
19
14
|
#timerFn;
|
|
15
|
+
#openTooltip = $state(null);
|
|
20
16
|
constructor(opts) {
|
|
21
17
|
this.opts = opts;
|
|
22
18
|
this.#timerFn = useTimeoutFn(() => {
|
|
@@ -24,18 +20,34 @@ class TooltipProviderState {
|
|
|
24
20
|
}, this.opts.skipDelayDuration.current, { immediate: false });
|
|
25
21
|
}
|
|
26
22
|
#startTimer = () => {
|
|
27
|
-
this
|
|
23
|
+
const skipDuration = this.opts.skipDelayDuration.current;
|
|
24
|
+
if (skipDuration === 0) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.#timerFn.start();
|
|
29
|
+
}
|
|
28
30
|
};
|
|
29
31
|
#clearTimer = () => {
|
|
30
32
|
this.#timerFn.stop();
|
|
31
33
|
};
|
|
32
|
-
onOpen = () => {
|
|
34
|
+
onOpen = (tooltip) => {
|
|
35
|
+
if (this.#openTooltip && this.#openTooltip !== tooltip) {
|
|
36
|
+
this.#openTooltip.handleClose();
|
|
37
|
+
}
|
|
33
38
|
this.#clearTimer();
|
|
34
39
|
this.isOpenDelayed = false;
|
|
40
|
+
this.#openTooltip = tooltip;
|
|
35
41
|
};
|
|
36
|
-
onClose = () => {
|
|
42
|
+
onClose = (tooltip) => {
|
|
43
|
+
if (this.#openTooltip === tooltip) {
|
|
44
|
+
this.#openTooltip = null;
|
|
45
|
+
}
|
|
37
46
|
this.#startTimer();
|
|
38
47
|
};
|
|
48
|
+
isTooltipOpen = (tooltip) => {
|
|
49
|
+
return this.#openTooltip === tooltip;
|
|
50
|
+
};
|
|
39
51
|
}
|
|
40
52
|
class TooltipRootState {
|
|
41
53
|
opts;
|
|
@@ -73,14 +85,11 @@ class TooltipRootState {
|
|
|
73
85
|
}, this.delayDuration, { immediate: false });
|
|
74
86
|
});
|
|
75
87
|
watch(() => this.opts.open.current, (isOpen) => {
|
|
76
|
-
if (!this.provider.onClose)
|
|
77
|
-
return;
|
|
78
88
|
if (isOpen) {
|
|
79
|
-
this.provider.onOpen();
|
|
80
|
-
TooltipOpenEvent.dispatch(document);
|
|
89
|
+
this.provider.onOpen(this);
|
|
81
90
|
}
|
|
82
91
|
else {
|
|
83
|
-
this.provider.onClose();
|
|
92
|
+
this.provider.onClose(this);
|
|
84
93
|
}
|
|
85
94
|
});
|
|
86
95
|
}
|
|
@@ -94,7 +103,19 @@ class TooltipRootState {
|
|
|
94
103
|
this.opts.open.current = false;
|
|
95
104
|
};
|
|
96
105
|
#handleDelayedOpen = () => {
|
|
97
|
-
this.#timerFn.
|
|
106
|
+
this.#timerFn.stop();
|
|
107
|
+
const shouldSkipDelay = !this.provider.isOpenDelayed;
|
|
108
|
+
const delayDuration = this.delayDuration ?? 0;
|
|
109
|
+
// if no delay needed (either skip delay active or delay is 0), open immediately
|
|
110
|
+
if (shouldSkipDelay || delayDuration === 0) {
|
|
111
|
+
// set wasOpenDelayed based on whether we actually had a delay
|
|
112
|
+
this.#wasOpenDelayed = delayDuration > 0 && shouldSkipDelay;
|
|
113
|
+
this.opts.open.current = true;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// use timer for actual delays
|
|
117
|
+
this.#timerFn.start();
|
|
118
|
+
}
|
|
98
119
|
};
|
|
99
120
|
onTriggerEnter = () => {
|
|
100
121
|
this.#handleDelayedOpen();
|
|
@@ -145,7 +166,9 @@ class TooltipTriggerState {
|
|
|
145
166
|
return;
|
|
146
167
|
if (e.pointerType === "touch")
|
|
147
168
|
return;
|
|
148
|
-
if (this.#hasPointerMoveOpened
|
|
169
|
+
if (this.#hasPointerMoveOpened)
|
|
170
|
+
return;
|
|
171
|
+
if (this.root.provider.isPointerInTransit.current)
|
|
149
172
|
return;
|
|
150
173
|
this.root.onTriggerEnter();
|
|
151
174
|
this.#hasPointerMoveOpened = true;
|
|
@@ -209,20 +232,23 @@ class TooltipContentState {
|
|
|
209
232
|
contentNode: () => this.root.contentNode,
|
|
210
233
|
enabled: () => this.root.opts.open.current && !this.root.disableHoverableContent,
|
|
211
234
|
onPointerExit: () => {
|
|
212
|
-
this.root.
|
|
235
|
+
if (this.root.provider.isTooltipOpen(this.root)) {
|
|
236
|
+
this.root.handleClose();
|
|
237
|
+
}
|
|
213
238
|
},
|
|
214
239
|
setIsPointerInTransit: (value) => {
|
|
215
240
|
this.root.provider.isPointerInTransit.current = value;
|
|
216
241
|
},
|
|
242
|
+
transitTimeout: this.root.provider.opts.skipDelayDuration.current,
|
|
217
243
|
});
|
|
218
|
-
onMountEffect(() =>
|
|
244
|
+
onMountEffect(() => on(window, "scroll", (e) => {
|
|
219
245
|
const target = e.target;
|
|
220
246
|
if (!target)
|
|
221
247
|
return;
|
|
222
248
|
if (target.contains(this.root.triggerNode)) {
|
|
223
249
|
this.root.handleClose();
|
|
224
250
|
}
|
|
225
|
-
})
|
|
251
|
+
}));
|
|
226
252
|
}
|
|
227
253
|
onInteractOutside = (e) => {
|
|
228
254
|
if (isElement(e.target) &&
|
|
@@ -5,6 +5,7 @@ interface UseGraceAreaOpts {
|
|
|
5
5
|
contentNode: Getter<HTMLElement | null>;
|
|
6
6
|
onPointerExit: () => void;
|
|
7
7
|
setIsPointerInTransit?: (value: boolean) => void;
|
|
8
|
+
transitTimeout?: number;
|
|
8
9
|
}
|
|
9
10
|
export declare function useGraceArea(opts: UseGraceAreaOpts): {
|
|
10
11
|
isPointerInTransit: import("svelte-toolbelt").WritableBox<boolean>;
|
|
@@ -5,7 +5,7 @@ import { boxAutoReset } from "./box-auto-reset.svelte.js";
|
|
|
5
5
|
import { isElement, isHTMLElement } from "./is.js";
|
|
6
6
|
export function useGraceArea(opts) {
|
|
7
7
|
const enabled = $derived(opts.enabled());
|
|
8
|
-
const isPointerInTransit = boxAutoReset(false, 300, (value) => {
|
|
8
|
+
const isPointerInTransit = boxAutoReset(false, opts.transitTimeout ?? 300, (value) => {
|
|
9
9
|
if (enabled) {
|
|
10
10
|
opts.setIsPointerInTransit?.(value);
|
|
11
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bits-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "github:huntabyte/bits-ui",
|
|
6
6
|
"funding": "https://github.com/sponsors/huntabyte",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"@sveltejs/kit": "^2.16.1",
|
|
22
22
|
"@sveltejs/package": "^2.3.9",
|
|
23
23
|
"@sveltejs/vite-plugin-svelte": "4.0.0",
|
|
24
|
+
"@types/css.escape": "^1.5.2",
|
|
24
25
|
"@types/node": "^20.17.6",
|
|
25
26
|
"@types/resize-observer-browser": "^0.1.11",
|
|
26
27
|
"csstype": "^3.1.3",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"@floating-ui/core": "^1.6.4",
|
|
42
43
|
"@floating-ui/dom": "^1.6.7",
|
|
43
44
|
"@internationalized/date": "^3.5.6",
|
|
45
|
+
"css.escape": "^1.5.1",
|
|
44
46
|
"esm-env": "^1.1.2",
|
|
45
47
|
"runed": "^0.23.2",
|
|
46
48
|
"svelte-toolbelt": "^0.7.1",
|