bits-ui 2.11.8 → 2.13.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.
@@ -20,10 +20,15 @@ export declare class DialogRootState {
20
20
  triggerId: string | undefined;
21
21
  descriptionId: string | undefined;
22
22
  cancelNode: HTMLElement | null;
23
- constructor(opts: DialogRootStateOpts);
23
+ nestedOpenCount: number;
24
+ readonly depth: number;
25
+ readonly parent: DialogRootState | null;
26
+ constructor(opts: DialogRootStateOpts, parent: DialogRootState | null);
24
27
  handleOpen(): void;
25
28
  handleClose(): void;
26
29
  getBitsAttr: typeof dialogAttrs.getAttr;
30
+ incrementNested(): void;
31
+ decrementNested(): void;
27
32
  readonly sharedProps: {
28
33
  readonly "data-state": "open" | "closed";
29
34
  };
@@ -137,8 +142,12 @@ export declare class DialogContentState {
137
142
  readonly style: {
138
143
  readonly pointerEvents: "auto";
139
144
  readonly outline: "none" | undefined;
145
+ readonly "--bits-dialog-depth": number;
146
+ readonly "--bits-dialog-nested-count": number;
140
147
  };
141
148
  readonly tabindex: -1 | undefined;
149
+ readonly "data-nested-open": "" | undefined;
150
+ readonly "data-nested": "" | undefined;
142
151
  };
143
152
  }
144
153
  interface DialogOverlayStateOpts extends WithRefOpts {
@@ -157,7 +166,11 @@ export declare class DialogOverlayState {
157
166
  readonly id: string;
158
167
  readonly style: {
159
168
  readonly pointerEvents: "auto";
169
+ readonly "--bits-dialog-depth": number;
170
+ readonly "--bits-dialog-nested-count": number;
160
171
  };
172
+ readonly "data-nested-open": "" | undefined;
173
+ readonly "data-nested": "" | undefined;
161
174
  };
162
175
  }
163
176
  interface AlertDialogCancelStateOpts extends WithRefOpts, ReadableBoxedValues<{
@@ -1,6 +1,6 @@
1
- import { attachRef, boxWith, } from "svelte-toolbelt";
1
+ import { attachRef, boxWith, onDestroyEffect, } from "svelte-toolbelt";
2
2
  import { Context, watch } from "runed";
3
- import { createBitsAttrs, boolToStr, getDataOpenClosed } from "../../internal/attrs.js";
3
+ import { createBitsAttrs, boolToStr, getDataOpenClosed, boolToEmptyStrOrUndef, } from "../../internal/attrs.js";
4
4
  import { kbd } from "../../internal/kbd.js";
5
5
  import { OpenChangeComplete } from "../../internal/open-change-complete.js";
6
6
  const dialogAttrs = createBitsAttrs({
@@ -10,7 +10,8 @@ const dialogAttrs = createBitsAttrs({
10
10
  const DialogRootContext = new Context("Dialog.Root | AlertDialog.Root");
11
11
  export class DialogRootState {
12
12
  static create(opts) {
13
- return DialogRootContext.set(new DialogRootState(opts));
13
+ const parent = DialogRootContext.getOr(null);
14
+ return DialogRootContext.set(new DialogRootState(opts, parent));
14
15
  }
15
16
  opts;
16
17
  triggerNode = $state(null);
@@ -21,8 +22,13 @@ export class DialogRootState {
21
22
  triggerId = $state(undefined);
22
23
  descriptionId = $state(undefined);
23
24
  cancelNode = $state(null);
24
- constructor(opts) {
25
+ nestedOpenCount = $state(0);
26
+ depth;
27
+ parent;
28
+ constructor(opts, parent) {
25
29
  this.opts = opts;
30
+ this.parent = parent;
31
+ this.depth = parent ? parent.depth + 1 : 0;
26
32
  this.handleOpen = this.handleOpen.bind(this);
27
33
  this.handleClose = this.handleClose.bind(this);
28
34
  new OpenChangeComplete({
@@ -33,6 +39,21 @@ export class DialogRootState {
33
39
  this.opts.onOpenChangeComplete.current(this.opts.open.current);
34
40
  },
35
41
  });
42
+ watch(() => this.opts.open.current, (isOpen) => {
43
+ if (!this.parent)
44
+ return;
45
+ if (isOpen) {
46
+ this.parent.incrementNested();
47
+ }
48
+ else {
49
+ this.parent.decrementNested();
50
+ }
51
+ }, { lazy: true });
52
+ onDestroyEffect(() => {
53
+ if (this.opts.open.current) {
54
+ this.parent?.decrementNested();
55
+ }
56
+ });
36
57
  }
37
58
  handleOpen() {
38
59
  if (this.opts.open.current)
@@ -47,6 +68,16 @@ export class DialogRootState {
47
68
  getBitsAttr = (part) => {
48
69
  return dialogAttrs.getAttr(part, this.opts.variant.current);
49
70
  };
71
+ incrementNested() {
72
+ this.nestedOpenCount++;
73
+ this.parent?.incrementNested();
74
+ }
75
+ decrementNested() {
76
+ if (this.nestedOpenCount === 0)
77
+ return;
78
+ this.nestedOpenCount--;
79
+ this.parent?.decrementNested();
80
+ }
50
81
  sharedProps = $derived.by(() => ({
51
82
  "data-state": getDataOpenClosed(this.opts.open.current),
52
83
  }));
@@ -231,8 +262,12 @@ export class DialogContentState {
231
262
  style: {
232
263
  pointerEvents: "auto",
233
264
  outline: this.root.opts.variant.current === "alert-dialog" ? "none" : undefined,
265
+ "--bits-dialog-depth": this.root.depth,
266
+ "--bits-dialog-nested-count": this.root.nestedOpenCount,
234
267
  },
235
268
  tabindex: this.root.opts.variant.current === "alert-dialog" ? -1 : undefined,
269
+ "data-nested-open": boolToEmptyStrOrUndef(this.root.nestedOpenCount > 0),
270
+ "data-nested": boolToEmptyStrOrUndef(this.root.parent !== null),
236
271
  ...this.root.sharedProps,
237
272
  ...this.attachment,
238
273
  }));
@@ -255,7 +290,11 @@ export class DialogOverlayState {
255
290
  [this.root.getBitsAttr("overlay")]: "",
256
291
  style: {
257
292
  pointerEvents: "auto",
293
+ "--bits-dialog-depth": this.root.depth,
294
+ "--bits-dialog-nested-count": this.root.nestedOpenCount,
258
295
  },
296
+ "data-nested-open": boolToEmptyStrOrUndef(this.root.nestedOpenCount > 0),
297
+ "data-nested": boolToEmptyStrOrUndef(this.root.parent !== null),
259
298
  ...this.root.sharedProps,
260
299
  ...this.attachment,
261
300
  }));
@@ -15,6 +15,7 @@
15
15
  children,
16
16
  child,
17
17
  onSelect = noop,
18
+ openDelay = 100,
18
19
  ...restProps
19
20
  }: MenuSubTriggerProps = $props();
20
21
 
@@ -26,6 +27,7 @@
26
27
  () => ref,
27
28
  (v) => (ref = v)
28
29
  ),
30
+ openDelay: boxWith(() => openDelay),
29
31
  });
30
32
 
31
33
  const mergedProps = $derived(mergeProps(restProps, subTriggerState.props));
@@ -1,4 +1,4 @@
1
- import { DOMContext, type ReadableBoxedValues, type WritableBoxedValues } from "svelte-toolbelt";
1
+ import { DOMContext, type ReadableBoxedValues, type WritableBoxedValues, type ReadableBox } from "svelte-toolbelt";
2
2
  import { Context } from "runed";
3
3
  import { CustomEventDispatcher } from "../../internal/events.js";
4
4
  import type { AnyFn, BitsFocusEvent, BitsKeyboardEvent, BitsMouseEvent, BitsPointerEvent, OnChangeFn, RefAttachment, WithRefOpts } from "../../internal/types.js";
@@ -38,7 +38,7 @@ export declare class MenuMenuState {
38
38
  readonly opts: MenuMenuStateOpts;
39
39
  readonly root: MenuRootState;
40
40
  readonly parentMenu: MenuMenuState | null;
41
- contentId: import("svelte-toolbelt").ReadableBox<string>;
41
+ contentId: ReadableBox<string>;
42
42
  contentNode: HTMLElement | null;
43
43
  triggerNode: HTMLElement | null;
44
44
  constructor(opts: MenuMenuStateOpts, root: MenuRootState, parentMenu: MenuMenuState | null);
@@ -157,6 +157,7 @@ export declare class MenuItemState {
157
157
  };
158
158
  }
159
159
  interface MenuSubTriggerStateOpts extends MenuItemSharedStateOpts, Pick<MenuItemStateOpts, "onSelect"> {
160
+ openDelay: ReadableBox<number>;
160
161
  }
161
162
  export declare class MenuSubTriggerState {
162
163
  #private;
@@ -510,7 +510,7 @@ export class MenuSubTriggerState {
510
510
  this.#openTimer = this.content.domContext.setTimeout(() => {
511
511
  this.submenu.onOpen();
512
512
  this.#clearOpenTimer();
513
- }, 100);
513
+ }, this.opts.openDelay.current);
514
514
  }
515
515
  }
516
516
  onpointerleave(e) {
@@ -149,8 +149,20 @@ export type MenuSubContentPropsWithoutHTML = Expand<WithChildNoChildrenSnippetPr
149
149
  export type MenuSubContentProps = MenuSubContentPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuSubContentPropsWithoutHTML>;
150
150
  export type MenuSubContentStaticPropsWithoutHTML = Expand<WithChildNoChildrenSnippetProps<Omit<PopperLayerStaticProps, "content" | "preventScroll"> & _SharedMenuContentProps, StaticContentSnippetProps>>;
151
151
  export type MenuSubContentStaticProps = MenuSubContentStaticPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuSubContentStaticPropsWithoutHTML>;
152
- export type MenuSubTriggerPropsWithoutHTML = Omit<MenuItemPropsWithoutHTML, "closeOnSelect">;
153
- export type MenuSubTriggerProps = Omit<MenuItemProps, "closeOnSelect">;
152
+ export type MenuSubTriggerPropsWithoutHTML = Omit<MenuItemPropsWithoutHTML, "closeOnSelect"> & {
153
+ /**
154
+ * The amount of time in ms from when the mouse enters the subtrigger until
155
+ * the submenu opens. This is useful for preventing the submenu from opening
156
+ * as a user is moving their mouse through the menu without a true intention to open that
157
+ * submenu.
158
+ *
159
+ * To disable the behavior, set it to `0`.
160
+ *
161
+ * @default 100
162
+ */
163
+ openDelay?: number;
164
+ };
165
+ export type MenuSubTriggerProps = MenuSubTriggerPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuSubTriggerPropsWithoutHTML>;
154
166
  export type MenuSeparatorPropsWithoutHTML = WithChild;
155
167
  export type MenuSeparatorProps = MenuSeparatorPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, MenuSeparatorPropsWithoutHTML>;
156
168
  export type MenuArrowPropsWithoutHTML = ArrowPropsWithoutHTML;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "2.11.8",
3
+ "version": "2.13.0",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",