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,15 +1,16 @@
|
|
|
1
|
-
import { composeHandlers, executeCallbacks } from "svelte-toolbelt";
|
|
1
|
+
import { DOMContext, composeHandlers, contains, executeCallbacks, } from "svelte-toolbelt";
|
|
2
2
|
import { watch } from "runed";
|
|
3
3
|
import { on } from "svelte/events";
|
|
4
4
|
import { noop } from "../../../internal/noop.js";
|
|
5
5
|
import { isHTMLElement } from "../../../internal/is.js";
|
|
6
|
-
import { isOrContainsTarget } from "../../../internal/elements.js";
|
|
7
6
|
globalThis.bitsTextSelectionLayers ??= new Map();
|
|
8
7
|
export class TextSelectionLayerState {
|
|
9
8
|
opts;
|
|
9
|
+
domContext;
|
|
10
10
|
#unsubSelectionLock = noop;
|
|
11
11
|
constructor(opts) {
|
|
12
12
|
this.opts = opts;
|
|
13
|
+
this.domContext = new DOMContext(opts.ref);
|
|
13
14
|
let unsubEvents = noop;
|
|
14
15
|
watch(() => this.opts.enabled.current, (isEnabled) => {
|
|
15
16
|
if (isEnabled) {
|
|
@@ -25,7 +26,7 @@ export class TextSelectionLayerState {
|
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
#addEventListeners() {
|
|
28
|
-
return executeCallbacks(on(
|
|
29
|
+
return executeCallbacks(on(this.domContext.getDocument(), "pointerdown", this.#pointerdown), on(this.domContext.getDocument(), "pointerup", composeHandlers(this.#resetSelectionLock, this.opts.onPointerUp.current)));
|
|
29
30
|
}
|
|
30
31
|
#pointerdown = (e) => {
|
|
31
32
|
const node = this.opts.ref.current;
|
|
@@ -37,12 +38,12 @@ export class TextSelectionLayerState {
|
|
|
37
38
|
* pointerdown occurred inside the node. You are still allowed to select text
|
|
38
39
|
* outside the node provided pointerdown occurs outside the node.
|
|
39
40
|
*/
|
|
40
|
-
if (!isHighestLayer(this) || !
|
|
41
|
+
if (!isHighestLayer(this) || !contains(node, target))
|
|
41
42
|
return;
|
|
42
43
|
this.opts.onPointerDown.current(e);
|
|
43
44
|
if (e.defaultPrevented)
|
|
44
45
|
return;
|
|
45
|
-
this.#unsubSelectionLock = preventTextSelectionOverflow(node);
|
|
46
|
+
this.#unsubSelectionLock = preventTextSelectionOverflow(node, this.domContext.getDocument().body);
|
|
46
47
|
};
|
|
47
48
|
#resetSelectionLock = () => {
|
|
48
49
|
this.#unsubSelectionLock();
|
|
@@ -53,8 +54,7 @@ export function useTextSelectionLayer(props) {
|
|
|
53
54
|
return new TextSelectionLayerState(props);
|
|
54
55
|
}
|
|
55
56
|
const getUserSelect = (node) => node.style.userSelect || node.style.webkitUserSelect;
|
|
56
|
-
function preventTextSelectionOverflow(node) {
|
|
57
|
-
const body = document.body;
|
|
57
|
+
function preventTextSelectionOverflow(node, body) {
|
|
58
58
|
const originalBodyUserSelect = getUserSelect(body);
|
|
59
59
|
const originalNodeUserSelect = getUserSelect(node);
|
|
60
60
|
setUserSelect(body, "none");
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { type WritableBox } from "svelte-toolbelt";
|
|
2
|
+
type BoxAutoResetOptions<T> = {
|
|
3
|
+
afterMs?: number;
|
|
4
|
+
onChange?: (value: T) => void;
|
|
5
|
+
getWindow: () => Window & typeof globalThis;
|
|
6
|
+
};
|
|
2
7
|
/**
|
|
3
8
|
* Creates a box which will be reset to the default value after some time.
|
|
4
9
|
*
|
|
5
10
|
* @param defaultValue The value which will be set.
|
|
6
11
|
* @param afterMs A zero-or-greater delay in milliseconds.
|
|
7
12
|
*/
|
|
8
|
-
export declare function boxAutoReset<T>(defaultValue: T,
|
|
13
|
+
export declare function boxAutoReset<T>(defaultValue: T, options: BoxAutoResetOptions<T>): WritableBox<T>;
|
|
14
|
+
export {};
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
import { box } from "svelte-toolbelt";
|
|
2
2
|
import { noop } from "./noop.js";
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
afterMs: 10000,
|
|
5
|
+
onChange: noop,
|
|
6
|
+
};
|
|
3
7
|
/**
|
|
4
8
|
* Creates a box which will be reset to the default value after some time.
|
|
5
9
|
*
|
|
6
10
|
* @param defaultValue The value which will be set.
|
|
7
11
|
* @param afterMs A zero-or-greater delay in milliseconds.
|
|
8
12
|
*/
|
|
9
|
-
export function boxAutoReset(defaultValue,
|
|
13
|
+
export function boxAutoReset(defaultValue, options) {
|
|
14
|
+
const { afterMs, onChange, getWindow } = { ...defaultOptions, ...options };
|
|
10
15
|
let timeout = null;
|
|
11
16
|
let value = $state(defaultValue);
|
|
12
17
|
function resetAfter() {
|
|
13
|
-
return
|
|
18
|
+
return getWindow().setTimeout(() => {
|
|
14
19
|
value = defaultValue;
|
|
15
|
-
onChange(defaultValue);
|
|
20
|
+
onChange?.(defaultValue);
|
|
16
21
|
}, afterMs);
|
|
17
22
|
}
|
|
18
23
|
$effect(() => {
|
|
19
24
|
return () => {
|
|
20
25
|
if (timeout)
|
|
21
|
-
clearTimeout(timeout);
|
|
26
|
+
getWindow().clearTimeout(timeout);
|
|
22
27
|
};
|
|
23
28
|
});
|
|
24
29
|
return box.with(() => value, (v) => {
|
|
25
30
|
value = v;
|
|
26
|
-
onChange(v);
|
|
31
|
+
onChange?.(v);
|
|
27
32
|
if (timeout)
|
|
28
|
-
clearTimeout(timeout);
|
|
33
|
+
getWindow().clearTimeout(timeout);
|
|
29
34
|
timeout = resetAfter();
|
|
30
35
|
});
|
|
31
36
|
}
|
|
@@ -2,6 +2,6 @@ export type Announcer = ReturnType<typeof getAnnouncer>;
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates an announcer object that can be used to make `aria-live` announcements to screen readers.
|
|
4
4
|
*/
|
|
5
|
-
export declare function getAnnouncer(): {
|
|
5
|
+
export declare function getAnnouncer(doc: Document | null): {
|
|
6
6
|
announce: (value: string | null | number, kind?: "assertive" | "polite", timeout?: number) => NodeJS.Timeout | undefined;
|
|
7
7
|
};
|
|
@@ -5,40 +5,40 @@ import { isBrowser, isHTMLElement } from "../is.js";
|
|
|
5
5
|
* Within the date components, we use this to announce when the values of the individual segments
|
|
6
6
|
* change, as without it we get inconsistent behavior across screen readers.
|
|
7
7
|
*/
|
|
8
|
-
function initAnnouncer() {
|
|
9
|
-
if (!isBrowser)
|
|
8
|
+
function initAnnouncer(doc) {
|
|
9
|
+
if (!isBrowser || !doc)
|
|
10
10
|
return null;
|
|
11
|
-
let el =
|
|
12
|
-
if (!isHTMLElement(el)) {
|
|
13
|
-
const div = document.createElement("div");
|
|
14
|
-
div.style.cssText = srOnlyStylesString;
|
|
15
|
-
div.setAttribute("data-bits-announcer", "");
|
|
16
|
-
div.appendChild(createLog("assertive"));
|
|
17
|
-
div.appendChild(createLog("polite"));
|
|
18
|
-
el = div;
|
|
19
|
-
document.body.insertBefore(el, document.body.firstChild);
|
|
20
|
-
}
|
|
11
|
+
let el = doc.querySelector("[data-bits-announcer]");
|
|
21
12
|
/**
|
|
22
13
|
* Creates a log element for assertive or polite announcements.
|
|
23
14
|
*/
|
|
24
|
-
|
|
25
|
-
const log =
|
|
15
|
+
const createLog = (kind) => {
|
|
16
|
+
const log = doc.createElement("div");
|
|
26
17
|
log.role = "log";
|
|
27
18
|
log.ariaLive = kind;
|
|
28
19
|
log.setAttribute("aria-relevant", "additions");
|
|
29
20
|
return log;
|
|
21
|
+
};
|
|
22
|
+
if (!isHTMLElement(el)) {
|
|
23
|
+
const div = doc.createElement("div");
|
|
24
|
+
div.style.cssText = srOnlyStylesString;
|
|
25
|
+
div.setAttribute("data-bits-announcer", "");
|
|
26
|
+
div.appendChild(createLog("assertive"));
|
|
27
|
+
div.appendChild(createLog("polite"));
|
|
28
|
+
el = div;
|
|
29
|
+
doc.body.insertBefore(el, doc.body.firstChild);
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Retrieves the log element for assertive or polite announcements.
|
|
33
33
|
*/
|
|
34
|
-
|
|
34
|
+
const getLog = (kind) => {
|
|
35
35
|
if (!isHTMLElement(el))
|
|
36
36
|
return null;
|
|
37
37
|
const log = el.querySelector(`[aria-live="${kind}"]`);
|
|
38
38
|
if (!isHTMLElement(log))
|
|
39
39
|
return null;
|
|
40
40
|
return log;
|
|
41
|
-
}
|
|
41
|
+
};
|
|
42
42
|
return {
|
|
43
43
|
getLog,
|
|
44
44
|
};
|
|
@@ -46,16 +46,16 @@ function initAnnouncer() {
|
|
|
46
46
|
/**
|
|
47
47
|
* Creates an announcer object that can be used to make `aria-live` announcements to screen readers.
|
|
48
48
|
*/
|
|
49
|
-
export function getAnnouncer() {
|
|
50
|
-
const announcer = initAnnouncer();
|
|
49
|
+
export function getAnnouncer(doc) {
|
|
50
|
+
const announcer = initAnnouncer(doc);
|
|
51
51
|
/**
|
|
52
52
|
* Announces a message to screen readers using the specified kind of announcement.
|
|
53
53
|
*/
|
|
54
54
|
function announce(value, kind = "assertive", timeout = 7500) {
|
|
55
|
-
if (!announcer || !isBrowser)
|
|
55
|
+
if (!announcer || !isBrowser || !doc)
|
|
56
56
|
return;
|
|
57
57
|
const log = announcer.getLog(kind);
|
|
58
|
-
const content =
|
|
58
|
+
const content = doc.createElement("div");
|
|
59
59
|
if (typeof value === "number") {
|
|
60
60
|
value = value.toString();
|
|
61
61
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { endOfMonth, isSameDay, isSameMonth, startOfMonth, } from "@internationalized/date";
|
|
2
|
-
import { afterTick, styleToString } from "svelte-toolbelt";
|
|
2
|
+
import { afterTick, getDocument, styleToString, } from "svelte-toolbelt";
|
|
3
3
|
import { untrack } from "svelte";
|
|
4
4
|
import { getDaysInMonth, getLastFirstDayOfWeek, getNextLastDayOfWeek, hasTime, isAfter, isBefore, parseAnyDateValue, parseStringToDateValue, toDate, } from "./utils.js";
|
|
5
5
|
import { getDataDisabled, getDataInvalid, getDataReadonly } from "../attrs.js";
|
|
@@ -331,7 +331,8 @@ export function useMonthViewOptionsSync(props) {
|
|
|
331
331
|
* Returns a function that removes the heading element.
|
|
332
332
|
*/
|
|
333
333
|
export function createAccessibleHeading({ calendarNode, label, accessibleHeadingId, }) {
|
|
334
|
-
const
|
|
334
|
+
const doc = getDocument(calendarNode);
|
|
335
|
+
const div = doc.createElement("div");
|
|
335
336
|
div.style.cssText = styleToString({
|
|
336
337
|
border: "0px",
|
|
337
338
|
clip: "rect(0px, 0px, 0px, 0px)",
|
|
@@ -344,7 +345,7 @@ export function createAccessibleHeading({ calendarNode, label, accessibleHeading
|
|
|
344
345
|
whiteSpace: "nowrap",
|
|
345
346
|
width: "1px",
|
|
346
347
|
});
|
|
347
|
-
const h2 =
|
|
348
|
+
const h2 = doc.createElement("div");
|
|
348
349
|
h2.textContent = label;
|
|
349
350
|
h2.id = accessibleHeadingId;
|
|
350
351
|
h2.role = "heading";
|
|
@@ -352,7 +353,7 @@ export function createAccessibleHeading({ calendarNode, label, accessibleHeading
|
|
|
352
353
|
calendarNode.insertBefore(div, calendarNode.firstChild);
|
|
353
354
|
div.appendChild(h2);
|
|
354
355
|
return () => {
|
|
355
|
-
const h2 =
|
|
356
|
+
const h2 = doc.getElementById(accessibleHeadingId);
|
|
356
357
|
if (!h2)
|
|
357
358
|
return;
|
|
358
359
|
div.parentElement?.removeChild(div);
|
|
@@ -442,7 +443,8 @@ export function getCalendarElementProps({ fullCalendarLabel, id, isInvalid, disa
|
|
|
442
443
|
};
|
|
443
444
|
}
|
|
444
445
|
export function pickerOpenFocus(e) {
|
|
445
|
-
const
|
|
446
|
+
const doc = getDocument(e.target);
|
|
447
|
+
const nodeToFocus = doc.querySelector("[data-bits-day][data-focused]");
|
|
446
448
|
if (nodeToFocus) {
|
|
447
449
|
e.preventDefault();
|
|
448
450
|
nodeToFocus?.focus();
|
|
@@ -61,6 +61,12 @@ export declare function isAcceptableSegmentKey(key: string): boolean;
|
|
|
61
61
|
* @param fieldNode - The id of the date field associated with the segment
|
|
62
62
|
*/
|
|
63
63
|
export declare function isFirstSegment(id: string, fieldNode: HTMLElement | null): boolean;
|
|
64
|
+
type SetDescriptionProps = {
|
|
65
|
+
id: string;
|
|
66
|
+
formatter: Formatter;
|
|
67
|
+
value: DateValue;
|
|
68
|
+
doc: Document;
|
|
69
|
+
};
|
|
64
70
|
/**
|
|
65
71
|
* Creates or updates a description element for a date field
|
|
66
72
|
* which enables screen readers to read the date field's value.
|
|
@@ -69,12 +75,12 @@ export declare function isFirstSegment(id: string, fieldNode: HTMLElement | null
|
|
|
69
75
|
* so it can be associated via `aria-describedby` and read by
|
|
70
76
|
* screen readers as the user interacts with the date field.
|
|
71
77
|
*/
|
|
72
|
-
export declare function setDescription(
|
|
78
|
+
export declare function setDescription(props: SetDescriptionProps): void;
|
|
73
79
|
/**
|
|
74
80
|
* Removes the description element for the date field with
|
|
75
81
|
* the provided ID. This function should be called when the
|
|
76
82
|
* date field is unmounted.
|
|
77
83
|
*/
|
|
78
|
-
export declare function removeDescriptionElement(id: string): void;
|
|
84
|
+
export declare function removeDescriptionElement(id: string, doc: Document): void;
|
|
79
85
|
export declare function getDefaultHourCycle(locale: string): 12 | 24;
|
|
80
86
|
export {};
|
|
@@ -345,19 +345,20 @@ export function isFirstSegment(id, fieldNode) {
|
|
|
345
345
|
* so it can be associated via `aria-describedby` and read by
|
|
346
346
|
* screen readers as the user interacts with the date field.
|
|
347
347
|
*/
|
|
348
|
-
export function setDescription(
|
|
348
|
+
export function setDescription(props) {
|
|
349
|
+
const { id, formatter, value, doc } = props;
|
|
349
350
|
if (!isBrowser)
|
|
350
351
|
return;
|
|
351
352
|
const valueString = formatter.selectedDate(value);
|
|
352
|
-
const el =
|
|
353
|
+
const el = doc.getElementById(id);
|
|
353
354
|
if (!el) {
|
|
354
|
-
const div =
|
|
355
|
+
const div = doc.createElement("div");
|
|
355
356
|
div.style.cssText = styleToString({
|
|
356
357
|
display: "none",
|
|
357
358
|
});
|
|
358
359
|
div.id = id;
|
|
359
360
|
div.innerText = `Selected Date: ${valueString}`;
|
|
360
|
-
|
|
361
|
+
doc.body.appendChild(div);
|
|
361
362
|
}
|
|
362
363
|
else {
|
|
363
364
|
el.innerText = `Selected Date: ${valueString}`;
|
|
@@ -368,13 +369,13 @@ export function setDescription(id, formatter, value) {
|
|
|
368
369
|
* the provided ID. This function should be called when the
|
|
369
370
|
* date field is unmounted.
|
|
370
371
|
*/
|
|
371
|
-
export function removeDescriptionElement(id) {
|
|
372
|
+
export function removeDescriptionElement(id, doc) {
|
|
372
373
|
if (!isBrowser)
|
|
373
374
|
return;
|
|
374
|
-
const el =
|
|
375
|
+
const el = doc.getElementById(id);
|
|
375
376
|
if (!el)
|
|
376
377
|
return;
|
|
377
|
-
|
|
378
|
+
doc.body.removeChild(el);
|
|
378
379
|
}
|
|
379
380
|
export function getDefaultHourCycle(locale) {
|
|
380
381
|
const formatter = new Intl.DateTimeFormat(locale, { hour: "numeric" });
|
|
@@ -54,6 +54,12 @@ export declare function inferTimeGranularity(granularity: TimeGranularity | unde
|
|
|
54
54
|
* @param fieldNode - The id of the date field associated with the segment
|
|
55
55
|
*/
|
|
56
56
|
export declare function isFirstTimeSegment(id: string, fieldNode: HTMLElement | null): boolean;
|
|
57
|
+
type SetTimeDescriptionProps = {
|
|
58
|
+
id: string;
|
|
59
|
+
formatter: TimeFormatter;
|
|
60
|
+
value: TimeValue;
|
|
61
|
+
doc: Document;
|
|
62
|
+
};
|
|
57
63
|
/**
|
|
58
64
|
* Creates or updates a description element for a date field
|
|
59
65
|
* which enables screen readers to read the date field's value.
|
|
@@ -62,13 +68,13 @@ export declare function isFirstTimeSegment(id: string, fieldNode: HTMLElement |
|
|
|
62
68
|
* so it can be associated via `aria-describedby` and read by
|
|
63
69
|
* screen readers as the user interacts with the date field.
|
|
64
70
|
*/
|
|
65
|
-
export declare function setTimeDescription(
|
|
71
|
+
export declare function setTimeDescription(props: SetTimeDescriptionProps): void;
|
|
66
72
|
/**
|
|
67
73
|
* Removes the description element for the date field with
|
|
68
74
|
* the provided ID. This function should be called when the
|
|
69
75
|
* date field is unmounted.
|
|
70
76
|
*/
|
|
71
|
-
export declare function removeTimeDescriptionElement(id: string): void;
|
|
77
|
+
export declare function removeTimeDescriptionElement(id: string, doc: Document): void;
|
|
72
78
|
export declare function convertTimeValueToDateValue(time: TimeValue): CalendarDateTime | ZonedDateTime;
|
|
73
79
|
export declare function convertTimeValueToTime(time: TimeValue): Time;
|
|
74
80
|
export declare function isTimeBefore(timeToCompare: Time, referenceTime: Time): boolean;
|
|
@@ -245,19 +245,19 @@ export function isFirstTimeSegment(id, fieldNode) {
|
|
|
245
245
|
* so it can be associated via `aria-describedby` and read by
|
|
246
246
|
* screen readers as the user interacts with the date field.
|
|
247
247
|
*/
|
|
248
|
-
export function setTimeDescription(
|
|
248
|
+
export function setTimeDescription(props) {
|
|
249
249
|
if (!isBrowser)
|
|
250
250
|
return;
|
|
251
|
-
const valueString = formatter.selectedTime(value);
|
|
252
|
-
const el =
|
|
251
|
+
const valueString = props.formatter.selectedTime(props.value);
|
|
252
|
+
const el = props.doc.getElementById(props.id);
|
|
253
253
|
if (!el) {
|
|
254
|
-
const div =
|
|
254
|
+
const div = props.doc.createElement("div");
|
|
255
255
|
div.style.cssText = styleToString({
|
|
256
256
|
display: "none",
|
|
257
257
|
});
|
|
258
|
-
div.id = id;
|
|
258
|
+
div.id = props.id;
|
|
259
259
|
div.innerText = `Selected Time: ${valueString}`;
|
|
260
|
-
|
|
260
|
+
props.doc.body.appendChild(div);
|
|
261
261
|
}
|
|
262
262
|
else {
|
|
263
263
|
el.innerText = `Selected Time: ${valueString}`;
|
|
@@ -268,13 +268,13 @@ export function setTimeDescription(id, formatter, value) {
|
|
|
268
268
|
* the provided ID. This function should be called when the
|
|
269
269
|
* date field is unmounted.
|
|
270
270
|
*/
|
|
271
|
-
export function removeTimeDescriptionElement(id) {
|
|
271
|
+
export function removeTimeDescriptionElement(id, doc) {
|
|
272
272
|
if (!isBrowser)
|
|
273
273
|
return;
|
|
274
|
-
const el =
|
|
274
|
+
const el = doc.getElementById(id);
|
|
275
275
|
if (!el)
|
|
276
276
|
return;
|
|
277
|
-
|
|
277
|
+
doc.body.removeChild(el);
|
|
278
278
|
}
|
|
279
279
|
export function convertTimeValueToDateValue(time) {
|
|
280
280
|
if (time instanceof Time) {
|
package/dist/internal/dom.d.ts
CHANGED
package/dist/internal/dom.js
CHANGED
package/dist/internal/focus.d.ts
CHANGED
|
@@ -20,9 +20,9 @@ export declare function focus(element?: FocusableTarget | null, { select }?: {
|
|
|
20
20
|
* Attempts to focus the first element in a list of candidates.
|
|
21
21
|
* Stops when focus is successful.
|
|
22
22
|
*/
|
|
23
|
-
export declare function focusFirst(candidates: HTMLElement[], { select }
|
|
23
|
+
export declare function focusFirst(candidates: HTMLElement[], { select }: {
|
|
24
24
|
select?: boolean | undefined;
|
|
25
|
-
}): true | undefined;
|
|
25
|
+
} | undefined, getActiveElement: () => HTMLElement | null): true | undefined;
|
|
26
26
|
/**
|
|
27
27
|
* Returns the first visible element in a list.
|
|
28
28
|
* NOTE: Only checks visibility up to the `container`.
|
package/dist/internal/focus.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getDocument, getWindow } from "svelte-toolbelt";
|
|
1
2
|
import { isBrowser, isElementHidden, isSelectableInput } from "./is.js";
|
|
2
3
|
/**
|
|
3
4
|
* Handles `initialFocus` prop behavior for the
|
|
@@ -20,12 +21,14 @@ export function handleCalendarInitialFocus(calendar) {
|
|
|
20
21
|
* A utility function that focuses an element without scrolling.
|
|
21
22
|
*/
|
|
22
23
|
export function focusWithoutScroll(element) {
|
|
24
|
+
const doc = getDocument(element);
|
|
25
|
+
const win = getWindow(element);
|
|
23
26
|
const scrollPosition = {
|
|
24
|
-
x:
|
|
25
|
-
y:
|
|
27
|
+
x: win.pageXOffset || doc.documentElement.scrollLeft,
|
|
28
|
+
y: win.pageYOffset || doc.documentElement.scrollTop,
|
|
26
29
|
};
|
|
27
30
|
element.focus();
|
|
28
|
-
|
|
31
|
+
win.scrollTo(scrollPosition.x, scrollPosition.y);
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* A utility function that focuses an element.
|
|
@@ -33,9 +36,10 @@ export function focusWithoutScroll(element) {
|
|
|
33
36
|
export function focus(element, { select = false } = {}) {
|
|
34
37
|
if (!(element && element.focus))
|
|
35
38
|
return;
|
|
36
|
-
|
|
39
|
+
const doc = getDocument(element);
|
|
40
|
+
if (doc.activeElement === element)
|
|
37
41
|
return;
|
|
38
|
-
const previouslyFocusedElement =
|
|
42
|
+
const previouslyFocusedElement = doc.activeElement;
|
|
39
43
|
// prevent scroll on focus
|
|
40
44
|
element.focus({ preventScroll: true });
|
|
41
45
|
// only elect if its not the same element, it supports selection, and we need to select it
|
|
@@ -47,11 +51,11 @@ export function focus(element, { select = false } = {}) {
|
|
|
47
51
|
* Attempts to focus the first element in a list of candidates.
|
|
48
52
|
* Stops when focus is successful.
|
|
49
53
|
*/
|
|
50
|
-
export function focusFirst(candidates, { select = false } = {}) {
|
|
51
|
-
const previouslyFocusedElement =
|
|
54
|
+
export function focusFirst(candidates, { select = false } = {}, getActiveElement) {
|
|
55
|
+
const previouslyFocusedElement = getActiveElement();
|
|
52
56
|
for (const candidate of candidates) {
|
|
53
57
|
focus(candidate, { select });
|
|
54
|
-
if (
|
|
58
|
+
if (getActiveElement() !== previouslyFocusedElement)
|
|
55
59
|
return true;
|
|
56
60
|
}
|
|
57
61
|
}
|
|
@@ -78,7 +82,8 @@ export function findVisible(elements, container) {
|
|
|
78
82
|
*/
|
|
79
83
|
export function getTabbableCandidates(container) {
|
|
80
84
|
const nodes = [];
|
|
81
|
-
const
|
|
85
|
+
const doc = getDocument(container);
|
|
86
|
+
const walker = doc.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
|
|
82
87
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
88
|
acceptNode: (node) => {
|
|
84
89
|
const isHiddenInput = node.tagName === "INPUT" && node.type === "hidden";
|
package/dist/internal/math.d.ts
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* From https://github.com/melt-ui/melt-ui/blob/main/packages/svelte/src/lib/internal/math.ts
|
|
3
|
-
*/
|
|
4
|
-
export declare function snapValueToStep(value: number, min: number, max: number, step: number): number;
|
|
5
1
|
export declare function linearScale(domain: [number, number], range: [number, number], clamp?: boolean): (x: number) => number;
|
package/dist/internal/math.js
CHANGED
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* From https://github.com/melt-ui/melt-ui/blob/main/packages/svelte/src/lib/internal/math.ts
|
|
3
|
-
*/
|
|
4
|
-
export function snapValueToStep(value, min, max, step) {
|
|
5
|
-
const remainder = (value - (Number.isNaN(min) ? 0 : min)) % step;
|
|
6
|
-
let snappedValue = Math.abs(remainder) * 2 >= step
|
|
7
|
-
? value + Math.sign(remainder) * (step - Math.abs(remainder))
|
|
8
|
-
: value - remainder;
|
|
9
|
-
if (!Number.isNaN(min)) {
|
|
10
|
-
if (snappedValue < min) {
|
|
11
|
-
snappedValue = min;
|
|
12
|
-
}
|
|
13
|
-
else if (!Number.isNaN(max) && snappedValue > max) {
|
|
14
|
-
snappedValue = min + Math.floor((max - min) / step) * step;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
else if (!Number.isNaN(max) && snappedValue > max) {
|
|
18
|
-
snappedValue = Math.floor(max / step) * step;
|
|
19
|
-
}
|
|
20
|
-
const string = step.toString();
|
|
21
|
-
const index = string.indexOf(".");
|
|
22
|
-
const precision = index >= 0 ? string.length - index : 0;
|
|
23
|
-
if (precision > 0) {
|
|
24
|
-
const pow = 10 ** precision;
|
|
25
|
-
snappedValue = Math.round(snappedValue * pow) / pow;
|
|
26
|
-
}
|
|
27
|
-
return snappedValue;
|
|
28
|
-
}
|
|
29
1
|
export function linearScale(domain, range, clamp = true) {
|
|
30
2
|
const [d0, d1] = domain;
|
|
31
3
|
const [r0, r1] = range;
|
|
@@ -6,5 +6,3 @@ export declare function getTabbableIn(container: HTMLElement, direction: "next"
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function getTabbableFrom(currentNode: HTMLElement, direction: "next" | "prev"): import("tabbable").FocusableElement | undefined;
|
|
8
8
|
export declare function getTabbableFromFocusable(currentNode: HTMLElement, direction: "next" | "prev"): import("tabbable").FocusableElement;
|
|
9
|
-
export declare function getNextTabbable(): import("tabbable").FocusableElement | undefined;
|
|
10
|
-
export declare function getPreviousTabbable(): import("tabbable").FocusableElement | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { focusable, isFocusable, isTabbable, tabbable } from "tabbable";
|
|
2
|
-
import { activeElement
|
|
2
|
+
import { activeElement } from "./dom.js";
|
|
3
|
+
import { getDocument } from "svelte-toolbelt";
|
|
3
4
|
function getTabbableOptions() {
|
|
4
5
|
return {
|
|
5
6
|
getShadowRoot: true,
|
|
@@ -32,35 +33,30 @@ export function getTabbableFrom(currentNode, direction) {
|
|
|
32
33
|
if (!isTabbable(currentNode, getTabbableOptions())) {
|
|
33
34
|
return getTabbableFromFocusable(currentNode, direction);
|
|
34
35
|
}
|
|
35
|
-
const
|
|
36
|
+
const doc = getDocument(currentNode);
|
|
37
|
+
const allTabbable = tabbable(doc.body, getTabbableOptions());
|
|
36
38
|
if (direction === "prev")
|
|
37
39
|
allTabbable.reverse();
|
|
38
40
|
const activeIndex = allTabbable.indexOf(currentNode);
|
|
39
41
|
if (activeIndex === -1)
|
|
40
|
-
return
|
|
42
|
+
return doc.body;
|
|
41
43
|
const nextTabbableElements = allTabbable.slice(activeIndex + 1);
|
|
42
44
|
return nextTabbableElements[0];
|
|
43
45
|
}
|
|
44
46
|
export function getTabbableFromFocusable(currentNode, direction) {
|
|
47
|
+
const doc = getDocument(currentNode);
|
|
45
48
|
if (!isFocusable(currentNode, getTabbableOptions()))
|
|
46
|
-
return
|
|
49
|
+
return doc.body;
|
|
47
50
|
// find all focusable nodes, since some elements may be focusable but not tabbable
|
|
48
51
|
// such as context menu triggers
|
|
49
|
-
const allFocusable = focusable(
|
|
52
|
+
const allFocusable = focusable(doc.body, getTabbableOptions());
|
|
50
53
|
// find index of current node among focusable siblings
|
|
51
54
|
if (direction === "prev")
|
|
52
55
|
allFocusable.reverse();
|
|
53
56
|
const activeIndex = allFocusable.indexOf(currentNode);
|
|
54
57
|
if (activeIndex === -1)
|
|
55
|
-
return
|
|
58
|
+
return doc.body;
|
|
56
59
|
const nextFocusableElements = allFocusable.slice(activeIndex + 1);
|
|
57
60
|
// find the next focusable node that is also tabbable
|
|
58
|
-
return
|
|
59
|
-
document.body);
|
|
60
|
-
}
|
|
61
|
-
export function getNextTabbable() {
|
|
62
|
-
return getTabbableIn(document.body, "next");
|
|
63
|
-
}
|
|
64
|
-
export function getPreviousTabbable() {
|
|
65
|
-
return getTabbableIn(document.body, "prev");
|
|
61
|
+
return nextFocusableElements.find((node) => isTabbable(node, getTabbableOptions())) ?? doc.body;
|
|
66
62
|
}
|
|
@@ -5,6 +5,7 @@ type UseDataTypeaheadOpts = {
|
|
|
5
5
|
getCurrentItem: () => string;
|
|
6
6
|
candidateValues: Getter<string[]>;
|
|
7
7
|
enabled: boolean;
|
|
8
|
+
getWindow: () => Window & typeof globalThis;
|
|
8
9
|
};
|
|
9
10
|
export declare function useDataTypeahead(opts: UseDataTypeaheadOpts): {
|
|
10
11
|
search: import("svelte-toolbelt").WritableBox<string>;
|
|
@@ -2,7 +2,10 @@ import { getNextMatch } from "./arrays.js";
|
|
|
2
2
|
import { boxAutoReset } from "./box-auto-reset.svelte.js";
|
|
3
3
|
export function useDataTypeahead(opts) {
|
|
4
4
|
// Reset `search` 1 second after it was last updated
|
|
5
|
-
const search = boxAutoReset("",
|
|
5
|
+
const search = boxAutoReset("", {
|
|
6
|
+
afterMs: 1000,
|
|
7
|
+
getWindow: opts.getWindow,
|
|
8
|
+
});
|
|
6
9
|
const candidateValues = $derived(opts.candidateValues());
|
|
7
10
|
function handleTypeaheadSearch(key) {
|
|
8
11
|
if (!opts.enabled)
|
|
@@ -2,8 +2,10 @@ export type DOMTypeahead = ReturnType<typeof useDOMTypeahead>;
|
|
|
2
2
|
type UseDOMTypeaheadOpts = {
|
|
3
3
|
onMatch?: (item: HTMLElement) => void;
|
|
4
4
|
getCurrentItem?: () => HTMLElement | null;
|
|
5
|
+
getActiveElement: () => HTMLElement | null;
|
|
6
|
+
getWindow: () => Window & typeof globalThis;
|
|
5
7
|
};
|
|
6
|
-
export declare function useDOMTypeahead(opts
|
|
8
|
+
export declare function useDOMTypeahead(opts: UseDOMTypeaheadOpts): {
|
|
7
9
|
search: import("svelte-toolbelt").WritableBox<string>;
|
|
8
10
|
handleTypeaheadSearch: (key: string, candidates: HTMLElement[]) => HTMLElement | undefined;
|
|
9
11
|
resetTypeahead: () => void;
|
|
@@ -2,9 +2,12 @@ import { getNextMatch } from "./arrays.js";
|
|
|
2
2
|
import { boxAutoReset } from "./box-auto-reset.svelte.js";
|
|
3
3
|
export function useDOMTypeahead(opts) {
|
|
4
4
|
// Reset `search` 1 second after it was last updated
|
|
5
|
-
const search = boxAutoReset("",
|
|
5
|
+
const search = boxAutoReset("", {
|
|
6
|
+
afterMs: 1000,
|
|
7
|
+
getWindow: opts.getWindow,
|
|
8
|
+
});
|
|
6
9
|
const onMatch = opts?.onMatch ?? ((node) => node.focus());
|
|
7
|
-
const getCurrentItem = opts?.getCurrentItem ?? (() =>
|
|
10
|
+
const getCurrentItem = opts?.getCurrentItem ?? (() => opts?.getActiveElement());
|
|
8
11
|
function handleTypeaheadSearch(key, candidates) {
|
|
9
12
|
if (!candidates.length)
|
|
10
13
|
return;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { executeCallbacks } from "svelte-toolbelt";
|
|
1
|
+
import { executeCallbacks, getWindow } from "svelte-toolbelt";
|
|
2
2
|
import { on } from "svelte/events";
|
|
3
3
|
import { watch } from "runed";
|
|
4
4
|
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,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const isPointerInTransit = boxAutoReset(false, {
|
|
9
|
+
afterMs: opts.transitTimeout ?? 300,
|
|
10
|
+
onChange: (value) => {
|
|
11
|
+
if (enabled) {
|
|
12
|
+
opts.setIsPointerInTransit?.(value);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
getWindow: () => getWindow(opts.triggerNode()),
|
|
12
16
|
});
|
|
13
17
|
let pointerGraceArea = $state(null);
|
|
14
18
|
function handleRemoveGraceArea() {
|