bits-ui 2.13.1 → 2.14.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 (57) hide show
  1. package/dist/bits/accordion/accordion.svelte.d.ts +5 -1
  2. package/dist/bits/accordion/accordion.svelte.js +21 -2
  3. package/dist/bits/accordion/components/accordion-content.svelte +12 -24
  4. package/dist/bits/alert-dialog/components/alert-dialog-content.svelte +50 -62
  5. package/dist/bits/collapsible/collapsible.svelte.d.ts +4 -1
  6. package/dist/bits/collapsible/collapsible.svelte.js +15 -2
  7. package/dist/bits/collapsible/components/collapsible-content.svelte +12 -24
  8. package/dist/bits/context-menu/components/context-menu-content-static.svelte +2 -0
  9. package/dist/bits/context-menu/components/context-menu-content.svelte +2 -0
  10. package/dist/bits/date-field/date-field.svelte.js +5 -3
  11. package/dist/bits/dialog/components/dialog-content.svelte +44 -57
  12. package/dist/bits/dialog/components/dialog-overlay.svelte +9 -12
  13. package/dist/bits/dialog/dialog.svelte.d.ts +6 -0
  14. package/dist/bits/dialog/dialog.svelte.js +17 -3
  15. package/dist/bits/dropdown-menu/components/dropdown-menu-content-static.svelte +2 -0
  16. package/dist/bits/dropdown-menu/components/dropdown-menu-content.svelte +2 -0
  17. package/dist/bits/link-preview/components/link-preview-content-static.svelte +2 -0
  18. package/dist/bits/link-preview/components/link-preview-content.svelte +2 -0
  19. package/dist/bits/link-preview/link-preview.svelte.d.ts +3 -0
  20. package/dist/bits/link-preview/link-preview.svelte.js +6 -2
  21. package/dist/bits/menu/components/menu-content-static.svelte +2 -0
  22. package/dist/bits/menu/components/menu-content.svelte +2 -0
  23. package/dist/bits/menu/components/menu-sub-content-static.svelte +2 -0
  24. package/dist/bits/menu/components/menu-sub-content.svelte +2 -0
  25. package/dist/bits/menu/menu.svelte.d.ts +3 -0
  26. package/dist/bits/menu/menu.svelte.js +6 -2
  27. package/dist/bits/popover/components/popover-content-static.svelte +2 -0
  28. package/dist/bits/popover/components/popover-content.svelte +2 -0
  29. package/dist/bits/popover/components/popover-overlay.svelte +37 -0
  30. package/dist/bits/popover/components/popover-overlay.svelte.d.ts +4 -0
  31. package/dist/bits/popover/exports.d.ts +2 -1
  32. package/dist/bits/popover/exports.js +1 -0
  33. package/dist/bits/popover/popover.svelte.d.ts +25 -0
  34. package/dist/bits/popover/popover.svelte.js +39 -3
  35. package/dist/bits/popover/types.d.ts +6 -0
  36. package/dist/bits/select/components/select-content-static.svelte +2 -0
  37. package/dist/bits/select/components/select-content.svelte +2 -0
  38. package/dist/bits/select/select.svelte.d.ts +3 -0
  39. package/dist/bits/select/select.svelte.js +6 -2
  40. package/dist/bits/tooltip/components/tooltip-content-static.svelte +2 -0
  41. package/dist/bits/tooltip/components/tooltip-content.svelte +2 -0
  42. package/dist/bits/tooltip/tooltip.svelte.d.ts +3 -0
  43. package/dist/bits/tooltip/tooltip.svelte.js +6 -2
  44. package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte +1 -1
  45. package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte.d.ts +1 -1
  46. package/dist/bits/utilities/popper-layer/popper-layer.svelte +43 -45
  47. package/dist/bits/utilities/popper-layer/types.d.ts +4 -0
  48. package/dist/internal/animations-complete.js +7 -15
  49. package/dist/internal/date-time/field/helpers.js +3 -1
  50. package/dist/internal/date-time/field/time-helpers.js +4 -1
  51. package/dist/internal/presence-manager.svelte.d.ts +14 -0
  52. package/dist/internal/presence-manager.svelte.js +34 -0
  53. package/dist/internal/should-enable-focus-trap.d.ts +1 -2
  54. package/dist/internal/should-enable-focus-trap.js +2 -2
  55. package/package.json +2 -2
  56. package/dist/internal/open-change-complete.d.ts +0 -13
  57. package/dist/internal/open-change-complete.js +0 -24
@@ -1,5 +1,6 @@
1
1
  import { type ReadableBoxedValues, type WritableBoxedValues } from "svelte-toolbelt";
2
2
  import type { BitsKeyboardEvent, BitsMouseEvent, OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
3
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
3
4
  type DialogVariant = "alert-dialog" | "dialog";
4
5
  declare const dialogAttrs: import("../../internal/attrs.js").CreateBitsAttrsReturn<readonly ["content", "trigger", "overlay", "title", "description", "close", "cancel", "action"]>;
5
6
  interface DialogRootStateOpts extends WritableBoxedValues<{
@@ -14,6 +15,7 @@ export declare class DialogRootState {
14
15
  readonly opts: DialogRootStateOpts;
15
16
  triggerNode: HTMLElement | null;
16
17
  contentNode: HTMLElement | null;
18
+ overlayNode: HTMLElement | null;
17
19
  descriptionNode: HTMLElement | null;
18
20
  contentId: string | undefined;
19
21
  titleId: string | undefined;
@@ -23,6 +25,8 @@ export declare class DialogRootState {
23
25
  nestedOpenCount: number;
24
26
  readonly depth: number;
25
27
  readonly parent: DialogRootState | null;
28
+ contentPresence: PresenceManager;
29
+ overlayPresence: PresenceManager;
26
30
  constructor(opts: DialogRootStateOpts, parent: DialogRootState | null);
27
31
  handleOpen(): void;
28
32
  handleClose(): void;
@@ -149,6 +153,7 @@ export declare class DialogContentState {
149
153
  readonly "data-nested-open": "" | undefined;
150
154
  readonly "data-nested": "" | undefined;
151
155
  };
156
+ get shouldRender(): boolean;
152
157
  }
153
158
  interface DialogOverlayStateOpts extends WithRefOpts {
154
159
  }
@@ -172,6 +177,7 @@ export declare class DialogOverlayState {
172
177
  readonly "data-nested-open": "" | undefined;
173
178
  readonly "data-nested": "" | undefined;
174
179
  };
180
+ get shouldRender(): boolean;
175
181
  }
176
182
  interface AlertDialogCancelStateOpts extends WithRefOpts, ReadableBoxedValues<{
177
183
  disabled: boolean;
@@ -2,7 +2,7 @@ import { attachRef, boxWith, onDestroyEffect, } from "svelte-toolbelt";
2
2
  import { Context, watch } from "runed";
3
3
  import { createBitsAttrs, boolToStr, getDataOpenClosed, boolToEmptyStrOrUndef, } from "../../internal/attrs.js";
4
4
  import { kbd } from "../../internal/kbd.js";
5
- import { OpenChangeComplete } from "../../internal/open-change-complete.js";
5
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
6
6
  const dialogAttrs = createBitsAttrs({
7
7
  component: "dialog",
8
8
  parts: ["content", "trigger", "overlay", "title", "description", "close", "cancel", "action"],
@@ -16,6 +16,7 @@ export class DialogRootState {
16
16
  opts;
17
17
  triggerNode = $state(null);
18
18
  contentNode = $state(null);
19
+ overlayNode = $state(null);
19
20
  descriptionNode = $state(null);
20
21
  contentId = $state(undefined);
21
22
  titleId = $state(undefined);
@@ -25,13 +26,15 @@ export class DialogRootState {
25
26
  nestedOpenCount = $state(0);
26
27
  depth;
27
28
  parent;
29
+ contentPresence;
30
+ overlayPresence;
28
31
  constructor(opts, parent) {
29
32
  this.opts = opts;
30
33
  this.parent = parent;
31
34
  this.depth = parent ? parent.depth + 1 : 0;
32
35
  this.handleOpen = this.handleOpen.bind(this);
33
36
  this.handleClose = this.handleClose.bind(this);
34
- new OpenChangeComplete({
37
+ this.contentPresence = new PresenceManager({
35
38
  ref: boxWith(() => this.contentNode),
36
39
  open: this.opts.open,
37
40
  enabled: true,
@@ -39,6 +42,11 @@ export class DialogRootState {
39
42
  this.opts.onOpenChangeComplete.current(this.opts.open.current);
40
43
  },
41
44
  });
45
+ this.overlayPresence = new PresenceManager({
46
+ ref: boxWith(() => this.overlayNode),
47
+ open: this.opts.open,
48
+ enabled: true,
49
+ });
42
50
  watch(() => this.opts.open.current, (isOpen) => {
43
51
  if (!this.parent)
44
52
  return;
@@ -271,6 +279,9 @@ export class DialogContentState {
271
279
  ...this.root.sharedProps,
272
280
  ...this.attachment,
273
281
  }));
282
+ get shouldRender() {
283
+ return this.root.contentPresence.shouldRender;
284
+ }
274
285
  }
275
286
  export class DialogOverlayState {
276
287
  static create(opts) {
@@ -282,7 +293,7 @@ export class DialogOverlayState {
282
293
  constructor(opts, root) {
283
294
  this.opts = opts;
284
295
  this.root = root;
285
- this.attachment = attachRef(this.opts.ref);
296
+ this.attachment = attachRef(this.opts.ref, (v) => (this.root.overlayNode = v));
286
297
  }
287
298
  snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
288
299
  props = $derived.by(() => ({
@@ -298,6 +309,9 @@ export class DialogOverlayState {
298
309
  ...this.root.sharedProps,
299
310
  ...this.attachment,
300
311
  }));
312
+ get shouldRender() {
313
+ return this.root.overlayPresence.shouldRender;
314
+ }
301
315
  }
302
316
  export class AlertDialogCancelState {
303
317
  static create(opts) {
@@ -62,6 +62,7 @@
62
62
  forceMount={true}
63
63
  isStatic
64
64
  {id}
65
+ shouldRender={contentState.shouldRender}
65
66
  >
66
67
  {#snippet popper({ props })}
67
68
  {@const finalProps = mergeProps(props, {
@@ -89,6 +90,7 @@
89
90
  forceMount={false}
90
91
  isStatic
91
92
  {id}
93
+ shouldRender={contentState.shouldRender}
92
94
  >
93
95
  {#snippet popper({ props })}
94
96
  {@const finalProps = mergeProps(props, {
@@ -68,6 +68,7 @@
68
68
  {loop}
69
69
  forceMount={true}
70
70
  {id}
71
+ shouldRender={contentState.shouldRender}
71
72
  >
72
73
  {#snippet popper({ props, wrapperProps })}
73
74
  {@const finalProps = mergeProps(props, {
@@ -96,6 +97,7 @@
96
97
  {loop}
97
98
  forceMount={false}
98
99
  {id}
100
+ shouldRender={contentState.shouldRender}
99
101
  >
100
102
  {#snippet popper({ props, wrapperProps })}
101
103
  {@const finalProps = mergeProps(props, {
@@ -47,6 +47,7 @@
47
47
  loop={false}
48
48
  preventScroll={false}
49
49
  forceMount={true}
50
+ shouldRender={contentState.shouldRender}
50
51
  >
51
52
  {#snippet popper({ props })}
52
53
  {@const mergedProps = mergeProps(props, {
@@ -74,6 +75,7 @@
74
75
  loop={false}
75
76
  preventScroll={false}
76
77
  forceMount={false}
78
+ shouldRender={contentState.shouldRender}
77
79
  >
78
80
  {#snippet popper({ props })}
79
81
  {@const mergedProps = mergeProps(props, {
@@ -65,6 +65,7 @@
65
65
  loop={false}
66
66
  preventScroll={false}
67
67
  forceMount={true}
68
+ shouldRender={contentState.shouldRender}
68
69
  >
69
70
  {#snippet popper({ props, wrapperProps })}
70
71
  {@const mergedProps = mergeProps(props, {
@@ -93,6 +94,7 @@
93
94
  loop={false}
94
95
  preventScroll={false}
95
96
  forceMount={false}
97
+ shouldRender={contentState.shouldRender}
96
98
  >
97
99
  {#snippet popper({ props, wrapperProps })}
98
100
  {@const mergedProps = mergeProps(props, {
@@ -1,5 +1,6 @@
1
1
  import { DOMContext, type ReadableBoxedValues, type WritableBoxedValues } from "svelte-toolbelt";
2
2
  import type { BitsFocusEvent, BitsPointerEvent, OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
3
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
3
4
  interface LinkPreviewRootStateOpts extends WritableBoxedValues<{
4
5
  open: boolean;
5
6
  }>, ReadableBoxedValues<{
@@ -18,6 +19,7 @@ export declare class LinkPreviewRootState {
18
19
  timeout: number | null;
19
20
  contentNode: HTMLElement | null;
20
21
  contentMounted: boolean;
22
+ contentPresence: PresenceManager;
21
23
  triggerNode: HTMLElement | null;
22
24
  isOpening: boolean;
23
25
  domContext: DOMContext;
@@ -70,6 +72,7 @@ export declare class LinkPreviewContentState {
70
72
  onEscapeKeydown: (e: KeyboardEvent) => void;
71
73
  onOpenAutoFocus: (e: Event) => void;
72
74
  onCloseAutoFocus: (e: Event) => void;
75
+ get shouldRender(): boolean;
73
76
  readonly snippetProps: {
74
77
  open: boolean;
75
78
  };
@@ -5,7 +5,7 @@ import { createBitsAttrs, boolToStr, getDataOpenClosed } from "../../internal/at
5
5
  import { isElement, isFocusVisible, isTouch } from "../../internal/is.js";
6
6
  import { getTabbableCandidates } from "../../internal/focus.js";
7
7
  import { GraceArea } from "../../internal/grace-area.svelte.js";
8
- import { OpenChangeComplete } from "../../internal/open-change-complete.js";
8
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
9
9
  const linkPreviewAttrs = createBitsAttrs({
10
10
  component: "link-preview",
11
11
  parts: ["content", "trigger"],
@@ -22,12 +22,13 @@ export class LinkPreviewRootState {
22
22
  timeout = null;
23
23
  contentNode = $state(null);
24
24
  contentMounted = $state(false);
25
+ contentPresence;
25
26
  triggerNode = $state(null);
26
27
  isOpening = false;
27
28
  domContext = new DOMContext(() => null);
28
29
  constructor(opts) {
29
30
  this.opts = opts;
30
- new OpenChangeComplete({
31
+ this.contentPresence = new PresenceManager({
31
32
  ref: boxWith(() => this.contentNode),
32
33
  open: this.opts.open,
33
34
  onComplete: () => {
@@ -216,6 +217,9 @@ export class LinkPreviewContentState {
216
217
  onCloseAutoFocus = (e) => {
217
218
  e.preventDefault();
218
219
  };
220
+ get shouldRender() {
221
+ return this.root.contentPresence.shouldRender;
222
+ }
219
223
  snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
220
224
  props = $derived.by(() => ({
221
225
  id: this.opts.id.current,
@@ -65,6 +65,7 @@
65
65
  forceMount={true}
66
66
  isStatic
67
67
  {id}
68
+ shouldRender={contentState.shouldRender}
68
69
  >
69
70
  {#snippet popper({ props })}
70
71
  {@const finalProps = mergeProps(props, {
@@ -95,6 +96,7 @@
95
96
  forceMount={false}
96
97
  isStatic
97
98
  {id}
99
+ shouldRender={contentState.shouldRender}
98
100
  >
99
101
  {#snippet popper({ props })}
100
102
  {@const finalProps = mergeProps(props, {
@@ -69,6 +69,7 @@
69
69
  {loop}
70
70
  forceMount={true}
71
71
  {id}
72
+ shouldRender={contentState.shouldRender}
72
73
  >
73
74
  {#snippet popper({ props, wrapperProps })}
74
75
  {@const finalProps = mergeProps(props, {
@@ -100,6 +101,7 @@
100
101
  {loop}
101
102
  forceMount={false}
102
103
  {id}
104
+ shouldRender={contentState.shouldRender}
103
105
  >
104
106
  {#snippet popper({ props, wrapperProps })}
105
107
  {@const finalProps = mergeProps(props, {
@@ -120,6 +120,7 @@
120
120
  {loop}
121
121
  {trapFocus}
122
122
  isStatic
123
+ shouldRender={subContentState.shouldRender}
123
124
  >
124
125
  {#snippet popper({ props })}
125
126
  {@const finalProps = mergeProps(props, mergedProps, {
@@ -150,6 +151,7 @@
150
151
  {loop}
151
152
  {trapFocus}
152
153
  isStatic
154
+ shouldRender={subContentState.shouldRender}
153
155
  >
154
156
  {#snippet popper({ props })}
155
157
  {@const finalProps = mergeProps(props, mergedProps, {
@@ -121,6 +121,7 @@
121
121
  preventScroll={false}
122
122
  {loop}
123
123
  {trapFocus}
124
+ shouldRender={subContentState.shouldRender}
124
125
  >
125
126
  {#snippet popper({ props, wrapperProps })}
126
127
  {@const finalProps = mergeProps(props, mergedProps, {
@@ -156,6 +157,7 @@
156
157
  preventScroll={false}
157
158
  {loop}
158
159
  {trapFocus}
160
+ shouldRender={subContentState.shouldRender}
159
161
  >
160
162
  {#snippet popper({ props, wrapperProps })}
161
163
  {@const finalProps = mergeProps(props, mergedProps, {
@@ -6,6 +6,7 @@ import type { Direction } from "../../shared/index.js";
6
6
  import { IsUsingKeyboard } from "../../index.js";
7
7
  import type { KeyboardEventHandler, PointerEventHandler, MouseEventHandler } from "svelte/elements";
8
8
  import { RovingFocusGroup } from "../../internal/roving-focus-group.js";
9
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
9
10
  export declare const CONTEXT_MENU_TRIGGER_ATTR = "data-context-menu-trigger";
10
11
  export declare const CONTEXT_MENU_CONTENT_ATTR = "data-context-menu-content";
11
12
  export declare const MenuCheckboxGroupContext: Context<MenuCheckboxGroupState>;
@@ -40,6 +41,7 @@ export declare class MenuMenuState {
40
41
  readonly parentMenu: MenuMenuState | null;
41
42
  contentId: ReadableBox<string>;
42
43
  contentNode: HTMLElement | null;
44
+ contentPresence: PresenceManager;
43
45
  triggerNode: HTMLElement | null;
44
46
  constructor(opts: MenuMenuStateOpts, root: MenuRootState, parentMenu: MenuMenuState | null);
45
47
  toggleOpen(): void;
@@ -72,6 +74,7 @@ export declare class MenuContentState {
72
74
  onItemLeave(e: BitsPointerEvent): void;
73
75
  onTriggerLeave(): boolean;
74
76
  handleInteractOutside(e: PointerEvent): void;
77
+ get shouldRender(): boolean;
75
78
  readonly snippetProps: {
76
79
  open: boolean;
77
80
  };
@@ -12,7 +12,7 @@ import { isTabbable } from "tabbable";
12
12
  import { DOMTypeahead } from "../../internal/dom-typeahead.svelte.js";
13
13
  import { RovingFocusGroup } from "../../internal/roving-focus-group.js";
14
14
  import { GraceArea } from "../../internal/grace-area.svelte.js";
15
- import { OpenChangeComplete } from "../../internal/open-change-complete.js";
15
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
16
16
  export const CONTEXT_MENU_TRIGGER_ATTR = "data-context-menu-trigger";
17
17
  export const CONTEXT_MENU_CONTENT_ATTR = "data-context-menu-content";
18
18
  const MenuRootContext = new Context("Menu.Root");
@@ -68,12 +68,13 @@ export class MenuMenuState {
68
68
  parentMenu;
69
69
  contentId = boxWith(() => "");
70
70
  contentNode = $state(null);
71
+ contentPresence;
71
72
  triggerNode = $state(null);
72
73
  constructor(opts, root, parentMenu) {
73
74
  this.opts = opts;
74
75
  this.root = root;
75
76
  this.parentMenu = parentMenu;
76
- new OpenChangeComplete({
77
+ this.contentPresence = new PresenceManager({
77
78
  ref: boxWith(() => this.contentNode),
78
79
  open: this.opts.open,
79
80
  onComplete: () => {
@@ -306,6 +307,9 @@ export class MenuContentState {
306
307
  e.preventDefault();
307
308
  }
308
309
  }
310
+ get shouldRender() {
311
+ return this.parentMenu.contentPresence.shouldRender;
312
+ }
309
313
  snippetProps = $derived.by(() => ({ open: this.parentMenu.opts.open.current }));
310
314
  props = $derived.by(() => ({
311
315
  id: this.opts.id.current,
@@ -51,6 +51,7 @@
51
51
  loop
52
52
  forceMount={true}
53
53
  {onCloseAutoFocus}
54
+ shouldRender={contentState.shouldRender}
54
55
  >
55
56
  {#snippet popper({ props })}
56
57
  {@const finalProps = mergeProps(props, {
@@ -78,6 +79,7 @@
78
79
  loop
79
80
  forceMount={false}
80
81
  {onCloseAutoFocus}
82
+ shouldRender={contentState.shouldRender}
81
83
  >
82
84
  {#snippet popper({ props })}
83
85
  {@const finalProps = mergeProps(props, {
@@ -52,6 +52,7 @@
52
52
  forceMount={true}
53
53
  {customAnchor}
54
54
  {onCloseAutoFocus}
55
+ shouldRender={contentState.shouldRender}
55
56
  >
56
57
  {#snippet popper({ props, wrapperProps })}
57
58
  {@const finalProps = mergeProps(props, {
@@ -81,6 +82,7 @@
81
82
  forceMount={false}
82
83
  {customAnchor}
83
84
  {onCloseAutoFocus}
85
+ shouldRender={contentState.shouldRender}
84
86
  >
85
87
  {#snippet popper({ props, wrapperProps })}
86
88
  {@const finalProps = mergeProps(props, {
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import { boxWith, mergeProps } from "svelte-toolbelt";
3
+ import { PopoverOverlayState } from "../popover.svelte.js";
4
+ import type { PopoverOverlayProps } from "../types.js";
5
+ import { createId } from "../../../internal/create-id.js";
6
+
7
+ const uid = $props.id();
8
+
9
+ let {
10
+ id = createId(uid),
11
+ forceMount = false,
12
+ child,
13
+ children,
14
+ ref = $bindable(null),
15
+ ...restProps
16
+ }: PopoverOverlayProps = $props();
17
+
18
+ const overlayState = PopoverOverlayState.create({
19
+ id: boxWith(() => id),
20
+ ref: boxWith(
21
+ () => ref,
22
+ (v) => (ref = v)
23
+ ),
24
+ });
25
+
26
+ const mergedProps = $derived(mergeProps(restProps, overlayState.props));
27
+ </script>
28
+
29
+ {#if overlayState.shouldRender || forceMount}
30
+ {#if child}
31
+ {@render child({ props: mergeProps(mergedProps), ...overlayState.snippetProps })}
32
+ {:else}
33
+ <div {...mergeProps(mergedProps)}>
34
+ {@render children?.(overlayState.snippetProps)}
35
+ </div>
36
+ {/if}
37
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { PopoverOverlayProps } from "../types.js";
2
+ declare const PopoverOverlay: import("svelte").Component<PopoverOverlayProps, {}, "ref">;
3
+ type PopoverOverlay = ReturnType<typeof PopoverOverlay>;
4
+ export default PopoverOverlay;
@@ -5,4 +5,5 @@ export { default as ContentStatic } from "./components/popover-content-static.sv
5
5
  export { default as Trigger } from "./components/popover-trigger.svelte";
6
6
  export { default as Close } from "./components/popover-close.svelte";
7
7
  export { default as Portal } from "../utilities/portal/portal.svelte";
8
- export type { PopoverRootProps as RootProps, PopoverArrowProps as ArrowProps, PopoverContentProps as ContentProps, PopoverContentStaticProps as ContentStaticProps, PopoverTriggerProps as TriggerProps, PopoverCloseProps as CloseProps, PopoverPortalProps as PortalProps, } from "./types.js";
8
+ export { default as Overlay } from "./components/popover-overlay.svelte";
9
+ export type { PopoverRootProps as RootProps, PopoverArrowProps as ArrowProps, PopoverContentProps as ContentProps, PopoverContentStaticProps as ContentStaticProps, PopoverTriggerProps as TriggerProps, PopoverCloseProps as CloseProps, PopoverPortalProps as PortalProps, PopoverOverlayProps as OverlayProps, } from "./types.js";
@@ -5,3 +5,4 @@ export { default as ContentStatic } from "./components/popover-content-static.sv
5
5
  export { default as Trigger } from "./components/popover-trigger.svelte";
6
6
  export { default as Close } from "./components/popover-close.svelte";
7
7
  export { default as Portal } from "../utilities/portal/portal.svelte";
8
+ export { default as Overlay } from "./components/popover-overlay.svelte";
@@ -1,6 +1,7 @@
1
1
  import { type ReadableBoxedValues, type WritableBoxedValues } from "svelte-toolbelt";
2
2
  import type { BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
3
3
  import type { Measurable } from "../../internal/floating-svelte/types.js";
4
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
4
5
  interface PopoverRootStateOpts extends WritableBoxedValues<{
5
6
  open: boolean;
6
7
  }>, ReadableBoxedValues<{
@@ -11,7 +12,10 @@ export declare class PopoverRootState {
11
12
  static create(opts: PopoverRootStateOpts): PopoverRootState;
12
13
  readonly opts: PopoverRootStateOpts;
13
14
  contentNode: HTMLElement | null;
15
+ contentPresence: PresenceManager;
14
16
  triggerNode: HTMLElement | null;
17
+ overlayNode: HTMLElement | null;
18
+ overlayPresence: PresenceManager;
15
19
  constructor(opts: PopoverRootStateOpts);
16
20
  toggleOpen(): void;
17
21
  handleClose(): void;
@@ -54,6 +58,7 @@ export declare class PopoverContentState {
54
58
  constructor(opts: PopoverContentStateOpts, root: PopoverRootState);
55
59
  onInteractOutside: (e: PointerEvent) => void;
56
60
  onEscapeKeydown: (e: KeyboardEvent) => void;
61
+ get shouldRender(): boolean;
57
62
  readonly snippetProps: {
58
63
  open: boolean;
59
64
  };
@@ -87,4 +92,24 @@ export declare class PopoverCloseState {
87
92
  readonly type: "button";
88
93
  };
89
94
  }
95
+ interface PopoverOverlayStateOpts extends WithRefOpts {
96
+ }
97
+ export declare class PopoverOverlayState {
98
+ static create(opts: PopoverOverlayStateOpts): PopoverOverlayState;
99
+ readonly opts: PopoverOverlayStateOpts;
100
+ readonly root: PopoverRootState;
101
+ readonly attachment: RefAttachment;
102
+ constructor(opts: PopoverOverlayStateOpts, root: PopoverRootState);
103
+ get shouldRender(): boolean;
104
+ readonly snippetProps: {
105
+ open: boolean;
106
+ };
107
+ readonly props: {
108
+ readonly id: string;
109
+ readonly style: {
110
+ readonly pointerEvents: "auto";
111
+ };
112
+ readonly "data-state": "open" | "closed";
113
+ };
114
+ }
90
115
  export {};
@@ -3,10 +3,10 @@ import { Context } from "runed";
3
3
  import { kbd } from "../../internal/kbd.js";
4
4
  import { createBitsAttrs, boolToStr, getDataOpenClosed } from "../../internal/attrs.js";
5
5
  import { isElement } from "../../internal/is.js";
6
- import { OpenChangeComplete } from "../../internal/open-change-complete.js";
6
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
7
7
  const popoverAttrs = createBitsAttrs({
8
8
  component: "popover",
9
- parts: ["root", "trigger", "content", "close"],
9
+ parts: ["root", "trigger", "content", "close", "overlay"],
10
10
  });
11
11
  const PopoverRootContext = new Context("Popover.Root");
12
12
  export class PopoverRootState {
@@ -15,16 +15,23 @@ export class PopoverRootState {
15
15
  }
16
16
  opts;
17
17
  contentNode = $state(null);
18
+ contentPresence;
18
19
  triggerNode = $state(null);
20
+ overlayNode = $state(null);
21
+ overlayPresence;
19
22
  constructor(opts) {
20
23
  this.opts = opts;
21
- new OpenChangeComplete({
24
+ this.contentPresence = new PresenceManager({
22
25
  ref: boxWith(() => this.contentNode),
23
26
  open: this.opts.open,
24
27
  onComplete: () => {
25
28
  this.opts.onOpenChangeComplete.current(this.opts.open.current);
26
29
  },
27
30
  });
31
+ this.overlayPresence = new PresenceManager({
32
+ ref: boxWith(() => this.overlayNode),
33
+ open: this.opts.open,
34
+ });
28
35
  }
29
36
  toggleOpen() {
30
37
  this.opts.open.current = !this.opts.open.current;
@@ -124,6 +131,9 @@ export class PopoverContentState {
124
131
  return;
125
132
  this.root.handleClose();
126
133
  };
134
+ get shouldRender() {
135
+ return this.root.contentPresence.shouldRender;
136
+ }
127
137
  snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
128
138
  props = $derived.by(() => ({
129
139
  id: this.opts.id.current,
@@ -172,3 +182,29 @@ export class PopoverCloseState {
172
182
  ...this.attachment,
173
183
  }));
174
184
  }
185
+ export class PopoverOverlayState {
186
+ static create(opts) {
187
+ return new PopoverOverlayState(opts, PopoverRootContext.get());
188
+ }
189
+ opts;
190
+ root;
191
+ attachment;
192
+ constructor(opts, root) {
193
+ this.opts = opts;
194
+ this.root = root;
195
+ this.attachment = attachRef(this.opts.ref, (v) => (this.root.overlayNode = v));
196
+ }
197
+ get shouldRender() {
198
+ return this.root.overlayPresence.shouldRender;
199
+ }
200
+ snippetProps = $derived.by(() => ({ open: this.root.opts.open.current }));
201
+ props = $derived.by(() => ({
202
+ id: this.opts.id.current,
203
+ [popoverAttrs.overlay]: "",
204
+ style: {
205
+ pointerEvents: "auto",
206
+ },
207
+ "data-state": getDataOpenClosed(this.root.opts.open.current),
208
+ ...this.attachment,
209
+ }));
210
+ }
@@ -4,6 +4,7 @@ import type { OnChangeFn, WithChild, WithChildNoChildrenSnippetProps, WithChildr
4
4
  import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
5
5
  import type { FloatingContentSnippetProps, StaticContentSnippetProps } from "../../shared/types.js";
6
6
  import type { PortalProps } from "../../types.js";
7
+ import type { PresenceLayerProps } from "../utilities/presence-layer/types.js";
7
8
  export type PopoverRootPropsWithoutHTML = WithChildren<{
8
9
  /**
9
10
  * The open state of the popover.
@@ -31,3 +32,8 @@ export type PopoverArrowPropsWithoutHTML = ArrowPropsWithoutHTML;
31
32
  export type PopoverArrowProps = ArrowProps;
32
33
  export type PopoverPortalPropsWithoutHTML = PortalProps;
33
34
  export type PopoverPortalProps = PortalProps;
35
+ export type PopoverOverlaySnippetProps = {
36
+ open: boolean;
37
+ };
38
+ export type PopoverOverlayPropsWithoutHTML = WithChild<PresenceLayerProps, PopoverOverlaySnippetProps>;
39
+ export type PopoverOverlayProps = PopoverOverlayPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, PopoverOverlayPropsWithoutHTML>;
@@ -44,6 +44,7 @@
44
44
  {id}
45
45
  {preventScroll}
46
46
  forceMount={true}
47
+ shouldRender={contentState.shouldRender}
47
48
  >
48
49
  {#snippet popper({ props })}
49
50
  {@const finalProps = mergeProps(props, { style: contentState.props.style })}
@@ -66,6 +67,7 @@
66
67
  {id}
67
68
  {preventScroll}
68
69
  forceMount={false}
70
+ shouldRender={contentState.shouldRender}
69
71
  >
70
72
  {#snippet popper({ props })}
71
73
  {@const finalProps = mergeProps(props, { style: contentState.props.style })}
@@ -45,6 +45,7 @@
45
45
  {id}
46
46
  {preventScroll}
47
47
  forceMount={true}
48
+ shouldRender={contentState.shouldRender}
48
49
  >
49
50
  {#snippet popper({ props, wrapperProps })}
50
51
  {@const finalProps = mergeProps(props, { style: contentState.props.style })}
@@ -69,6 +70,7 @@
69
70
  {id}
70
71
  {preventScroll}
71
72
  forceMount={false}
73
+ shouldRender={contentState.shouldRender}
72
74
  >
73
75
  {#snippet popper({ props, wrapperProps })}
74
76
  {@const finalProps = mergeProps(props, { style: contentState.props.style })}
@@ -1,6 +1,7 @@
1
1
  import { Previous } from "runed";
2
2
  import { DOMContext, type ReadableBoxedValues, type WritableBoxedValues, type Box } from "svelte-toolbelt";
3
3
  import type { BitsEvent, BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, OnChangeFn, WithRefOpts, RefAttachment } from "../../internal/types.js";
4
+ import { PresenceManager } from "../../internal/presence-manager.svelte.js";
4
5
  export declare const INTERACTION_KEYS: string[];
5
6
  export declare const FIRST_KEYS: string[];
6
7
  export declare const LAST_KEYS: string[];
@@ -32,6 +33,7 @@ declare abstract class SelectBaseRootState {
32
33
  touchedInput: boolean;
33
34
  inputNode: HTMLElement | null;
34
35
  contentNode: HTMLElement | null;
36
+ contentPresence: PresenceManager;
35
37
  viewportNode: HTMLElement | null;
36
38
  triggerNode: HTMLElement | null;
37
39
  valueId: string;
@@ -204,6 +206,7 @@ export declare class SelectContentState {
204
206
  onEscapeKeydown: (e: KeyboardEvent) => void;
205
207
  onOpenAutoFocus: (e: Event) => void;
206
208
  onCloseAutoFocus: (e: Event) => void;
209
+ get shouldRender(): boolean;
207
210
  readonly snippetProps: {
208
211
  open: boolean;
209
212
  };