bits-ui 2.1.0 → 2.2.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/avatar/avatar.svelte.d.ts +2 -1
- package/dist/bits/avatar/avatar.svelte.js +5 -3
- package/dist/bits/calendar/calendar.svelte.d.ts +2 -0
- package/dist/bits/calendar/calendar.svelte.js +9 -4
- package/dist/bits/combobox/components/combobox-input.svelte +2 -2
- package/dist/bits/combobox/components/combobox.svelte +5 -0
- package/dist/bits/combobox/types.d.ts +18 -1
- package/dist/bits/date-field/date-field.svelte.d.ts +3 -1
- package/dist/bits/date-field/date-field.svelte.js +15 -6
- package/dist/bits/date-range-field/date-range-field.svelte.d.ts +2 -0
- package/dist/bits/date-range-field/date-range-field.svelte.js +4 -2
- package/dist/bits/link-preview/link-preview.svelte.d.ts +2 -0
- package/dist/bits/link-preview/link-preview.svelte.js +11 -6
- package/dist/bits/menu/menu.svelte.d.ts +2 -0
- package/dist/bits/menu/menu.svelte.js +15 -10
- package/dist/bits/navigation-menu/navigation-menu.svelte.d.ts +3 -1
- package/dist/bits/navigation-menu/navigation-menu.svelte.js +21 -11
- package/dist/bits/pin-input/pin-input.svelte.d.ts +4 -2
- package/dist/bits/pin-input/pin-input.svelte.js +17 -13
- package/dist/bits/pin-input/usePasswordManager.svelte.d.ts +3 -2
- package/dist/bits/pin-input/usePasswordManager.svelte.js +6 -5
- package/dist/bits/range-calendar/range-calendar.svelte.d.ts +2 -0
- package/dist/bits/range-calendar/range-calendar.svelte.js +9 -3
- package/dist/bits/scroll-area/scroll-area.svelte.d.ts +2 -0
- package/dist/bits/scroll-area/scroll-area.svelte.js +15 -12
- package/dist/bits/select/components/select.svelte +6 -0
- package/dist/bits/select/select.svelte.d.ts +5 -1
- package/dist/bits/select/select.svelte.js +34 -18
- package/dist/bits/slider/helpers.js +33 -2
- package/dist/bits/time-field/time-field.svelte.d.ts +3 -1
- package/dist/bits/time-field/time-field.svelte.js +15 -6
- package/dist/bits/time-range-field/time-range-field.svelte.d.ts +2 -0
- package/dist/bits/time-range-field/time-range-field.svelte.js +4 -2
- package/dist/bits/tooltip/components/tooltip-content-static.svelte +2 -0
- package/dist/bits/tooltip/components/tooltip-content.svelte +2 -0
- package/dist/bits/tooltip/components/tooltip-trigger.svelte +1 -1
- package/dist/bits/tooltip/components/tooltip.svelte +1 -1
- package/dist/bits/tooltip/tooltip.svelte.d.ts +18 -18
- package/dist/bits/tooltip/tooltip.svelte.js +7 -3
- package/dist/bits/utilities/floating-layer/components/floating-layer-anchor.svelte +9 -6
- package/dist/bits/utilities/floating-layer/components/floating-layer-content.svelte +25 -21
- package/dist/bits/utilities/floating-layer/components/floating-layer.svelte +2 -2
- package/dist/bits/utilities/floating-layer/components/floating-layer.svelte.d.ts +1 -0
- package/dist/bits/utilities/floating-layer/types.d.ts +18 -0
- package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.d.ts +3 -3
- package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.js +16 -11
- package/dist/bits/utilities/focus-scope/use-focus-scope.svelte.js +14 -9
- package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte +2 -0
- package/dist/bits/utilities/popper-layer/types.d.ts +9 -0
- package/dist/bits/utilities/portal/types.d.ts +1 -1
- package/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.d.ts +2 -0
- package/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.js +7 -7
- package/dist/internal/box-auto-reset.svelte.d.ts +7 -1
- package/dist/internal/box-auto-reset.svelte.js +11 -6
- package/dist/internal/date-time/announcer.d.ts +1 -1
- package/dist/internal/date-time/announcer.js +20 -20
- package/dist/internal/date-time/calendar-helpers.svelte.js +7 -5
- package/dist/internal/date-time/field/helpers.d.ts +8 -2
- package/dist/internal/date-time/field/helpers.js +8 -7
- package/dist/internal/date-time/field/time-helpers.d.ts +8 -2
- package/dist/internal/date-time/field/time-helpers.js +9 -9
- package/dist/internal/dom.d.ts +0 -1
- package/dist/internal/dom.js +0 -3
- package/dist/internal/focus.d.ts +2 -2
- package/dist/internal/focus.js +14 -9
- package/dist/internal/math.d.ts +0 -4
- package/dist/internal/math.js +0 -28
- package/dist/internal/tabbable.d.ts +0 -2
- package/dist/internal/tabbable.js +10 -14
- package/dist/internal/use-data-typeahead.svelte.d.ts +1 -0
- package/dist/internal/use-data-typeahead.svelte.js +4 -1
- package/dist/internal/use-dom-typeahead.svelte.d.ts +3 -1
- package/dist/internal/use-dom-typeahead.svelte.js +5 -2
- package/dist/internal/use-grace-area.svelte.js +9 -5
- package/package.json +2 -2
- package/dist/internal/dom-context.svelte.d.ts +0 -9
- package/dist/internal/dom-context.svelte.js +0 -26
- package/dist/internal/use-size.svelte.d.ts +0 -7
- package/dist/internal/use-size.svelte.js +0 -54
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DOMContext } from "svelte-toolbelt";
|
|
1
2
|
import type { PinInputCell, PinInputRootProps as RootComponentProps } from "./types.js";
|
|
2
3
|
import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
|
|
3
4
|
import type { BitsEvent, BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, WithRefProps } from "../../internal/types.js";
|
|
@@ -21,6 +22,7 @@ type PinInputRootStateProps = WithRefProps<WritableBoxedValues<{
|
|
|
21
22
|
declare class PinInputRootState {
|
|
22
23
|
#private;
|
|
23
24
|
readonly opts: PinInputRootStateProps;
|
|
25
|
+
domContext: DOMContext;
|
|
24
26
|
constructor(opts: PinInputRootStateProps);
|
|
25
27
|
onkeydown: (e: BitsKeyboardEvent) => void;
|
|
26
28
|
rootProps: {
|
|
@@ -75,7 +77,7 @@ declare class PinInputRootState {
|
|
|
75
77
|
"data-pin-input-input": string;
|
|
76
78
|
"data-pin-input-input-mss": number | null;
|
|
77
79
|
"data-pin-input-input-mse": number | null;
|
|
78
|
-
inputmode: "none" | "
|
|
80
|
+
inputmode: "none" | "email" | "tel" | "url" | "text" | "numeric" | "decimal" | "search" | null | undefined;
|
|
79
81
|
pattern: any;
|
|
80
82
|
maxlength: number;
|
|
81
83
|
value: string;
|
|
@@ -111,7 +113,7 @@ declare class PinInputCellState {
|
|
|
111
113
|
readonly "data-inactive": "" | undefined;
|
|
112
114
|
};
|
|
113
115
|
}
|
|
114
|
-
export declare function syncTimeouts(cb: (...args: any[]) => unknown): number[];
|
|
116
|
+
export declare function syncTimeouts(cb: (...args: any[]) => unknown, domContext: DOMContext): number[];
|
|
115
117
|
export declare function usePinInput(props: PinInputRootStateProps): PinInputRootState;
|
|
116
118
|
export declare function usePinInputCell(props: PinInputCellStateProps): PinInputCellState;
|
|
117
119
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Previous, watch } from "runed";
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
|
-
import { box, attachRef } from "svelte-toolbelt";
|
|
3
|
+
import { box, attachRef, DOMContext } from "svelte-toolbelt";
|
|
4
4
|
import { usePasswordManagerBadge } from "./usePasswordManager.svelte.js";
|
|
5
5
|
import { getDisabled } from "../../internal/attrs.js";
|
|
6
6
|
import { on } from "svelte/events";
|
|
@@ -47,8 +47,10 @@ class PinInputRootState {
|
|
|
47
47
|
});
|
|
48
48
|
#pwmb;
|
|
49
49
|
#initialLoad;
|
|
50
|
+
domContext;
|
|
50
51
|
constructor(opts) {
|
|
51
52
|
this.opts = opts;
|
|
53
|
+
this.domContext = new DOMContext(opts.ref);
|
|
52
54
|
this.#initialLoad = {
|
|
53
55
|
value: this.opts.value,
|
|
54
56
|
isIOS: typeof window !== "undefined" &&
|
|
@@ -59,6 +61,7 @@ class PinInputRootState {
|
|
|
59
61
|
inputRef: this.#inputRef,
|
|
60
62
|
isFocused: this.#isFocused,
|
|
61
63
|
pushPasswordManagerStrategy: this.opts.pushPasswordManagerStrategy,
|
|
64
|
+
domContext: this.domContext,
|
|
62
65
|
});
|
|
63
66
|
onMount(() => {
|
|
64
67
|
const input = this.#inputRef.current;
|
|
@@ -73,14 +76,14 @@ class PinInputRootState {
|
|
|
73
76
|
input.selectionEnd,
|
|
74
77
|
input.selectionDirection ?? "none",
|
|
75
78
|
];
|
|
76
|
-
const unsub = on(
|
|
79
|
+
const unsub = on(this.domContext.getDocument(), "selectionchange", this.#onDocumentSelectionChange, {
|
|
77
80
|
capture: true,
|
|
78
81
|
});
|
|
79
82
|
this.#onDocumentSelectionChange();
|
|
80
|
-
if (
|
|
83
|
+
if (this.domContext.getActiveElement() === input) {
|
|
81
84
|
this.#isFocused.current = true;
|
|
82
85
|
}
|
|
83
|
-
if (!
|
|
86
|
+
if (!this.domContext.getElementById("pin-input-style")) {
|
|
84
87
|
this.#applyStyles();
|
|
85
88
|
}
|
|
86
89
|
const updateRootHeight = () => {
|
|
@@ -112,7 +115,7 @@ class PinInputRootState {
|
|
|
112
115
|
this.#mirrorSelectionEnd = end;
|
|
113
116
|
this.#prevInputMetadata.prev = [start, end, dir];
|
|
114
117
|
}
|
|
115
|
-
});
|
|
118
|
+
}, this.domContext);
|
|
116
119
|
});
|
|
117
120
|
$effect(() => {
|
|
118
121
|
// invoke `onComplete` when the input is completely filled.
|
|
@@ -186,9 +189,10 @@ class PinInputRootState {
|
|
|
186
189
|
fontVariantNumeric: "tabular-nums",
|
|
187
190
|
}));
|
|
188
191
|
#applyStyles() {
|
|
189
|
-
const
|
|
192
|
+
const doc = this.domContext.getDocument();
|
|
193
|
+
const styleEl = doc.createElement("style");
|
|
190
194
|
styleEl.id = "pin-input-style";
|
|
191
|
-
|
|
195
|
+
doc.head.appendChild(styleEl);
|
|
192
196
|
if (styleEl.sheet) {
|
|
193
197
|
const autoFillStyles = "background: transparent !important; color: transparent !important; border-color: transparent !important; opacity: 0 !important; box-shadow: none !important; -webkit-box-shadow: none !important; -webkit-text-fill-color: transparent !important;";
|
|
194
198
|
safeInsertRule(styleEl.sheet, "[data-pin-input-input]::selection { background: transparent !important; color: transparent !important; }");
|
|
@@ -205,7 +209,7 @@ class PinInputRootState {
|
|
|
205
209
|
const container = this.opts.ref.current;
|
|
206
210
|
if (!input || !container)
|
|
207
211
|
return;
|
|
208
|
-
if (
|
|
212
|
+
if (this.domContext.getActiveElement() !== input) {
|
|
209
213
|
this.#mirrorSelectionStart = null;
|
|
210
214
|
this.#mirrorSelectionEnd = null;
|
|
211
215
|
return;
|
|
@@ -272,7 +276,7 @@ class PinInputRootState {
|
|
|
272
276
|
// selectionchange event, we'll have to dispatch it manually.
|
|
273
277
|
// NOTE: The following line also triggers when cmd+A then pasting
|
|
274
278
|
// a value with smaller length, which is not ideal for performance.
|
|
275
|
-
|
|
279
|
+
this.domContext.getDocument().dispatchEvent(new Event("selectionchange"));
|
|
276
280
|
}
|
|
277
281
|
this.opts.value.current = newValue;
|
|
278
282
|
};
|
|
@@ -397,10 +401,10 @@ class PinInputCellState {
|
|
|
397
401
|
}));
|
|
398
402
|
}
|
|
399
403
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
400
|
-
export function syncTimeouts(cb) {
|
|
401
|
-
const t1 = setTimeout(cb, 0); // For faster machines
|
|
402
|
-
const t2 = setTimeout(cb, 1_0);
|
|
403
|
-
const t3 = setTimeout(cb, 5_0);
|
|
404
|
+
export function syncTimeouts(cb, domContext) {
|
|
405
|
+
const t1 = domContext.setTimeout(cb, 0); // For faster machines
|
|
406
|
+
const t2 = domContext.setTimeout(cb, 1_0);
|
|
407
|
+
const t3 = domContext.setTimeout(cb, 5_0);
|
|
404
408
|
return [t1, t2, t3];
|
|
405
409
|
}
|
|
406
410
|
function safeInsertRule(sheet, rule) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DOMContext, type ReadableBox, type WritableBox } from "svelte-toolbelt";
|
|
2
2
|
import type { PinInputRootPropsWithoutHTML } from "./types.js";
|
|
3
3
|
type UsePasswordManagerBadgeProps = {
|
|
4
4
|
containerRef: WritableBox<HTMLElement | null>;
|
|
5
5
|
inputRef: WritableBox<HTMLInputElement | null>;
|
|
6
6
|
pushPasswordManagerStrategy: ReadableBox<PinInputRootPropsWithoutHTML["pushPasswordManagerStrategy"]>;
|
|
7
7
|
isFocused: ReadableBox<boolean>;
|
|
8
|
+
domContext: DOMContext;
|
|
8
9
|
};
|
|
9
|
-
export declare function usePasswordManagerBadge({ containerRef, inputRef, pushPasswordManagerStrategy, isFocused, }: UsePasswordManagerBadgeProps): {
|
|
10
|
+
export declare function usePasswordManagerBadge({ containerRef, inputRef, pushPasswordManagerStrategy, isFocused, domContext, }: UsePasswordManagerBadgeProps): {
|
|
10
11
|
readonly hasPwmBadge: boolean;
|
|
11
12
|
readonly willPushPwmBadge: boolean;
|
|
12
13
|
PWM_BADGE_SPACE_WIDTH: "40px";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getWindow } from "svelte-toolbelt";
|
|
1
2
|
const PWM_BADGE_MARGIN_RIGHT = 18;
|
|
2
3
|
const PWM_BADGE_SPACE_WIDTH_PX = 40;
|
|
3
4
|
const PWM_BADGE_SPACE_WIDTH = `${PWM_BADGE_SPACE_WIDTH_PX}px`;
|
|
@@ -7,7 +8,7 @@ const PASSWORD_MANAGER_SELECTORS = [
|
|
|
7
8
|
"[data-dashlanecreated]", // Dashlane,
|
|
8
9
|
'[style$="2147483647 !important;"]', // Bitwarden
|
|
9
10
|
].join(",");
|
|
10
|
-
export function usePasswordManagerBadge({ containerRef, inputRef, pushPasswordManagerStrategy, isFocused, }) {
|
|
11
|
+
export function usePasswordManagerBadge({ containerRef, inputRef, pushPasswordManagerStrategy, isFocused, domContext, }) {
|
|
11
12
|
let hasPwmBadge = $state(false);
|
|
12
13
|
let hasPwmBadgeSpace = $state(false);
|
|
13
14
|
let done = $state(false);
|
|
@@ -31,11 +32,11 @@ export function usePasswordManagerBadge({ containerRef, inputRef, pushPasswordMa
|
|
|
31
32
|
const x = rightCornerX - PWM_BADGE_MARGIN_RIGHT;
|
|
32
33
|
const y = centeredY;
|
|
33
34
|
// do an extra search to check for all the password manager badges
|
|
34
|
-
const passwordManagerStrategy =
|
|
35
|
+
const passwordManagerStrategy = domContext.querySelectorAll(PASSWORD_MANAGER_SELECTORS);
|
|
35
36
|
// if no password manager is detected, dispatch document.elementFromPoint to
|
|
36
37
|
// identify the badges
|
|
37
38
|
if (passwordManagerStrategy.length === 0) {
|
|
38
|
-
const maybeBadgeEl =
|
|
39
|
+
const maybeBadgeEl = domContext.getDocument().elementFromPoint(x, y);
|
|
39
40
|
// if the found element is the container,
|
|
40
41
|
// then it is not a badge, most times there is no badge in this case
|
|
41
42
|
if (maybeBadgeEl === container)
|
|
@@ -50,7 +51,7 @@ export function usePasswordManagerBadge({ containerRef, inputRef, pushPasswordMa
|
|
|
50
51
|
return;
|
|
51
52
|
// check if the pwm area is fully visible
|
|
52
53
|
function checkHasSpace() {
|
|
53
|
-
const viewportWidth =
|
|
54
|
+
const viewportWidth = getWindow(container).innerWidth;
|
|
54
55
|
const distanceToRightEdge = viewportWidth - container.getBoundingClientRect().right;
|
|
55
56
|
hasPwmBadgeSpace = distanceToRightEdge >= PWM_BADGE_SPACE_WIDTH_PX;
|
|
56
57
|
}
|
|
@@ -61,7 +62,7 @@ export function usePasswordManagerBadge({ containerRef, inputRef, pushPasswordMa
|
|
|
61
62
|
};
|
|
62
63
|
});
|
|
63
64
|
$effect(() => {
|
|
64
|
-
const focused = isFocused.current ||
|
|
65
|
+
const focused = isFocused.current || domContext.getActiveElement() === inputRef.current;
|
|
65
66
|
if (pushPasswordManagerStrategy.current === "none" || !focused)
|
|
66
67
|
return;
|
|
67
68
|
const t1 = setTimeout(trackPwmBadge, 0);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DateValue } from "@internationalized/date";
|
|
2
|
+
import { DOMContext } from "svelte-toolbelt";
|
|
2
3
|
import type { DateRange, Month } from "../../shared/index.js";
|
|
3
4
|
import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
|
|
4
5
|
import type { BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, WithRefProps } from "../../internal/types.js";
|
|
@@ -45,6 +46,7 @@ export declare class RangeCalendarRootState {
|
|
|
45
46
|
accessibleHeadingId: string;
|
|
46
47
|
focusedValue: DateValue | undefined;
|
|
47
48
|
lastPressedDateValue: DateValue | undefined;
|
|
49
|
+
domContext: DOMContext;
|
|
48
50
|
constructor(opts: RangeCalendarRootStateProps);
|
|
49
51
|
setMonths: (months: Month<DateValue>[]) => void;
|
|
50
52
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getLocalTimeZone, isSameDay, isSameMonth, isToday, } from "@internationalized/date";
|
|
2
|
-
import { attachRef } from "svelte-toolbelt";
|
|
2
|
+
import { attachRef, DOMContext } from "svelte-toolbelt";
|
|
3
3
|
import { Context, watch } from "runed";
|
|
4
4
|
import { CalendarRootContext } from "../calendar/calendar.svelte.js";
|
|
5
5
|
import { useId } from "../../internal/use-id.js";
|
|
@@ -8,6 +8,7 @@ import { getAnnouncer } from "../../internal/date-time/announcer.js";
|
|
|
8
8
|
import { createFormatter } from "../../internal/date-time/formatter.js";
|
|
9
9
|
import { createMonths, getCalendarElementProps, getCalendarHeadingValue, getIsNextButtonDisabled, getIsPrevButtonDisabled, getWeekdays, handleCalendarKeydown, handleCalendarNextPage, handleCalendarPrevPage, shiftCalendarFocus, useEnsureNonDisabledPlaceholder, useMonthViewOptionsSync, useMonthViewPlaceholderSync, } from "../../internal/date-time/calendar-helpers.svelte.js";
|
|
10
10
|
import { areAllDaysBetweenValid, getDateValueType, isAfter, isBefore, isBetweenInclusive, toDate, } from "../../internal/date-time/utils.js";
|
|
11
|
+
import { onMount } from "svelte";
|
|
11
12
|
export class RangeCalendarRootState {
|
|
12
13
|
opts;
|
|
13
14
|
months = $state([]);
|
|
@@ -17,9 +18,11 @@ export class RangeCalendarRootState {
|
|
|
17
18
|
accessibleHeadingId = useId();
|
|
18
19
|
focusedValue = $state(undefined);
|
|
19
20
|
lastPressedDateValue = undefined;
|
|
21
|
+
domContext;
|
|
20
22
|
constructor(opts) {
|
|
21
23
|
this.opts = opts;
|
|
22
|
-
this.
|
|
24
|
+
this.domContext = new DOMContext(opts.ref);
|
|
25
|
+
this.announcer = getAnnouncer(null);
|
|
23
26
|
this.formatter = createFormatter(this.opts.locale.current);
|
|
24
27
|
this.months = createMonths({
|
|
25
28
|
dateObj: this.opts.placeholder.current,
|
|
@@ -33,6 +36,9 @@ export class RangeCalendarRootState {
|
|
|
33
36
|
return;
|
|
34
37
|
this.formatter.setLocale(this.opts.locale.current);
|
|
35
38
|
});
|
|
39
|
+
onMount(() => {
|
|
40
|
+
this.announcer = getAnnouncer(this.domContext.getDocument());
|
|
41
|
+
});
|
|
36
42
|
/**
|
|
37
43
|
* Updates the displayed months based on changes in the placeholder values,
|
|
38
44
|
* which determines the month to show in the calendar.
|
|
@@ -63,7 +69,7 @@ export class RangeCalendarRootState {
|
|
|
63
69
|
* changes.
|
|
64
70
|
*/
|
|
65
71
|
$effect(() => {
|
|
66
|
-
const node =
|
|
72
|
+
const node = this.domContext.getElementById(this.accessibleHeadingId);
|
|
67
73
|
if (!node)
|
|
68
74
|
return;
|
|
69
75
|
node.textContent = this.fullCalendarLabel;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Incredible thought must have went into solving all the intricacies of this component.
|
|
6
6
|
*/
|
|
7
7
|
import { Context } from "runed";
|
|
8
|
+
import { DOMContext } from "svelte-toolbelt";
|
|
8
9
|
import type { ScrollAreaType } from "./types.js";
|
|
9
10
|
import type { ReadableBoxedValues } from "../../internal/box.svelte.js";
|
|
10
11
|
import type { BitsPointerEvent, WithRefProps } from "../../internal/types.js";
|
|
@@ -34,6 +35,7 @@ declare class ScrollAreaRootState {
|
|
|
34
35
|
cornerHeight: number;
|
|
35
36
|
scrollbarXEnabled: boolean;
|
|
36
37
|
scrollbarYEnabled: boolean;
|
|
38
|
+
domContext: DOMContext;
|
|
37
39
|
constructor(opts: ScrollAreaRootStateProps);
|
|
38
40
|
props: {
|
|
39
41
|
readonly id: string;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Context, useDebounce } from "runed";
|
|
8
8
|
import { untrack } from "svelte";
|
|
9
|
-
import { box, executeCallbacks, attachRef } from "svelte-toolbelt";
|
|
9
|
+
import { box, executeCallbacks, attachRef, DOMContext, getWindow } from "svelte-toolbelt";
|
|
10
10
|
import { addEventListener } from "../../internal/events.js";
|
|
11
11
|
import { mergeProps, useId } from "../../shared/index.js";
|
|
12
12
|
import { useStateMachine } from "../../internal/use-state-machine.svelte.js";
|
|
@@ -29,8 +29,10 @@ class ScrollAreaRootState {
|
|
|
29
29
|
cornerHeight = $state(0);
|
|
30
30
|
scrollbarXEnabled = $state(false);
|
|
31
31
|
scrollbarYEnabled = $state(false);
|
|
32
|
+
domContext;
|
|
32
33
|
constructor(opts) {
|
|
33
34
|
this.opts = opts;
|
|
35
|
+
this.domContext = new DOMContext(opts.ref);
|
|
34
36
|
}
|
|
35
37
|
props = $derived.by(() => ({
|
|
36
38
|
id: this.opts.id.current,
|
|
@@ -110,13 +112,13 @@ class ScrollAreaScrollbarHoverState {
|
|
|
110
112
|
if (!scrollAreaNode)
|
|
111
113
|
return;
|
|
112
114
|
const handlePointerEnter = () => {
|
|
113
|
-
|
|
115
|
+
this.root.domContext.clearTimeout(hideTimer);
|
|
114
116
|
untrack(() => (this.isVisible = true));
|
|
115
117
|
};
|
|
116
118
|
const handlePointerLeave = () => {
|
|
117
119
|
if (hideTimer)
|
|
118
|
-
|
|
119
|
-
hideTimer =
|
|
120
|
+
this.root.domContext.clearTimeout(hideTimer);
|
|
121
|
+
hideTimer = this.root.domContext.setTimeout(() => {
|
|
120
122
|
untrack(() => {
|
|
121
123
|
this.scrollbar.hasThumb = false;
|
|
122
124
|
this.isVisible = false;
|
|
@@ -125,7 +127,7 @@ class ScrollAreaScrollbarHoverState {
|
|
|
125
127
|
};
|
|
126
128
|
const unsubListeners = executeCallbacks(on(scrollAreaNode, "pointerenter", handlePointerEnter), on(scrollAreaNode, "pointerleave", handlePointerLeave));
|
|
127
129
|
return () => {
|
|
128
|
-
|
|
130
|
+
this.root.domContext.getWindow().clearTimeout(hideTimer);
|
|
129
131
|
unsubListeners();
|
|
130
132
|
};
|
|
131
133
|
});
|
|
@@ -164,8 +166,8 @@ class ScrollAreaScrollbarScrollState {
|
|
|
164
166
|
const _state = this.machine.state.current;
|
|
165
167
|
const scrollHideDelay = this.root.opts.scrollHideDelay.current;
|
|
166
168
|
if (_state === "idle") {
|
|
167
|
-
const hideTimer =
|
|
168
|
-
return () =>
|
|
169
|
+
const hideTimer = this.root.domContext.setTimeout(() => this.machine.dispatch("HIDE"), scrollHideDelay);
|
|
170
|
+
return () => this.root.domContext.clearTimeout(hideTimer);
|
|
169
171
|
}
|
|
170
172
|
});
|
|
171
173
|
$effect(() => {
|
|
@@ -538,8 +540,8 @@ class ScrollAreaScrollbarSharedState {
|
|
|
538
540
|
this.rect = this.scrollbar.opts.ref.current?.getBoundingClientRect() ?? null;
|
|
539
541
|
// pointer capture doesn't prevent text selection in Safari
|
|
540
542
|
// so we remove text selection manually when scrolling
|
|
541
|
-
this.prevWebkitUserSelect =
|
|
542
|
-
|
|
543
|
+
this.prevWebkitUserSelect = this.root.domContext.getDocument().body.style.webkitUserSelect;
|
|
544
|
+
this.root.domContext.getDocument().body.style.webkitUserSelect = "none";
|
|
543
545
|
if (this.root.viewportNode)
|
|
544
546
|
this.root.viewportNode.style.scrollBehavior = "auto";
|
|
545
547
|
this.handleDragScroll(e);
|
|
@@ -552,7 +554,7 @@ class ScrollAreaScrollbarSharedState {
|
|
|
552
554
|
if (target.hasPointerCapture(e.pointerId)) {
|
|
553
555
|
target.releasePointerCapture(e.pointerId);
|
|
554
556
|
}
|
|
555
|
-
|
|
557
|
+
this.root.domContext.getDocument().body.style.webkitUserSelect = this.prevWebkitUserSelect;
|
|
556
558
|
if (this.root.viewportNode)
|
|
557
559
|
this.root.viewportNode.style.scrollBehavior = "";
|
|
558
560
|
this.rect = null;
|
|
@@ -755,6 +757,7 @@ function isScrollingWithinScrollbarBounds(scrollPos, maxScrollPos) {
|
|
|
755
757
|
function addUnlinkedScrollListener(node, handler) {
|
|
756
758
|
let prevPosition = { left: node.scrollLeft, top: node.scrollTop };
|
|
757
759
|
let rAF = 0;
|
|
760
|
+
const win = getWindow(node);
|
|
758
761
|
(function loop() {
|
|
759
762
|
const position = { left: node.scrollLeft, top: node.scrollTop };
|
|
760
763
|
const isHorizontalScroll = prevPosition.left !== position.left;
|
|
@@ -762,7 +765,7 @@ function addUnlinkedScrollListener(node, handler) {
|
|
|
762
765
|
if (isHorizontalScroll || isVerticalScroll)
|
|
763
766
|
handler();
|
|
764
767
|
prevPosition = position;
|
|
765
|
-
rAF =
|
|
768
|
+
rAF = win.requestAnimationFrame(loop);
|
|
766
769
|
})();
|
|
767
|
-
return () =>
|
|
770
|
+
return () => win.cancelAnimationFrame(rAF);
|
|
768
771
|
}
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
}
|
|
39
39
|
);
|
|
40
40
|
|
|
41
|
+
let inputValue = $state("");
|
|
42
|
+
|
|
41
43
|
const rootState = useSelectRoot({
|
|
42
44
|
type,
|
|
43
45
|
value: box.with(
|
|
@@ -63,6 +65,10 @@
|
|
|
63
65
|
isCombobox: false,
|
|
64
66
|
items: box.with(() => items),
|
|
65
67
|
allowDeselect: box.with(() => allowDeselect),
|
|
68
|
+
inputValue: box.with(
|
|
69
|
+
() => inputValue,
|
|
70
|
+
(v) => (inputValue = v)
|
|
71
|
+
),
|
|
66
72
|
});
|
|
67
73
|
</script>
|
|
68
74
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Previous } from "runed";
|
|
2
|
+
import { DOMContext } from "svelte-toolbelt";
|
|
2
3
|
import type { Box, ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
|
|
3
4
|
import type { BitsEvent, BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, WithRefProps } from "../../internal/types.js";
|
|
4
5
|
export declare const INTERACTION_KEYS: string[];
|
|
@@ -21,13 +22,13 @@ type SelectBaseRootStateProps = ReadableBoxedValues<{
|
|
|
21
22
|
allowDeselect: boolean;
|
|
22
23
|
}> & WritableBoxedValues<{
|
|
23
24
|
open: boolean;
|
|
25
|
+
inputValue: string;
|
|
24
26
|
}> & {
|
|
25
27
|
isCombobox: boolean;
|
|
26
28
|
};
|
|
27
29
|
declare class SelectBaseRootState {
|
|
28
30
|
readonly opts: SelectBaseRootStateProps;
|
|
29
31
|
touchedInput: boolean;
|
|
30
|
-
inputValue: string;
|
|
31
32
|
inputNode: HTMLElement | null;
|
|
32
33
|
contentNode: HTMLElement | null;
|
|
33
34
|
triggerNode: HTMLElement | null;
|
|
@@ -39,6 +40,7 @@ declare class SelectBaseRootState {
|
|
|
39
40
|
isUsingKeyboard: boolean;
|
|
40
41
|
isCombobox: boolean;
|
|
41
42
|
bitsAttrs: SelectBitsAttrs;
|
|
43
|
+
domContext: DOMContext;
|
|
42
44
|
constructor(opts: SelectBaseRootStateProps);
|
|
43
45
|
setHighlightedNode(node: HTMLElement | null, initial?: boolean): void;
|
|
44
46
|
getCandidateNodes(): HTMLElement[];
|
|
@@ -156,6 +158,7 @@ declare class SelectContentState {
|
|
|
156
158
|
readonly root: SelectRootState;
|
|
157
159
|
viewportNode: HTMLElement | null;
|
|
158
160
|
isPositioned: boolean;
|
|
161
|
+
domContext: DOMContext;
|
|
159
162
|
constructor(opts: SelectContentStateProps, root: SelectRootState);
|
|
160
163
|
onpointermove(_: BitsPointerEvent): void;
|
|
161
164
|
onInteractOutside: (e: PointerEvent) => void;
|
|
@@ -379,6 +382,7 @@ type InitSelectProps = {
|
|
|
379
382
|
allowDeselect: boolean;
|
|
380
383
|
}> & WritableBoxedValues<{
|
|
381
384
|
open: boolean;
|
|
385
|
+
inputValue: string;
|
|
382
386
|
}> & {
|
|
383
387
|
isCombobox: boolean;
|
|
384
388
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Context, Previous, watch } from "runed";
|
|
2
|
-
import { afterSleep, afterTick, onDestroyEffect, attachRef } from "svelte-toolbelt";
|
|
2
|
+
import { afterSleep, afterTick, onDestroyEffect, attachRef, DOMContext } from "svelte-toolbelt";
|
|
3
3
|
import { on } from "svelte/events";
|
|
4
4
|
import { backward, forward, next, prev } from "../../internal/arrays.js";
|
|
5
5
|
import { getAriaExpanded, getAriaHidden, getDataDisabled, getDataOpenClosed, getDisabled, getRequired, } from "../../internal/attrs.js";
|
|
@@ -18,7 +18,6 @@ export const CONTENT_MARGIN = 10;
|
|
|
18
18
|
class SelectBaseRootState {
|
|
19
19
|
opts;
|
|
20
20
|
touchedInput = $state(false);
|
|
21
|
-
inputValue = $state("");
|
|
22
21
|
inputNode = $state(null);
|
|
23
22
|
contentNode = $state(null);
|
|
24
23
|
triggerNode = $state(null);
|
|
@@ -42,6 +41,7 @@ class SelectBaseRootState {
|
|
|
42
41
|
isUsingKeyboard = false;
|
|
43
42
|
isCombobox = false;
|
|
44
43
|
bitsAttrs;
|
|
44
|
+
domContext = new DOMContext(() => null);
|
|
45
45
|
constructor(opts) {
|
|
46
46
|
this.opts = opts;
|
|
47
47
|
this.isCombobox = opts.isCombobox;
|
|
@@ -134,11 +134,12 @@ class SelectSingleRootState extends SelectBaseRootState {
|
|
|
134
134
|
}
|
|
135
135
|
toggleItem(itemValue, itemLabel = itemValue) {
|
|
136
136
|
this.opts.value.current = this.includesItem(itemValue) ? "" : itemValue;
|
|
137
|
-
this.inputValue = itemLabel;
|
|
137
|
+
this.opts.inputValue.current = itemLabel;
|
|
138
138
|
}
|
|
139
139
|
setInitialHighlightedNode() {
|
|
140
140
|
afterTick(() => {
|
|
141
|
-
if (this.highlightedNode &&
|
|
141
|
+
if (this.highlightedNode &&
|
|
142
|
+
this.domContext.getDocument().contains(this.highlightedNode))
|
|
142
143
|
return;
|
|
143
144
|
if (this.opts.value.current !== "") {
|
|
144
145
|
const node = this.getNodeByValue(this.opts.value.current);
|
|
@@ -183,11 +184,14 @@ class SelectMultipleRootState extends SelectBaseRootState {
|
|
|
183
184
|
else {
|
|
184
185
|
this.opts.value.current = [...this.opts.value.current, itemValue];
|
|
185
186
|
}
|
|
186
|
-
this.inputValue = itemLabel;
|
|
187
|
+
this.opts.inputValue.current = itemLabel;
|
|
187
188
|
}
|
|
188
189
|
setInitialHighlightedNode() {
|
|
189
190
|
afterTick(() => {
|
|
190
|
-
if (this.
|
|
191
|
+
if (!this.domContext)
|
|
192
|
+
return;
|
|
193
|
+
if (this.highlightedNode &&
|
|
194
|
+
this.domContext.getDocument().contains(this.highlightedNode))
|
|
191
195
|
return;
|
|
192
196
|
if (this.opts.value.current.length && this.opts.value.current[0] !== "") {
|
|
193
197
|
const node = this.getNodeByValue(this.opts.value.current[0]);
|
|
@@ -210,6 +214,7 @@ class SelectInputState {
|
|
|
210
214
|
constructor(opts, root) {
|
|
211
215
|
this.opts = opts;
|
|
212
216
|
this.root = root;
|
|
217
|
+
this.root.domContext = new DOMContext(opts.ref);
|
|
213
218
|
this.onkeydown = this.onkeydown.bind(this);
|
|
214
219
|
this.oninput = this.oninput.bind(this);
|
|
215
220
|
watch([() => this.root.opts.value.current, () => this.opts.clearOnDeselect.current], ([value, clearOnDeselect], [prevValue]) => {
|
|
@@ -217,11 +222,11 @@ class SelectInputState {
|
|
|
217
222
|
return;
|
|
218
223
|
if (Array.isArray(value) && Array.isArray(prevValue)) {
|
|
219
224
|
if (value.length === 0 && prevValue.length !== 0) {
|
|
220
|
-
this.root.inputValue = "";
|
|
225
|
+
this.root.opts.inputValue.current = "";
|
|
221
226
|
}
|
|
222
227
|
}
|
|
223
228
|
else if (value === "" && prevValue !== "") {
|
|
224
|
-
this.root.inputValue = "";
|
|
229
|
+
this.root.opts.inputValue.current = "";
|
|
225
230
|
}
|
|
226
231
|
});
|
|
227
232
|
}
|
|
@@ -237,7 +242,7 @@ class SelectInputState {
|
|
|
237
242
|
return;
|
|
238
243
|
if (e.key === kbd.TAB)
|
|
239
244
|
return;
|
|
240
|
-
if (e.key === kbd.BACKSPACE && this.root.inputValue === "")
|
|
245
|
+
if (e.key === kbd.BACKSPACE && this.root.opts.inputValue.current === "")
|
|
241
246
|
return;
|
|
242
247
|
this.root.handleOpen();
|
|
243
248
|
// we need to wait for a tick after the menu opens to ensure the highlighted nodes are
|
|
@@ -319,7 +324,7 @@ class SelectInputState {
|
|
|
319
324
|
}
|
|
320
325
|
}
|
|
321
326
|
oninput(e) {
|
|
322
|
-
this.root.inputValue = e.currentTarget.value;
|
|
327
|
+
this.root.opts.inputValue.current = e.currentTarget.value;
|
|
323
328
|
this.root.setHighlightedToFirstCandidate();
|
|
324
329
|
}
|
|
325
330
|
props = $derived.by(() => ({
|
|
@@ -347,9 +352,11 @@ class SelectComboTriggerState {
|
|
|
347
352
|
this.onpointerdown = this.onpointerdown.bind(this);
|
|
348
353
|
}
|
|
349
354
|
onkeydown(e) {
|
|
355
|
+
if (!this.root.domContext)
|
|
356
|
+
return;
|
|
350
357
|
if (e.key === kbd.ENTER || e.key === kbd.SPACE) {
|
|
351
358
|
e.preventDefault();
|
|
352
|
-
if (
|
|
359
|
+
if (this.root.domContext.getActiveElement() !== this.root.inputNode) {
|
|
353
360
|
this.root.inputNode?.focus();
|
|
354
361
|
}
|
|
355
362
|
this.root.toggleMenu();
|
|
@@ -360,10 +367,10 @@ class SelectComboTriggerState {
|
|
|
360
367
|
* behavior of focusing the button and keep focus on the input.
|
|
361
368
|
*/
|
|
362
369
|
onpointerdown(e) {
|
|
363
|
-
if (this.root.opts.disabled.current)
|
|
370
|
+
if (this.root.opts.disabled.current || !this.root.domContext)
|
|
364
371
|
return;
|
|
365
372
|
e.preventDefault();
|
|
366
|
-
if (
|
|
373
|
+
if (this.root.domContext.getActiveElement() !== this.root.inputNode) {
|
|
367
374
|
this.root.inputNode?.focus();
|
|
368
375
|
}
|
|
369
376
|
this.root.toggleMenu();
|
|
@@ -388,11 +395,14 @@ class SelectTriggerState {
|
|
|
388
395
|
constructor(opts, root) {
|
|
389
396
|
this.opts = opts;
|
|
390
397
|
this.root = root;
|
|
398
|
+
this.root.domContext = new DOMContext(opts.ref);
|
|
391
399
|
this.#domTypeahead = useDOMTypeahead({
|
|
392
400
|
getCurrentItem: () => this.root.highlightedNode,
|
|
393
401
|
onMatch: (node) => {
|
|
394
402
|
this.root.setHighlightedNode(node);
|
|
395
403
|
},
|
|
404
|
+
getActiveElement: () => this.root.domContext.getActiveElement(),
|
|
405
|
+
getWindow: () => this.root.domContext.getWindow(),
|
|
396
406
|
});
|
|
397
407
|
this.#dataTypeahead = useDataTypeahead({
|
|
398
408
|
getCurrentItem: () => {
|
|
@@ -412,6 +422,7 @@ class SelectTriggerState {
|
|
|
412
422
|
},
|
|
413
423
|
enabled: !this.root.isMulti && this.root.dataTypeaheadEnabled,
|
|
414
424
|
candidateValues: () => (this.root.isMulti ? [] : this.root.candidateLabels),
|
|
425
|
+
getWindow: () => this.root.domContext.getWindow(),
|
|
415
426
|
});
|
|
416
427
|
this.onkeydown = this.onkeydown.bind(this);
|
|
417
428
|
this.onpointerdown = this.onpointerdown.bind(this);
|
|
@@ -614,9 +625,14 @@ class SelectContentState {
|
|
|
614
625
|
root;
|
|
615
626
|
viewportNode = $state(null);
|
|
616
627
|
isPositioned = $state(false);
|
|
628
|
+
domContext;
|
|
617
629
|
constructor(opts, root) {
|
|
618
630
|
this.opts = opts;
|
|
619
631
|
this.root = root;
|
|
632
|
+
this.domContext = new DOMContext(this.opts.ref);
|
|
633
|
+
if (this.root.domContext === null) {
|
|
634
|
+
this.root.domContext = this.domContext;
|
|
635
|
+
}
|
|
620
636
|
onDestroyEffect(() => {
|
|
621
637
|
this.root.contentNode = null;
|
|
622
638
|
this.isPositioned = false;
|
|
@@ -924,16 +940,16 @@ class SelectScrollButtonImplState {
|
|
|
924
940
|
this.onpointerleave = this.onpointerleave.bind(this);
|
|
925
941
|
}
|
|
926
942
|
handleUserScroll() {
|
|
927
|
-
|
|
943
|
+
this.content.domContext.clearTimeout(this.userScrollTimer);
|
|
928
944
|
this.isUserScrolling = true;
|
|
929
|
-
this.userScrollTimer =
|
|
945
|
+
this.userScrollTimer = this.content.domContext.setTimeout(() => {
|
|
930
946
|
this.isUserScrolling = false;
|
|
931
947
|
}, 200);
|
|
932
948
|
}
|
|
933
949
|
clearAutoScrollInterval() {
|
|
934
950
|
if (this.autoScrollTimer === null)
|
|
935
951
|
return;
|
|
936
|
-
|
|
952
|
+
this.content.domContext.clearTimeout(this.autoScrollTimer);
|
|
937
953
|
this.autoScrollTimer = null;
|
|
938
954
|
}
|
|
939
955
|
onpointerdown(_) {
|
|
@@ -941,9 +957,9 @@ class SelectScrollButtonImplState {
|
|
|
941
957
|
return;
|
|
942
958
|
const autoScroll = (tick) => {
|
|
943
959
|
this.onAutoScroll();
|
|
944
|
-
this.autoScrollTimer =
|
|
960
|
+
this.autoScrollTimer = this.content.domContext.setTimeout(() => autoScroll(tick + 1), this.opts.delay.current(tick));
|
|
945
961
|
};
|
|
946
|
-
this.autoScrollTimer =
|
|
962
|
+
this.autoScrollTimer = this.content.domContext.setTimeout(() => autoScroll(1), this.opts.delay.current(0));
|
|
947
963
|
}
|
|
948
964
|
onpointermove(e) {
|
|
949
965
|
this.onpointerdown(e);
|
|
@@ -132,6 +132,29 @@ export function getThumbLabelStyles(direction, thumbPosition, labelPosition = "t
|
|
|
132
132
|
}
|
|
133
133
|
return style;
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Gets the number of decimal places in a number
|
|
137
|
+
*/
|
|
138
|
+
function getDecimalPlaces(num) {
|
|
139
|
+
if (Math.floor(num) === num)
|
|
140
|
+
return 0;
|
|
141
|
+
const str = num.toString();
|
|
142
|
+
if (str.indexOf(".") !== -1 && str.indexOf("e-") === -1) {
|
|
143
|
+
return str.split(".")[1].length;
|
|
144
|
+
}
|
|
145
|
+
else if (str.indexOf("e-") !== -1) {
|
|
146
|
+
const parts = str.split("e-");
|
|
147
|
+
return parseInt(parts[1], 10);
|
|
148
|
+
}
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Rounds a number to the specified number of decimal places
|
|
153
|
+
*/
|
|
154
|
+
function roundToPrecision(num, precision) {
|
|
155
|
+
const factor = Math.pow(10, precision);
|
|
156
|
+
return Math.round(num * factor) / factor;
|
|
157
|
+
}
|
|
135
158
|
/**
|
|
136
159
|
* Normalizes step to always be a sorted array of valid values within min/max range
|
|
137
160
|
*/
|
|
@@ -140,13 +163,21 @@ export function normalizeSteps(step, min, max) {
|
|
|
140
163
|
// generate regular steps - match original behavior exactly
|
|
141
164
|
const difference = max - min;
|
|
142
165
|
let count = Math.ceil(difference / step);
|
|
143
|
-
|
|
166
|
+
// Get precision from step to avoid floating point errors
|
|
167
|
+
const precision = getDecimalPlaces(step);
|
|
168
|
+
// Check if difference is divisible by step using integer arithmetic to avoid floating point errors
|
|
169
|
+
const factor = Math.pow(10, precision);
|
|
170
|
+
const intDifference = Math.round(difference * factor);
|
|
171
|
+
const intStep = Math.round(step * factor);
|
|
172
|
+
if (intDifference % intStep === 0) {
|
|
144
173
|
count++;
|
|
145
174
|
}
|
|
146
175
|
const steps = [];
|
|
147
176
|
for (let i = 0; i < count; i++) {
|
|
148
177
|
const value = min + i * step;
|
|
149
|
-
|
|
178
|
+
// Round to the precision of the step to avoid floating point errors
|
|
179
|
+
const roundedValue = roundToPrecision(value, precision);
|
|
180
|
+
steps.push(roundedValue);
|
|
150
181
|
}
|
|
151
182
|
return steps;
|
|
152
183
|
}
|