bits-ui 2.1.0 → 2.2.0

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.
Files changed (66) hide show
  1. package/dist/bits/avatar/avatar.svelte.d.ts +2 -1
  2. package/dist/bits/avatar/avatar.svelte.js +5 -3
  3. package/dist/bits/calendar/calendar.svelte.d.ts +2 -0
  4. package/dist/bits/calendar/calendar.svelte.js +9 -4
  5. package/dist/bits/combobox/components/combobox-input.svelte +2 -2
  6. package/dist/bits/combobox/components/combobox.svelte +5 -0
  7. package/dist/bits/combobox/types.d.ts +18 -1
  8. package/dist/bits/date-field/date-field.svelte.d.ts +3 -1
  9. package/dist/bits/date-field/date-field.svelte.js +15 -6
  10. package/dist/bits/date-range-field/date-range-field.svelte.d.ts +2 -0
  11. package/dist/bits/date-range-field/date-range-field.svelte.js +4 -2
  12. package/dist/bits/link-preview/link-preview.svelte.d.ts +2 -0
  13. package/dist/bits/link-preview/link-preview.svelte.js +11 -6
  14. package/dist/bits/menu/menu.svelte.d.ts +2 -0
  15. package/dist/bits/menu/menu.svelte.js +15 -10
  16. package/dist/bits/navigation-menu/navigation-menu.svelte.d.ts +3 -1
  17. package/dist/bits/navigation-menu/navigation-menu.svelte.js +21 -11
  18. package/dist/bits/pin-input/pin-input.svelte.d.ts +4 -2
  19. package/dist/bits/pin-input/pin-input.svelte.js +17 -13
  20. package/dist/bits/pin-input/usePasswordManager.svelte.d.ts +3 -2
  21. package/dist/bits/pin-input/usePasswordManager.svelte.js +6 -5
  22. package/dist/bits/range-calendar/range-calendar.svelte.d.ts +2 -0
  23. package/dist/bits/range-calendar/range-calendar.svelte.js +9 -3
  24. package/dist/bits/scroll-area/scroll-area.svelte.d.ts +2 -0
  25. package/dist/bits/scroll-area/scroll-area.svelte.js +15 -12
  26. package/dist/bits/select/components/select.svelte +6 -0
  27. package/dist/bits/select/select.svelte.d.ts +5 -1
  28. package/dist/bits/select/select.svelte.js +34 -18
  29. package/dist/bits/time-field/time-field.svelte.d.ts +3 -1
  30. package/dist/bits/time-field/time-field.svelte.js +15 -6
  31. package/dist/bits/time-range-field/time-range-field.svelte.d.ts +2 -0
  32. package/dist/bits/time-range-field/time-range-field.svelte.js +4 -2
  33. package/dist/bits/tooltip/tooltip.svelte.d.ts +2 -0
  34. package/dist/bits/tooltip/tooltip.svelte.js +4 -2
  35. package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.js +3 -2
  36. package/dist/bits/utilities/focus-scope/use-focus-scope.svelte.js +14 -9
  37. package/dist/bits/utilities/portal/types.d.ts +1 -1
  38. package/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.d.ts +2 -0
  39. package/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.js +7 -7
  40. package/dist/internal/box-auto-reset.svelte.d.ts +7 -1
  41. package/dist/internal/box-auto-reset.svelte.js +11 -6
  42. package/dist/internal/date-time/announcer.d.ts +1 -1
  43. package/dist/internal/date-time/announcer.js +20 -20
  44. package/dist/internal/date-time/calendar-helpers.svelte.js +7 -5
  45. package/dist/internal/date-time/field/helpers.d.ts +8 -2
  46. package/dist/internal/date-time/field/helpers.js +8 -7
  47. package/dist/internal/date-time/field/time-helpers.d.ts +8 -2
  48. package/dist/internal/date-time/field/time-helpers.js +9 -9
  49. package/dist/internal/dom.d.ts +0 -1
  50. package/dist/internal/dom.js +0 -3
  51. package/dist/internal/focus.d.ts +2 -2
  52. package/dist/internal/focus.js +14 -9
  53. package/dist/internal/math.d.ts +0 -4
  54. package/dist/internal/math.js +0 -28
  55. package/dist/internal/tabbable.d.ts +0 -2
  56. package/dist/internal/tabbable.js +10 -14
  57. package/dist/internal/use-data-typeahead.svelte.d.ts +1 -0
  58. package/dist/internal/use-data-typeahead.svelte.js +4 -1
  59. package/dist/internal/use-dom-typeahead.svelte.d.ts +3 -1
  60. package/dist/internal/use-dom-typeahead.svelte.js +5 -2
  61. package/dist/internal/use-grace-area.svelte.js +9 -5
  62. package/package.json +2 -2
  63. package/dist/internal/dom-context.svelte.d.ts +0 -9
  64. package/dist/internal/dom-context.svelte.js +0 -26
  65. package/dist/internal/use-size.svelte.d.ts +0 -7
  66. package/dist/internal/use-size.svelte.js +0 -54
@@ -1,5 +1,5 @@
1
1
  import { CalendarDateTime, Time, ZonedDateTime } from "@internationalized/date";
2
- import { onDestroyEffect, attachRef, box } from "svelte-toolbelt";
2
+ import { onDestroyEffect, attachRef, box, DOMContext } from "svelte-toolbelt";
3
3
  import { onMount, untrack } from "svelte";
4
4
  import { Context, watch } from "runed";
5
5
  import { getAriaDisabled, getAriaHidden, getAriaInvalid, getAriaReadonly, getDataDisabled, getDataInvalid, getDataReadonly, } from "../../internal/attrs.js";
@@ -96,6 +96,7 @@ export class TimeFieldRootState {
96
96
  return getDefaultHourCycle(this.locale.current);
97
97
  });
98
98
  rangeRoot = undefined;
99
+ domContext = new DOMContext(() => null);
99
100
  constructor(props, rangeRoot) {
100
101
  this.rangeRoot = rangeRoot;
101
102
  /**
@@ -124,7 +125,7 @@ export class TimeFieldRootState {
124
125
  this.formatter = createTimeFormatter(this.locale.current);
125
126
  this.initialSegments = this.#initializeTimeSegmentValues();
126
127
  this.segmentValues = this.initialSegments;
127
- this.announcer = getAnnouncer();
128
+ this.announcer = getAnnouncer(null);
128
129
  this.getFieldNode = this.getFieldNode.bind(this);
129
130
  this.updateSegment = this.updateSegment.bind(this);
130
131
  this.handleSegmentClick = this.handleSegmentClick.bind(this);
@@ -135,10 +136,10 @@ export class TimeFieldRootState {
135
136
  });
136
137
  });
137
138
  onMount(() => {
138
- this.announcer = getAnnouncer();
139
+ this.announcer = getAnnouncer(this.domContext.getDocument());
139
140
  });
140
141
  onDestroyEffect(() => {
141
- removeTimeDescriptionElement(this.descriptionId);
142
+ removeTimeDescriptionElement(this.descriptionId, this.domContext.getDocument());
142
143
  });
143
144
  $effect(() => {
144
145
  if (this.formatter.getLocale() === this.locale.current)
@@ -148,7 +149,12 @@ export class TimeFieldRootState {
148
149
  $effect(() => {
149
150
  if (this.value.current) {
150
151
  const descriptionId = untrack(() => this.descriptionId);
151
- setTimeDescription(descriptionId, this.formatter, this.#toDateValue(this.value.current));
152
+ setTimeDescription({
153
+ id: descriptionId,
154
+ formatter: this.formatter,
155
+ value: this.#toDateValue(this.value.current),
156
+ doc: this.domContext.getDocument(),
157
+ });
152
158
  }
153
159
  const placeholder = untrack(() => this.placeholder.current);
154
160
  if (this.value.current && placeholder !== this.value.current) {
@@ -446,9 +452,12 @@ export class TimeFieldRootState {
446
452
  export class TimeFieldInputState {
447
453
  opts;
448
454
  root;
455
+ domContext;
449
456
  constructor(opts, root) {
450
457
  this.opts = opts;
451
458
  this.root = root;
459
+ this.domContext = new DOMContext(opts.ref);
460
+ this.root.setName(this.opts.name.current);
452
461
  $effect(() => {
453
462
  this.root.setName(this.opts.name.current);
454
463
  });
@@ -456,7 +465,7 @@ export class TimeFieldInputState {
456
465
  #ariaDescribedBy = $derived.by(() => {
457
466
  if (!isBrowser)
458
467
  return undefined;
459
- const doesDescriptionExist = document.getElementById(this.root.descriptionId);
468
+ const doesDescriptionExist = this.domContext.getElementById(this.root.descriptionId);
460
469
  if (!doesDescriptionExist)
461
470
  return undefined;
462
471
  return this.root.descriptionId;
@@ -1,4 +1,5 @@
1
1
  import type { Time } from "@internationalized/date";
2
+ import { DOMContext } from "svelte-toolbelt";
2
3
  import { Context } from "runed";
3
4
  import type { TimeFieldRootState } from "../time-field/time-field.svelte.js";
4
5
  import { TimeFieldInputState } from "../time-field/time-field.svelte.js";
@@ -45,6 +46,7 @@ export declare class TimeRangeFieldRootState<T extends TimeValue = Time> {
45
46
  endValueTime: Time | undefined;
46
47
  minValueTime: Time | undefined;
47
48
  maxValueTime: Time | undefined;
49
+ domContext: DOMContext;
48
50
  constructor(opts: TimeRangeFieldRootStateProps<T>);
49
51
  validationStatus: false | {
50
52
  readonly reason: "custom";
@@ -1,4 +1,4 @@
1
- import { box, onDestroyEffect, attachRef } from "svelte-toolbelt";
1
+ import { box, onDestroyEffect, attachRef, DOMContext } from "svelte-toolbelt";
2
2
  import { Context, watch } from "runed";
3
3
  import { TimeFieldInputState, useTimeFieldRoot } from "../time-field/time-field.svelte.js";
4
4
  import { useId } from "../../internal/use-id.js";
@@ -41,11 +41,13 @@ export class TimeRangeFieldRootState {
41
41
  return undefined;
42
42
  return convertTimeValueToTime(this.opts.maxValue.current);
43
43
  });
44
+ domContext;
44
45
  constructor(opts) {
45
46
  this.opts = opts;
46
47
  this.formatter = createTimeFormatter(this.opts.locale.current);
48
+ this.domContext = new DOMContext(this.opts.ref);
47
49
  onDestroyEffect(() => {
48
- removeDescriptionElement(this.descriptionId);
50
+ removeDescriptionElement(this.descriptionId, this.domContext.getDocument());
49
51
  });
50
52
  $effect(() => {
51
53
  if (this.formatter.getLocale() === this.opts.locale.current)
@@ -1,3 +1,4 @@
1
+ import { DOMContext } from "svelte-toolbelt";
1
2
  import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
2
3
  import type { WithRefProps } from "../../internal/types.js";
3
4
  import type { PointerEventHandler } from "svelte/elements";
@@ -53,6 +54,7 @@ declare class TooltipTriggerState {
53
54
  #private;
54
55
  readonly opts: TooltipTriggerStateProps;
55
56
  readonly root: TooltipRootState;
57
+ domContext: DOMContext;
56
58
  constructor(opts: TooltipTriggerStateProps, root: TooltipRootState);
57
59
  handlePointerUp: () => void;
58
60
  props: {
@@ -1,4 +1,4 @@
1
- import { box, onMountEffect, attachRef } from "svelte-toolbelt";
1
+ import { box, onMountEffect, attachRef, DOMContext } 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";
@@ -135,9 +135,11 @@ class TooltipTriggerState {
135
135
  #isPointerDown = box(false);
136
136
  #hasPointerMoveOpened = $state(false);
137
137
  #isDisabled = $derived.by(() => this.opts.disabled.current || this.root.disabled);
138
+ domContext;
138
139
  constructor(opts, root) {
139
140
  this.opts = opts;
140
141
  this.root = root;
142
+ this.domContext = new DOMContext(opts.ref);
141
143
  }
142
144
  handlePointerUp = () => {
143
145
  this.#isPointerDown.current = false;
@@ -151,7 +153,7 @@ class TooltipTriggerState {
151
153
  if (this.#isDisabled)
152
154
  return;
153
155
  this.#isPointerDown.current = true;
154
- document.addEventListener("pointerup", () => {
156
+ this.domContext.getDocument().addEventListener("pointerup", () => {
155
157
  this.handlePointerUp();
156
158
  }, { once: true });
157
159
  };
@@ -1,5 +1,5 @@
1
1
  import { arrow, autoUpdate, flip, hide, limitShift, offset, shift, size, } from "@floating-ui/dom";
2
- import { attachRef, box, cssToStyleObj, styleToString } from "svelte-toolbelt";
2
+ import { attachRef, box, cssToStyleObj, getWindow, styleToString, } from "svelte-toolbelt";
3
3
  import { Context, ElementSize, watch } from "runed";
4
4
  import { isNotNull } from "../../../internal/is.js";
5
5
  import { useId } from "../../../internal/use-id.js";
@@ -195,7 +195,8 @@ class FloatingContentState {
195
195
  watch(() => this.contentRef.current, (contentNode) => {
196
196
  if (!contentNode)
197
197
  return;
198
- this.contentZIndex = window.getComputedStyle(contentNode).zIndex;
198
+ const win = getWindow(contentNode);
199
+ this.contentZIndex = win.getComputedStyle(contentNode).zIndex;
199
200
  });
200
201
  $effect(() => {
201
202
  this.floating.floating.current = this.wrapperRef.current;
@@ -1,4 +1,4 @@
1
- import { afterSleep, afterTick, executeCallbacks } from "svelte-toolbelt";
1
+ import { afterSleep, afterTick, DOMContext, executeCallbacks } from "svelte-toolbelt";
2
2
  import { Context, watch } from "runed";
3
3
  import { on } from "svelte/events";
4
4
  import { createFocusScopeAPI, createFocusScopeStack, removeLinks, } from "./focus-scope-stack.svelte.js";
@@ -21,6 +21,7 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
21
21
  const focusScope = createFocusScopeAPI();
22
22
  const ctx = FocusScopeContext.getOr({ ignoreCloseAutoFocus: false });
23
23
  let lastFocusedElement = null;
24
+ const domContext = new DOMContext(ref);
24
25
  function manageFocus(event) {
25
26
  if (focusScope.paused || !ref.current || focusScope.isHandlingFocus)
26
27
  return;
@@ -89,14 +90,16 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
89
90
  * If the element was removed and focus is now outside the container,
90
91
  * (e.g., browser moved it to body), refocus the container.
91
92
  */
92
- if (elementWasRemoved && ref.current && !ref.current.contains(document.activeElement)) {
93
+ if (elementWasRemoved &&
94
+ ref.current &&
95
+ !ref.current.contains(domContext.getActiveElement())) {
93
96
  focus(ref.current);
94
97
  }
95
98
  }
96
99
  watch([() => ref.current, () => enabled.current], ([container, enabled]) => {
97
100
  if (!container || !enabled)
98
101
  return;
99
- const removeEvents = executeCallbacks(on(document, "focusin", manageFocus), on(document, "focusout", manageFocus));
102
+ const removeEvents = executeCallbacks(on(domContext.getDocument(), "focusin", manageFocus), on(domContext.getDocument(), "focusout", manageFocus));
100
103
  const mutationObserver = new MutationObserver(handleMutations);
101
104
  mutationObserver.observe(container, {
102
105
  childList: true,
@@ -111,7 +114,7 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
111
114
  watch([() => forceMount.current, () => ref.current], ([forceMount, container]) => {
112
115
  if (forceMount)
113
116
  return;
114
- const prevFocusedElement = document.activeElement;
117
+ const prevFocusedElement = domContext.getActiveElement();
115
118
  handleOpen(container, prevFocusedElement);
116
119
  return () => {
117
120
  if (!container)
@@ -122,7 +125,7 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
122
125
  watch([() => forceMount.current, () => ref.current, () => enabled.current], ([forceMount, container]) => {
123
126
  if (!forceMount)
124
127
  return;
125
- const prevFocusedElement = document.activeElement;
128
+ const prevFocusedElement = domContext.getActiveElement();
126
129
  handleOpen(container, prevFocusedElement);
127
130
  return () => {
128
131
  if (!container)
@@ -132,7 +135,7 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
132
135
  });
133
136
  function handleOpen(container, prevFocusedElement) {
134
137
  if (!container)
135
- container = document.getElementById(id.current);
138
+ container = domContext.getElementById(id.current);
136
139
  if (!container || !enabled.current)
137
140
  return;
138
141
  focusScopeStack.add(focusScope);
@@ -146,7 +149,7 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
146
149
  return;
147
150
  const result = focusFirst(removeLinks(getTabbableCandidates(container)), {
148
151
  select: true,
149
- });
152
+ }, () => domContext.getActiveElement());
150
153
  if (!result)
151
154
  focus(container);
152
155
  });
@@ -159,7 +162,9 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
159
162
  const shouldIgnore = ctx.ignoreCloseAutoFocus;
160
163
  afterSleep(0, () => {
161
164
  if (!destroyEvent.defaultPrevented && prevFocusedElement && !shouldIgnore) {
162
- focus(isTabbable(prevFocusedElement) ? prevFocusedElement : document.body, {
165
+ focus(isTabbable(prevFocusedElement)
166
+ ? prevFocusedElement
167
+ : domContext.getDocument().body, {
163
168
  select: true,
164
169
  });
165
170
  }
@@ -174,7 +179,7 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
174
179
  if (focusScope.paused)
175
180
  return;
176
181
  const isTabKey = e.key === kbd.TAB && !e.ctrlKey && !e.altKey && !e.metaKey;
177
- const focusedElement = document.activeElement;
182
+ const focusedElement = domContext.getActiveElement();
178
183
  if (!(isTabKey && focusedElement))
179
184
  return;
180
185
  const container = ref.current;
@@ -3,7 +3,7 @@ export type PortalProps = {
3
3
  /**
4
4
  * Where to portal the content to.
5
5
  *
6
- * @defaultValue document.body
6
+ * @default document.body
7
7
  */
8
8
  to?: HTMLElement | string | DocumentFragment;
9
9
  /**
@@ -1,3 +1,4 @@
1
+ import { DOMContext } from "svelte-toolbelt";
1
2
  import type { TextSelectionLayerImplProps } from "./types.js";
2
3
  import type { ReadableBoxedValues } from "../../../internal/box.svelte.js";
3
4
  type TextSelectionLayerStateProps = ReadableBoxedValues<Required<Omit<TextSelectionLayerImplProps, "children" | "preventOverflowTextSelection" | "ref">> & {
@@ -6,6 +7,7 @@ type TextSelectionLayerStateProps = ReadableBoxedValues<Required<Omit<TextSelect
6
7
  export declare class TextSelectionLayerState {
7
8
  #private;
8
9
  readonly opts: TextSelectionLayerStateProps;
10
+ readonly domContext: DOMContext;
9
11
  constructor(opts: TextSelectionLayerStateProps);
10
12
  }
11
13
  export declare function useTextSelectionLayer(props: TextSelectionLayerStateProps): TextSelectionLayerState;
@@ -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(document, "pointerdown", this.#pointerdown), on(document, "pointerup", composeHandlers(this.#resetSelectionLock, this.opts.onPointerUp.current)));
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) || !isOrContainsTarget(node, target))
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, afterMs?: number, onChange?: (value: T) => void): WritableBox<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, afterMs = 10000, onChange = noop) {
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 window.setTimeout(() => {
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 = document.querySelector("[data-bits-announcer]");
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
- function createLog(kind) {
25
- const log = document.createElement("div");
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
- function getLog(kind) {
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 = document.createElement("div");
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 div = document.createElement("div");
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 = document.createElement("div");
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 = document.getElementById(accessibleHeadingId);
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 nodeToFocus = document.querySelector("[data-bits-day][data-focused]");
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(id: string, formatter: Formatter, value: DateValue): void;
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(id, formatter, value) {
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 = document.getElementById(id);
353
+ const el = doc.getElementById(id);
353
354
  if (!el) {
354
- const div = document.createElement("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
- document.body.appendChild(div);
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 = document.getElementById(id);
375
+ const el = doc.getElementById(id);
375
376
  if (!el)
376
377
  return;
377
- document.body.removeChild(el);
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(id: string, formatter: TimeFormatter, value: TimeValue): void;
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;