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
@@ -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(id, formatter, value) {
248
+ export function setTimeDescription(props) {
249
249
  if (!isBrowser)
250
250
  return;
251
- const valueString = formatter.selectedTime(value);
252
- const el = document.getElementById(id);
251
+ const valueString = props.formatter.selectedTime(props.value);
252
+ const el = props.doc.getElementById(props.id);
253
253
  if (!el) {
254
- const div = document.createElement("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
- document.body.appendChild(div);
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 = document.getElementById(id);
274
+ const el = doc.getElementById(id);
275
275
  if (!el)
276
276
  return;
277
- document.body.removeChild(el);
277
+ doc.body.removeChild(el);
278
278
  }
279
279
  export function convertTimeValueToDateValue(time) {
280
280
  if (time instanceof Time) {
@@ -1,4 +1,3 @@
1
- export declare function getDocument(element?: Element | null): Document;
2
1
  export declare function activeElement(doc: Document): Element | null;
3
2
  export declare function getFirstNonCommentChild(element: HTMLElement | null): ChildNode | null;
4
3
  /**
@@ -1,6 +1,3 @@
1
- export function getDocument(element) {
2
- return element?.ownerDocument ?? document;
3
- }
4
1
  export function activeElement(doc) {
5
2
  let activeElement = doc.activeElement;
6
3
  while (activeElement?.shadowRoot?.activeElement != null) {
@@ -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`.
@@ -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: window.pageXOffset || document.documentElement.scrollLeft,
25
- y: window.pageYOffset || document.documentElement.scrollTop,
27
+ x: win.pageXOffset || doc.documentElement.scrollLeft,
28
+ y: win.pageYOffset || doc.documentElement.scrollTop,
26
29
  };
27
30
  element.focus();
28
- window.scrollTo(scrollPosition.x, scrollPosition.y);
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
- if (document.activeElement === element)
39
+ const doc = getDocument(element);
40
+ if (doc.activeElement === element)
37
41
  return;
38
- const previouslyFocusedElement = document.activeElement;
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 = document.activeElement;
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 (document.activeElement !== previouslyFocusedElement)
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 walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
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";
@@ -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;
@@ -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, getDocument } from "./dom.js";
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 allTabbable = tabbable(getDocument(currentNode).body, getTabbableOptions());
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 document.body;
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 document.body;
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(getDocument(currentNode).body, getTabbableOptions());
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 document.body;
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 (nextFocusableElements.find((node) => isTabbable(node, getTabbableOptions())) ??
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("", 1000);
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?: UseDOMTypeaheadOpts): {
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("", 1000);
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 ?? (() => document.activeElement);
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, opts.transitTimeout ?? 300, (value) => {
9
- if (enabled) {
10
- opts.setIsPointerInTransit?.(value);
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",
@@ -46,7 +46,7 @@
46
46
  "css.escape": "^1.5.1",
47
47
  "esm-env": "^1.1.2",
48
48
  "runed": "^0.28.0",
49
- "svelte-toolbelt": "^0.8.2",
49
+ "svelte-toolbelt": "^0.9.1",
50
50
  "tabbable": "^6.2.0"
51
51
  },
52
52
  "peerDependencies": {
@@ -1,9 +0,0 @@
1
- import type { Box } from "svelte-toolbelt";
2
- export declare class DOMContext {
3
- readonly element: Box<HTMLElement | null>;
4
- readonly root: Document | ShadowRoot | null;
5
- constructor(element: Box<HTMLElement | null>);
6
- querySelector: (selector: string) => Element | null;
7
- querySelectorAll: (selector: string) => NodeListOf<Element>;
8
- getElementById: (id: string) => HTMLElement | null;
9
- }
@@ -1,26 +0,0 @@
1
- export class DOMContext {
2
- element;
3
- root = $derived.by(() => {
4
- if (!this.element.current)
5
- return null;
6
- return this.element.current.getRootNode();
7
- });
8
- constructor(element) {
9
- this.element = element;
10
- }
11
- querySelector = (selector) => {
12
- if (!this.root)
13
- return null;
14
- return this.root.querySelector(selector);
15
- };
16
- querySelectorAll = (selector) => {
17
- if (!this.root)
18
- return [];
19
- return this.root.querySelectorAll(selector);
20
- };
21
- getElementById = (id) => {
22
- if (!this.root)
23
- return null;
24
- return this.root.getElementById(id);
25
- };
26
- }
@@ -1,7 +0,0 @@
1
- import { type WritableBox } from "svelte-toolbelt";
2
- export declare function useSize(node: WritableBox<HTMLElement | null>): {
3
- readonly value: {
4
- width: number;
5
- height: number;
6
- } | undefined;
7
- };
@@ -1,54 +0,0 @@
1
- /// <reference types="resize-observer-browser" />
2
- import { untrack } from "svelte";
3
- import { afterTick } from "svelte-toolbelt";
4
- export function useSize(node) {
5
- let size = $state(undefined);
6
- $effect(() => {
7
- const currNode = node.current;
8
- if (!currNode) {
9
- size = undefined;
10
- return;
11
- }
12
- afterTick(() => {
13
- if (!currNode)
14
- return;
15
- size = {
16
- width: currNode.offsetWidth,
17
- height: currNode.offsetHeight,
18
- };
19
- });
20
- const resizeObserver = new ResizeObserver((entries) => {
21
- if (!Array.isArray(entries) || !entries.length)
22
- return;
23
- const entry = entries[0];
24
- if (!entry)
25
- return;
26
- let width;
27
- let height;
28
- if ("borderBoxSize" in entry) {
29
- const borderSizeEntry = entry.borderBoxSize;
30
- const borderSize = Array.isArray(borderSizeEntry)
31
- ? borderSizeEntry[0]
32
- : borderSizeEntry;
33
- width = borderSize.inlineSize;
34
- height = borderSize.blockSize;
35
- }
36
- else {
37
- width = currNode.offsetWidth;
38
- height = currNode.offsetHeight;
39
- }
40
- untrack(() => (size = { width, height }));
41
- });
42
- resizeObserver.observe(currNode, { box: "border-box" });
43
- return () => {
44
- if (!currNode)
45
- return;
46
- resizeObserver.unobserve(currNode);
47
- };
48
- });
49
- return {
50
- get value() {
51
- return size;
52
- },
53
- };
54
- }