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.
Files changed (79) 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/slider/helpers.js +33 -2
  30. package/dist/bits/time-field/time-field.svelte.d.ts +3 -1
  31. package/dist/bits/time-field/time-field.svelte.js +15 -6
  32. package/dist/bits/time-range-field/time-range-field.svelte.d.ts +2 -0
  33. package/dist/bits/time-range-field/time-range-field.svelte.js +4 -2
  34. package/dist/bits/tooltip/components/tooltip-content-static.svelte +2 -0
  35. package/dist/bits/tooltip/components/tooltip-content.svelte +2 -0
  36. package/dist/bits/tooltip/components/tooltip-trigger.svelte +1 -1
  37. package/dist/bits/tooltip/components/tooltip.svelte +1 -1
  38. package/dist/bits/tooltip/tooltip.svelte.d.ts +18 -18
  39. package/dist/bits/tooltip/tooltip.svelte.js +7 -3
  40. package/dist/bits/utilities/floating-layer/components/floating-layer-anchor.svelte +9 -6
  41. package/dist/bits/utilities/floating-layer/components/floating-layer-content.svelte +25 -21
  42. package/dist/bits/utilities/floating-layer/components/floating-layer.svelte +2 -2
  43. package/dist/bits/utilities/floating-layer/components/floating-layer.svelte.d.ts +1 -0
  44. package/dist/bits/utilities/floating-layer/types.d.ts +18 -0
  45. package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.d.ts +3 -3
  46. package/dist/bits/utilities/floating-layer/use-floating-layer.svelte.js +16 -11
  47. package/dist/bits/utilities/focus-scope/use-focus-scope.svelte.js +14 -9
  48. package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte +2 -0
  49. package/dist/bits/utilities/popper-layer/types.d.ts +9 -0
  50. package/dist/bits/utilities/portal/types.d.ts +1 -1
  51. package/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.d.ts +2 -0
  52. package/dist/bits/utilities/text-selection-layer/use-text-selection-layer.svelte.js +7 -7
  53. package/dist/internal/box-auto-reset.svelte.d.ts +7 -1
  54. package/dist/internal/box-auto-reset.svelte.js +11 -6
  55. package/dist/internal/date-time/announcer.d.ts +1 -1
  56. package/dist/internal/date-time/announcer.js +20 -20
  57. package/dist/internal/date-time/calendar-helpers.svelte.js +7 -5
  58. package/dist/internal/date-time/field/helpers.d.ts +8 -2
  59. package/dist/internal/date-time/field/helpers.js +8 -7
  60. package/dist/internal/date-time/field/time-helpers.d.ts +8 -2
  61. package/dist/internal/date-time/field/time-helpers.js +9 -9
  62. package/dist/internal/dom.d.ts +0 -1
  63. package/dist/internal/dom.js +0 -3
  64. package/dist/internal/focus.d.ts +2 -2
  65. package/dist/internal/focus.js +14 -9
  66. package/dist/internal/math.d.ts +0 -4
  67. package/dist/internal/math.js +0 -28
  68. package/dist/internal/tabbable.d.ts +0 -2
  69. package/dist/internal/tabbable.js +10 -14
  70. package/dist/internal/use-data-typeahead.svelte.d.ts +1 -0
  71. package/dist/internal/use-data-typeahead.svelte.js +4 -1
  72. package/dist/internal/use-dom-typeahead.svelte.d.ts +3 -1
  73. package/dist/internal/use-dom-typeahead.svelte.js +5 -2
  74. package/dist/internal/use-grace-area.svelte.js +9 -5
  75. package/package.json +2 -2
  76. package/dist/internal/dom-context.svelte.d.ts +0 -9
  77. package/dist/internal/dom-context.svelte.js +0 -26
  78. package/dist/internal/use-size.svelte.d.ts +0 -7
  79. package/dist/internal/use-size.svelte.js +0 -54
@@ -1,6 +1,6 @@
1
1
  import type { Updater } from "svelte/store";
2
2
  import { Time } from "@internationalized/date";
3
- import { type WritableBox } from "svelte-toolbelt";
3
+ import { type WritableBox, DOMContext } from "svelte-toolbelt";
4
4
  import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
5
5
  import type { BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, WithRefProps } from "../../internal/types.js";
6
6
  import type { TimeSegmentObj, SegmentPart, HourCycle, TimeValidator, TimeOnInvalid, EditableTimeSegmentPart } from "../../shared/date/types.js";
@@ -70,6 +70,7 @@ export declare class TimeFieldRootState<T extends TimeValue = Time> {
70
70
  valueTime: Time | undefined;
71
71
  hourCycle: HourCycle;
72
72
  rangeRoot: TimeRangeFieldRootState<T> | undefined;
73
+ domContext: DOMContext;
73
74
  constructor(props: TimeFieldRootStateProps<T>, rangeRoot?: TimeRangeFieldRootState<T>);
74
75
  setName(name: string): void;
75
76
  setFieldNode(node: HTMLElement | null): void;
@@ -149,6 +150,7 @@ export declare class TimeFieldInputState {
149
150
  #private;
150
151
  readonly opts: TimeFieldInputStateProps;
151
152
  readonly root: TimeFieldRootState;
153
+ domContext: DOMContext;
152
154
  constructor(opts: TimeFieldInputStateProps, root: TimeFieldRootState);
153
155
  props: {
154
156
  readonly id: string;
@@ -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)
@@ -46,6 +46,7 @@
46
46
  preventScroll={false}
47
47
  forceMount={true}
48
48
  ref={contentState.opts.ref}
49
+ tooltip={true}
49
50
  >
50
51
  {#snippet popper({ props })}
51
52
  {@const mergedProps = mergeProps(props, {
@@ -64,6 +65,7 @@
64
65
  <PopperLayer
65
66
  {...mergedProps}
66
67
  {...contentState.popperProps}
68
+ tooltip={true}
67
69
  isStatic
68
70
  present={contentState.root.opts.open.current}
69
71
  {id}
@@ -64,6 +64,7 @@
64
64
  preventScroll={false}
65
65
  forceMount={true}
66
66
  ref={contentState.opts.ref}
67
+ tooltip={true}
67
68
  >
68
69
  {#snippet popper({ props, wrapperProps })}
69
70
  {@const mergedProps = mergeProps(props, {
@@ -91,6 +92,7 @@
91
92
  preventScroll={false}
92
93
  forceMount={false}
93
94
  ref={contentState.opts.ref}
95
+ tooltip={true}
94
96
  >
95
97
  {#snippet popper({ props, wrapperProps })}
96
98
  {@const mergedProps = mergeProps(props, {
@@ -29,7 +29,7 @@
29
29
  const mergedProps = $derived(mergeProps(restProps, triggerState.props, { type }));
30
30
  </script>
31
31
 
32
- <FloatingLayerAnchor {id} ref={triggerState.opts.ref}>
32
+ <FloatingLayerAnchor {id} ref={triggerState.opts.ref} tooltip={true}>
33
33
  {#if child}
34
34
  {@render child({ props: mergedProps })}
35
35
  {:else}
@@ -32,6 +32,6 @@
32
32
  });
33
33
  </script>
34
34
 
35
- <FloatingLayer>
35
+ <FloatingLayer tooltip>
36
36
  {@render children?.()}
37
37
  </FloatingLayer>
@@ -1,6 +1,7 @@
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
- import type { PointerEventHandler } from "svelte/elements";
4
+ import type { FocusEventHandler, MouseEventHandler, PointerEventHandler } from "svelte/elements";
4
5
  type TooltipProviderStateProps = ReadableBoxedValues<{
5
6
  delayDuration: number;
6
7
  disableHoverableContent: boolean;
@@ -53,26 +54,25 @@ 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: {
59
- id: string;
60
- "aria-describedby": string | undefined;
61
- "data-state": string;
62
- "data-disabled": "" | undefined;
63
- "data-delay-duration": string;
64
- "data-tooltip-trigger": string;
65
- tabindex: number | undefined;
66
- disabled: boolean;
67
- onpointerup: () => void;
68
- onpointerdown: () => void;
69
- onpointermove: PointerEventHandler<HTMLElement>;
70
- onpointerleave: () => void;
71
- onfocus: (e: FocusEvent & {
72
- currentTarget: HTMLElement;
73
- }) => void;
74
- onblur: () => void;
75
- onclick: () => void;
61
+ readonly id: string;
62
+ readonly "aria-describedby": string | undefined;
63
+ readonly "data-state": string;
64
+ readonly "data-disabled": "" | undefined;
65
+ readonly "data-delay-duration": `${number}`;
66
+ readonly "data-tooltip-trigger": "";
67
+ readonly tabindex: 0 | undefined;
68
+ readonly disabled: boolean;
69
+ readonly onpointerup: PointerEventHandler<HTMLElement>;
70
+ readonly onpointerdown: PointerEventHandler<HTMLElement>;
71
+ readonly onpointermove: PointerEventHandler<HTMLElement>;
72
+ readonly onpointerleave: PointerEventHandler<HTMLElement>;
73
+ readonly onfocus: FocusEventHandler<HTMLElement>;
74
+ readonly onblur: FocusEventHandler<HTMLElement>;
75
+ readonly onclick: MouseEventHandler<HTMLElement>;
76
76
  };
77
77
  }
78
78
  type TooltipContentStateProps = WithRefProps & ReadableBoxedValues<{
@@ -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
  };
@@ -192,7 +194,9 @@ class TooltipTriggerState {
192
194
  };
193
195
  props = $derived.by(() => ({
194
196
  id: this.opts.id.current,
195
- "aria-describedby": this.root.opts.open.current ? this.root.contentNode?.id : undefined,
197
+ "aria-describedby": this.root.opts.open.current
198
+ ? this.root.contentNode?.id
199
+ : undefined,
196
200
  "data-state": this.root.stateAttr,
197
201
  "data-disabled": getDataDisabled(this.#isDisabled),
198
202
  "data-delay-duration": `${this.root.delayDuration}`,
@@ -4,13 +4,16 @@
4
4
  import type { AnchorProps } from "./index.js";
5
5
  import type { Measurable } from "../../../../internal/floating-svelte/types.js";
6
6
 
7
- let { id, children, virtualEl, ref }: AnchorProps = $props();
7
+ let { id, children, virtualEl, ref, tooltip = false }: AnchorProps = $props();
8
8
 
9
- useFloatingAnchorState({
10
- id: box.with(() => id),
11
- virtualEl: box.with(() => virtualEl as unknown as Measurable | null),
12
- ref,
13
- });
9
+ useFloatingAnchorState(
10
+ {
11
+ id: box.with(() => id),
12
+ virtualEl: box.with(() => virtualEl as unknown as Measurable | null),
13
+ ref,
14
+ },
15
+ tooltip
16
+ );
14
17
  </script>
15
18
 
16
19
  {@render children?.()}
@@ -25,29 +25,33 @@
25
25
  wrapperId = useId(),
26
26
  customAnchor = null,
27
27
  enabled,
28
+ tooltip = false,
28
29
  }: ContentImplProps = $props();
29
30
 
30
- const contentState = useFloatingContentState({
31
- side: box.with(() => side),
32
- sideOffset: box.with(() => sideOffset),
33
- align: box.with(() => align),
34
- alignOffset: box.with(() => alignOffset),
35
- id: box.with(() => id),
36
- arrowPadding: box.with(() => arrowPadding),
37
- avoidCollisions: box.with(() => avoidCollisions),
38
- collisionBoundary: box.with(() => collisionBoundary),
39
- collisionPadding: box.with(() => collisionPadding),
40
- hideWhenDetached: box.with(() => hideWhenDetached),
41
- onPlaced: box.with(() => onPlaced),
42
- sticky: box.with(() => sticky),
43
- updatePositionStrategy: box.with(() => updatePositionStrategy),
44
- strategy: box.with(() => strategy),
45
- dir: box.with(() => dir),
46
- style: box.with(() => style),
47
- enabled: box.with(() => enabled),
48
- wrapperId: box.with(() => wrapperId),
49
- customAnchor: box.with(() => customAnchor),
50
- });
31
+ const contentState = useFloatingContentState(
32
+ {
33
+ side: box.with(() => side),
34
+ sideOffset: box.with(() => sideOffset),
35
+ align: box.with(() => align),
36
+ alignOffset: box.with(() => alignOffset),
37
+ id: box.with(() => id),
38
+ arrowPadding: box.with(() => arrowPadding),
39
+ avoidCollisions: box.with(() => avoidCollisions),
40
+ collisionBoundary: box.with(() => collisionBoundary),
41
+ collisionPadding: box.with(() => collisionPadding),
42
+ hideWhenDetached: box.with(() => hideWhenDetached),
43
+ onPlaced: box.with(() => onPlaced),
44
+ sticky: box.with(() => sticky),
45
+ updatePositionStrategy: box.with(() => updatePositionStrategy),
46
+ strategy: box.with(() => strategy),
47
+ dir: box.with(() => dir),
48
+ style: box.with(() => style),
49
+ enabled: box.with(() => enabled),
50
+ wrapperId: box.with(() => wrapperId),
51
+ customAnchor: box.with(() => customAnchor),
52
+ },
53
+ tooltip
54
+ );
51
55
 
52
56
  const mergedProps = $derived(
53
57
  mergeProps(contentState.wrapperProps, {
@@ -2,9 +2,9 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import { useFloatingRootState } from "../use-floating-layer.svelte.js";
4
4
 
5
- let { children }: { children?: Snippet } = $props();
5
+ let { children, tooltip = false }: { children?: Snippet; tooltip?: boolean } = $props();
6
6
 
7
- useFloatingRootState();
7
+ useFloatingRootState(tooltip);
8
8
  </script>
9
9
 
10
10
  {@render children?.()}
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from "svelte";
2
2
  type $$ComponentProps = {
3
3
  children?: Snippet;
4
+ tooltip?: boolean;
4
5
  };
5
6
  declare const FloatingLayer: import("svelte").Component<$$ComponentProps, {}, "">;
6
7
  type FloatingLayer = ReturnType<typeof FloatingLayer>;
@@ -107,10 +107,28 @@ export type FloatingLayerContentImplProps = {
107
107
  */
108
108
  onPlaced?: () => void;
109
109
  enabled: boolean;
110
+ /**
111
+ * Tooltips are special in that they are commonly composed
112
+ * with other floating components, where the same trigger is
113
+ * used for both the tooltip and the popover.
114
+ *
115
+ * For situations like this, we need to use a different context
116
+ * symbol so that conflicts don't occur.
117
+ */
118
+ tooltip?: boolean;
110
119
  } & FloatingLayerContentProps;
111
120
  export type FloatingLayerAnchorProps = {
112
121
  id: string;
113
122
  children?: Snippet;
114
123
  virtualEl?: ReadableBox<Measurable | null>;
115
124
  ref: ReadableBox<HTMLElement | null>;
125
+ /**
126
+ * Tooltips are special in that they are commonly composed
127
+ * with other floating components, where the same trigger is
128
+ * used for both the tooltip and the popover.
129
+ *
130
+ * For situations like this, we need to use a different context
131
+ * symbol so that conflicts don't occur.
132
+ */
133
+ tooltip?: boolean;
116
134
  };
@@ -942,10 +942,10 @@ declare class FloatingAnchorState {
942
942
  readonly root: FloatingRootState;
943
943
  constructor(opts: FloatingAnchorStateProps, root: FloatingRootState);
944
944
  }
945
- export declare function useFloatingRootState(): FloatingRootState;
946
- export declare function useFloatingContentState(props: FloatingContentStateProps): FloatingContentState;
945
+ export declare function useFloatingRootState(tooltip?: boolean): FloatingRootState;
946
+ export declare function useFloatingContentState(props: FloatingContentStateProps, tooltip?: boolean): FloatingContentState;
947
947
  export declare function useFloatingArrowState(props: FloatingArrowStateProps): FloatingArrowState;
948
- export declare function useFloatingAnchorState(props: FloatingAnchorStateProps): FloatingAnchorState;
948
+ export declare function useFloatingAnchorState(props: FloatingAnchorStateProps, tooltip?: boolean): FloatingAnchorState;
949
949
  export declare function getSideFromPlacement(placement: Placement): "left" | "right" | "top" | "bottom";
950
950
  export declare function getAlignFromPlacement(placement: Placement): "end" | "center" | "start";
951
951
  export {};
@@ -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";
@@ -140,9 +140,6 @@ class FloatingContentState {
140
140
  "data-align": this.placedAlign,
141
141
  style: styleToString({
142
142
  ...this.#transformedStyle,
143
- // if the FloatingContent hasn't been placed yet (not all measurements done)
144
- // we prevent animations so that users's animation don't kick in too early referring wrong sides
145
- // animation: !this.floating.isPositioned ? "none" : undefined,
146
143
  }),
147
144
  ...attachRef(this.contentRef),
148
145
  }));
@@ -195,7 +192,8 @@ class FloatingContentState {
195
192
  watch(() => this.contentRef.current, (contentNode) => {
196
193
  if (!contentNode)
197
194
  return;
198
- this.contentZIndex = window.getComputedStyle(contentNode).zIndex;
195
+ const win = getWindow(contentNode);
196
+ this.contentZIndex = win.getComputedStyle(contentNode).zIndex;
199
197
  });
200
198
  $effect(() => {
201
199
  this.floating.floating.current = this.wrapperRef.current;
@@ -232,17 +230,24 @@ class FloatingAnchorState {
232
230
  }
233
231
  const FloatingRootContext = new Context("Floating.Root");
234
232
  const FloatingContentContext = new Context("Floating.Content");
235
- export function useFloatingRootState() {
236
- return FloatingRootContext.set(new FloatingRootState());
233
+ const FloatingTooltipRootContext = new Context("Floating.Root");
234
+ export function useFloatingRootState(tooltip = false) {
235
+ return tooltip
236
+ ? FloatingTooltipRootContext.set(new FloatingRootState())
237
+ : FloatingRootContext.set(new FloatingRootState());
237
238
  }
238
- export function useFloatingContentState(props) {
239
- return FloatingContentContext.set(new FloatingContentState(props, FloatingRootContext.get()));
239
+ export function useFloatingContentState(props, tooltip = false) {
240
+ return tooltip
241
+ ? FloatingContentContext.set(new FloatingContentState(props, FloatingTooltipRootContext.get()))
242
+ : FloatingContentContext.set(new FloatingContentState(props, FloatingRootContext.get()));
240
243
  }
241
244
  export function useFloatingArrowState(props) {
242
245
  return new FloatingArrowState(props, FloatingContentContext.get());
243
246
  }
244
- export function useFloatingAnchorState(props) {
245
- return new FloatingAnchorState(props, FloatingRootContext.get());
247
+ export function useFloatingAnchorState(props, tooltip = false) {
248
+ return tooltip
249
+ ? new FloatingAnchorState(props, FloatingTooltipRootContext.get())
250
+ : new FloatingAnchorState(props, FloatingRootContext.get());
246
251
  }
247
252
  //
248
253
  // HELPERS
@@ -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;
@@ -45,6 +45,7 @@
45
45
  isStatic = false,
46
46
  enabled,
47
47
  ref,
48
+ tooltip = false,
48
49
  ...restProps
49
50
  }: Omit<PopperLayerImplProps, "present" | "children"> & {
50
51
  enabled: boolean;
@@ -72,6 +73,7 @@
72
73
  {onPlaced}
73
74
  {customAnchor}
74
75
  {enabled}
76
+ {tooltip}
75
77
  >
76
78
  {#snippet content({ props: floatingProps, wrapperProps })}
77
79
  {#if restProps.forceMount && enabled}
@@ -22,4 +22,13 @@ export type PopperLayerImplProps = Omit<EscapeLayerImplProps & DismissibleLayerI
22
22
  }
23
23
  ]>;
24
24
  isStatic?: boolean;
25
+ /**
26
+ * Tooltips are special in that they are commonly composed
27
+ * with other floating components, where the same trigger is
28
+ * used for both the tooltip and the popover.
29
+ *
30
+ * For situations like this, we need to use a different context
31
+ * symbol so that conflicts don't occur.
32
+ */
33
+ tooltip?: boolean;
25
34
  }, "enabled">;
@@ -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;