bits-ui 1.0.0-next.20 → 1.0.0-next.22

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 (49) hide show
  1. package/dist/bits/accordion/accordion.svelte.d.ts +2 -1
  2. package/dist/bits/accordion/accordion.svelte.js +15 -2
  3. package/dist/bits/alert-dialog/components/alert-dialog-cancel.svelte +2 -0
  4. package/dist/bits/checkbox/checkbox.svelte.js +15 -5
  5. package/dist/bits/collapsible/collapsible.svelte.d.ts +3 -1
  6. package/dist/bits/collapsible/collapsible.svelte.js +24 -2
  7. package/dist/bits/command/command.svelte.d.ts +2 -1
  8. package/dist/bits/command/command.svelte.js +13 -2
  9. package/dist/bits/context-menu/components/context-menu-content-static.svelte +0 -14
  10. package/dist/bits/context-menu/components/context-menu-content.svelte +0 -14
  11. package/dist/bits/dialog/components/dialog-close.svelte +2 -0
  12. package/dist/bits/dialog/components/dialog-trigger.svelte +2 -0
  13. package/dist/bits/dialog/dialog.svelte.d.ts +16 -5
  14. package/dist/bits/dialog/dialog.svelte.js +88 -6
  15. package/dist/bits/dropdown-menu/components/dropdown-menu-content-static.svelte +0 -3
  16. package/dist/bits/dropdown-menu/components/dropdown-menu-content.svelte +0 -3
  17. package/dist/bits/menu/components/menu-content-static.svelte +0 -14
  18. package/dist/bits/menu/components/menu-content.svelte +0 -14
  19. package/dist/bits/menu/menu.svelte.d.ts +2 -2
  20. package/dist/bits/menu/menu.svelte.js +14 -1
  21. package/dist/bits/navigation-menu/types.d.ts +1 -2
  22. package/dist/bits/pagination/components/pagination-next-button.svelte +2 -0
  23. package/dist/bits/pagination/components/pagination-page.svelte +2 -0
  24. package/dist/bits/pagination/components/pagination-prev-button.svelte +2 -0
  25. package/dist/bits/pagination/pagination.svelte.d.ts +7 -2
  26. package/dist/bits/pagination/pagination.svelte.js +51 -6
  27. package/dist/bits/pin-input/pin-input.svelte.d.ts +1 -1
  28. package/dist/bits/popover/components/popover-trigger.svelte +2 -0
  29. package/dist/bits/popover/popover.svelte.d.ts +7 -3
  30. package/dist/bits/popover/popover.svelte.js +29 -4
  31. package/dist/bits/radio-group/radio-group.svelte.d.ts +2 -1
  32. package/dist/bits/radio-group/radio-group.svelte.js +15 -2
  33. package/dist/bits/select/select.svelte.d.ts +1 -2
  34. package/dist/bits/select/select.svelte.js +4 -4
  35. package/dist/bits/switch/switch.svelte.d.ts +1 -1
  36. package/dist/bits/switch/switch.svelte.js +1 -1
  37. package/dist/bits/tabs/tabs.svelte.d.ts +2 -1
  38. package/dist/bits/tabs/tabs.svelte.js +16 -4
  39. package/dist/bits/toggle/toggle.svelte.d.ts +1 -2
  40. package/dist/bits/toggle/toggle.svelte.js +1 -6
  41. package/dist/bits/toggle-group/toggle-group.svelte.d.ts +1 -1
  42. package/dist/bits/toggle-group/toggle-group.svelte.js +3 -1
  43. package/dist/bits/toolbar/toolbar.svelte.d.ts +3 -2
  44. package/dist/bits/toolbar/toolbar.svelte.js +15 -2
  45. package/dist/bits/utilities/dismissible-layer/dismissible-layer.svelte +0 -2
  46. package/dist/bits/utilities/dismissible-layer/types.d.ts +3 -14
  47. package/dist/bits/utilities/dismissible-layer/useDismissibleLayer.svelte.js +34 -64
  48. package/dist/bits/utilities/popper-layer/popper-layer.svelte +0 -2
  49. package/package.json +2 -2
@@ -79,7 +79,8 @@ declare class AccordionTriggerState {
79
79
  readonly "data-orientation": "horizontal" | "vertical";
80
80
  readonly "data-accordion-trigger": "";
81
81
  readonly tabindex: 0;
82
- readonly onclick: () => void;
82
+ readonly onpointerdown: (e: PointerEvent) => void;
83
+ readonly onpointerup: (e: PointerEvent) => void;
83
84
  readonly onkeydown: (e: KeyboardEvent) => void;
84
85
  };
85
86
  }
@@ -123,12 +123,24 @@ class AccordionTriggerState {
123
123
  ref: this.#ref,
124
124
  });
125
125
  }
126
- #onclick = () => {
126
+ #onpointerdown = (e) => {
127
127
  if (this.#isDisabled)
128
128
  return;
129
+ if (e.pointerType === "touch" || e.button !== 0)
130
+ return e.preventDefault();
129
131
  this.#itemState.updateValue();
130
132
  };
133
+ #onpointerup = (e) => {
134
+ if (this.#isDisabled)
135
+ return;
136
+ if (e.pointerType === "touch") {
137
+ e.preventDefault();
138
+ this.#itemState.updateValue();
139
+ }
140
+ };
131
141
  #onkeydown = (e) => {
142
+ if (this.#isDisabled)
143
+ return;
132
144
  if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
133
145
  e.preventDefault();
134
146
  this.#itemState.updateValue();
@@ -147,7 +159,8 @@ class AccordionTriggerState {
147
159
  [ACCORDION_TRIGGER_ATTR]: "",
148
160
  tabindex: 0,
149
161
  //
150
- onclick: this.#onclick,
162
+ onpointerdown: this.#onpointerdown,
163
+ onpointerup: this.#onpointerup,
151
164
  onkeydown: this.#onkeydown,
152
165
  }));
153
166
  }
@@ -9,6 +9,7 @@
9
9
  ref = $bindable(null),
10
10
  children,
11
11
  child,
12
+ disabled = false,
12
13
  ...restProps
13
14
  }: AlertDialogCancelProps = $props();
14
15
 
@@ -18,6 +19,7 @@
18
19
  () => ref,
19
20
  (v) => (ref = v)
20
21
  ),
22
+ disabled: box.with(() => Boolean(disabled)),
21
23
  });
22
24
 
23
25
  const mergedProps = $derived(mergeProps(restProps, cancelState.props));
@@ -25,17 +25,27 @@ class CheckboxRootState {
25
25
  });
26
26
  }
27
27
  #onkeydown = (e) => {
28
+ if (this.disabled.current)
29
+ return;
28
30
  if (e.key === kbd.ENTER)
29
31
  e.preventDefault();
32
+ if (e.key === kbd.SPACE) {
33
+ e.preventDefault();
34
+ this.#toggle();
35
+ }
30
36
  };
31
- #onclick = () => {
32
- if (this.disabled.current)
33
- return;
37
+ #toggle = () => {
34
38
  if (this.checked.current === "indeterminate") {
35
39
  this.checked.current = true;
36
- return;
37
40
  }
38
- this.checked.current = !this.checked.current;
41
+ else {
42
+ this.checked.current = !this.checked.current;
43
+ }
44
+ };
45
+ #onclick = () => {
46
+ if (this.disabled.current)
47
+ return;
48
+ this.#toggle();
39
49
  };
40
50
  props = $derived.by(() => ({
41
51
  id: this.#id.current,
@@ -64,7 +64,9 @@ declare class CollapsibleTriggerState {
64
64
  readonly "data-state": "open" | "closed";
65
65
  readonly "data-disabled": "" | undefined;
66
66
  readonly "data-collapsible-trigger": "";
67
- readonly onclick: () => void;
67
+ readonly onpointerdown: (e: PointerEvent) => void;
68
+ readonly onpointerup: (e: PointerEvent) => void;
69
+ readonly onkeydown: (e: KeyboardEvent) => void;
68
70
  };
69
71
  }
70
72
  export declare function useCollapsibleRoot(props: CollapsibleRootStateProps): CollapsibleRootState;
@@ -1,6 +1,7 @@
1
1
  import { afterTick, useRefById } from "svelte-toolbelt";
2
2
  import { getAriaExpanded, getDataDisabled, getDataOpenClosed } from "../../internal/attrs.js";
3
3
  import { createContext } from "../../internal/create-context.js";
4
+ import { kbd } from "../../internal/kbd.js";
4
5
  const COLLAPSIBLE_ROOT_ATTR = "data-collapsible-root";
5
6
  const COLLAPSIBLE_CONTENT_ATTR = "data-collapsible-content";
6
7
  const COLLAPSIBLE_TRIGGER_ATTR = "data-collapsible-trigger";
@@ -126,9 +127,28 @@ class CollapsibleTriggerState {
126
127
  ref: this.#ref,
127
128
  });
128
129
  }
129
- #onclick = () => {
130
+ #onpointerdown = (e) => {
131
+ if (this.#isDisabled)
132
+ return;
133
+ if (e.pointerType === "touch" || e.button !== 0)
134
+ return e.preventDefault();
130
135
  this.#root.toggleOpen();
131
136
  };
137
+ #onpointerup = (e) => {
138
+ if (this.#isDisabled)
139
+ return;
140
+ if (e.pointerType === "touch") {
141
+ e.preventDefault();
142
+ this.#root.toggleOpen();
143
+ }
144
+ };
145
+ #onkeydown = (e) => {
146
+ if (this.#isDisabled)
147
+ return;
148
+ if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
149
+ this.#root.toggleOpen();
150
+ }
151
+ };
132
152
  props = $derived.by(() => ({
133
153
  id: this.#id.current,
134
154
  type: "button",
@@ -139,7 +159,9 @@ class CollapsibleTriggerState {
139
159
  "data-disabled": getDataDisabled(this.#isDisabled),
140
160
  [COLLAPSIBLE_TRIGGER_ATTR]: "",
141
161
  //
142
- onclick: this.#onclick,
162
+ onpointerdown: this.#onpointerdown,
163
+ onpointerup: this.#onpointerup,
164
+ onkeydown: this.#onkeydown,
143
165
  }));
144
166
  }
145
167
  const [setCollapsibleRootContext, getCollapsibleRootContext] = createContext("Collapsible.Root");
@@ -153,8 +153,9 @@ declare class CommandItemState {
153
153
  readonly "data-selected": "" | undefined;
154
154
  readonly "data-command-item": "";
155
155
  readonly role: "option";
156
- readonly onclick: () => void;
156
+ readonly onpointerdown: (e: PointerEvent) => void;
157
157
  readonly onpointermove: () => void;
158
+ readonly onpointerup: (e: PointerEvent) => void;
158
159
  };
159
160
  }
160
161
  type CommandLoadingStateProps = WithRefProps<ReadableBoxedValues<{
@@ -696,11 +696,21 @@ class CommandItemState {
696
696
  return;
697
697
  this.#select();
698
698
  };
699
- #onclick = () => {
699
+ #onpointerdown = (e) => {
700
700
  if (this.#disabled.current)
701
701
  return;
702
+ if (e.pointerType === "touch" || e.button !== 0)
703
+ return e.preventDefault();
702
704
  this.#onSelect();
703
705
  };
706
+ #onpointerup = (e) => {
707
+ if (this.#disabled.current)
708
+ return;
709
+ if (e.pointerType === "touch") {
710
+ e.preventDefault();
711
+ this.#onSelect();
712
+ }
713
+ };
704
714
  props = $derived.by(() => ({
705
715
  id: this.id.current,
706
716
  "aria-disabled": getAriaDisabled(this.#disabled.current),
@@ -709,8 +719,9 @@ class CommandItemState {
709
719
  "data-selected": getDataSelected(this.isSelected),
710
720
  [ITEM_ATTR]: "",
711
721
  role: "option",
712
- onclick: this.#onclick,
722
+ onpointerdown: this.#onpointerdown,
713
723
  onpointermove: this.#onpointermove,
724
+ onpointerup: this.#onpointerup,
714
725
  }));
715
726
  }
716
727
  class CommandLoadingState {
@@ -5,8 +5,6 @@
5
5
  import { useId } from "../../../internal/use-id.js";
6
6
  import { noop } from "../../../internal/noop.js";
7
7
  import PopperLayer from "../../utilities/popper-layer/popper-layer.svelte";
8
- import { isElement } from "../../../internal/is.js";
9
- import type { InteractOutsideEvent } from "../../utilities/dismissible-layer/types.js";
10
8
  import Mounted from "../../utilities/mounted.svelte";
11
9
 
12
10
  let {
@@ -36,17 +34,6 @@
36
34
  isMounted: box.with(() => isMounted),
37
35
  });
38
36
 
39
- function handleInteractOutsideStart(e: InteractOutsideEvent) {
40
- if (!isElement(e.target)) return;
41
- if (e.target.id === contentState.parentMenu.triggerNode?.id) {
42
- e.preventDefault();
43
- return;
44
- }
45
- if (e.target.closest(`#${contentState.parentMenu.triggerNode?.id}`)) {
46
- e.preventDefault();
47
- }
48
- }
49
-
50
37
  const mergedProps = $derived(mergeProps(restProps, contentState.props));
51
38
  </script>
52
39
 
@@ -54,7 +41,6 @@
54
41
  isStatic={true}
55
42
  {...mergedProps}
56
43
  present={contentState.parentMenu.open.current || forceMount}
57
- onInteractOutsideStart={handleInteractOutsideStart}
58
44
  {preventScroll}
59
45
  onInteractOutside={(e) => {
60
46
  onInteractOutside(e);
@@ -5,8 +5,6 @@
5
5
  import { useId } from "../../../internal/use-id.js";
6
6
  import { noop } from "../../../internal/noop.js";
7
7
  import PopperLayer from "../../utilities/popper-layer/popper-layer.svelte";
8
- import { isElement } from "../../../internal/is.js";
9
- import type { InteractOutsideEvent } from "../../utilities/dismissible-layer/types.js";
10
8
  import Mounted from "../../utilities/mounted.svelte";
11
9
  import { getFloatingContentCSSVars } from "../../../internal/floating-svelte/floating-utils.svelte.js";
12
10
 
@@ -37,17 +35,6 @@
37
35
  isMounted: box.with(() => isMounted),
38
36
  });
39
37
 
40
- function handleInteractOutsideStart(e: InteractOutsideEvent) {
41
- if (!isElement(e.target)) return;
42
- if (e.target.id === contentState.parentMenu.triggerNode?.id) {
43
- e.preventDefault();
44
- return;
45
- }
46
- if (e.target.closest(`#${contentState.parentMenu.triggerNode?.id}`)) {
47
- e.preventDefault();
48
- }
49
- }
50
-
51
38
  const mergedProps = $derived(mergeProps(restProps, contentState.props));
52
39
  </script>
53
40
 
@@ -58,7 +45,6 @@
58
45
  align="start"
59
46
  present={contentState.parentMenu.open.current || forceMount}
60
47
  {preventScroll}
61
- onInteractOutsideStart={handleInteractOutsideStart}
62
48
  onInteractOutside={(e) => {
63
49
  onInteractOutside(e);
64
50
  if (e.defaultPrevented) return;
@@ -9,6 +9,7 @@
9
9
  child,
10
10
  id = useId(),
11
11
  ref = $bindable(null),
12
+ disabled = false,
12
13
  ...restProps
13
14
  }: DialogCloseProps = $props();
14
15
 
@@ -19,6 +20,7 @@
19
20
  () => ref,
20
21
  (v) => (ref = v)
21
22
  ),
23
+ disabled: box.with(() => Boolean(disabled)),
22
24
  });
23
25
 
24
26
  const mergedProps = $derived(mergeProps(restProps, closeState.props));
@@ -9,6 +9,7 @@
9
9
  ref = $bindable(null),
10
10
  children,
11
11
  child,
12
+ disabled = false,
12
13
  ...restProps
13
14
  }: DialogTriggerProps = $props();
14
15
 
@@ -18,6 +19,7 @@
18
19
  () => ref,
19
20
  (v) => (ref = v)
20
21
  ),
22
+ disabled: box.with(() => Boolean(disabled)),
21
23
  });
22
24
 
23
25
  const mergedProps = $derived(mergeProps(restProps, triggerState.props));
@@ -35,7 +35,9 @@ declare class DialogRootState {
35
35
  readonly "data-state": "open" | "closed";
36
36
  };
37
37
  }
38
- type DialogTriggerStateProps = WithRefProps;
38
+ type DialogTriggerStateProps = WithRefProps & ReadableBoxedValues<{
39
+ disabled: boolean;
40
+ }>;
39
41
  declare class DialogTriggerState {
40
42
  #private;
41
43
  constructor(props: DialogTriggerStateProps, root: DialogRootState);
@@ -45,11 +47,14 @@ declare class DialogTriggerState {
45
47
  readonly "aria-haspopup": "dialog";
46
48
  readonly "aria-expanded": "true" | "false";
47
49
  readonly "aria-controls": string | undefined;
48
- readonly onclick: () => void;
50
+ readonly onpointerdown: (e: PointerEvent) => void;
51
+ readonly onkeydown: (e: KeyboardEvent) => void;
52
+ readonly onpointerup: (e: PointerEvent) => void;
49
53
  };
50
54
  }
51
55
  type DialogCloseStateProps = WithRefProps & ReadableBoxedValues<{
52
56
  variant: "action" | "cancel" | "close";
57
+ disabled: boolean;
53
58
  }>;
54
59
  declare class DialogCloseState {
55
60
  #private;
@@ -57,7 +62,9 @@ declare class DialogCloseState {
57
62
  props: {
58
63
  readonly "data-state": "open" | "closed";
59
64
  readonly id: string;
60
- readonly onclick: () => void;
65
+ readonly onpointerdown: (e: PointerEvent) => void;
66
+ readonly onpointerup: (e: PointerEvent) => void;
67
+ readonly onkeydown: (e: KeyboardEvent) => void;
61
68
  };
62
69
  }
63
70
  type DialogActionStateProps = WithRefProps;
@@ -120,14 +127,18 @@ declare class DialogOverlayState {
120
127
  readonly id: string;
121
128
  };
122
129
  }
123
- type AlertDialogCancelStateProps = WithRefProps;
130
+ type AlertDialogCancelStateProps = WithRefProps & ReadableBoxedValues<{
131
+ disabled: boolean;
132
+ }>;
124
133
  declare class AlertDialogCancelState {
125
134
  #private;
126
135
  constructor(props: AlertDialogCancelStateProps, root: DialogRootState);
127
136
  props: {
128
137
  readonly "data-state": "open" | "closed";
129
138
  readonly id: string;
130
- readonly onclick: () => void;
139
+ readonly onpointerdown: (e: PointerEvent) => void;
140
+ readonly onpointerup: (e: PointerEvent) => void;
141
+ readonly onkeydown: (e: KeyboardEvent) => void;
131
142
  };
132
143
  }
133
144
  export declare function useDialogRoot(props: DialogRootStateProps): DialogRootState;
@@ -1,6 +1,7 @@
1
1
  import { useRefById } from "svelte-toolbelt";
2
2
  import { getAriaExpanded, getDataOpenClosed } from "../../internal/attrs.js";
3
3
  import { createContext } from "../../internal/create-context.js";
4
+ import { kbd } from "../../internal/kbd.js";
4
5
  function createAttrs(variant) {
5
6
  return {
6
7
  content: `data-${variant}-content`,
@@ -48,10 +49,12 @@ class DialogTriggerState {
48
49
  #id;
49
50
  #ref;
50
51
  #root;
52
+ #disabled;
51
53
  constructor(props, root) {
52
54
  this.#id = props.id;
53
55
  this.#root = root;
54
56
  this.#ref = props.ref;
57
+ this.#disabled = props.disabled;
55
58
  useRefById({
56
59
  id: this.#id,
57
60
  ref: this.#ref,
@@ -61,16 +64,43 @@ class DialogTriggerState {
61
64
  },
62
65
  });
63
66
  }
64
- #onclick = () => {
67
+ #onpointerdown = (e) => {
68
+ if (this.#disabled.current)
69
+ return;
70
+ if (e.pointerType === "touch")
71
+ return e.preventDefault();
72
+ if (e.button > 0)
73
+ return;
74
+ // by default, it will attempt to focus this trigger on pointerdown
75
+ // since this also opens the dialog we want to prevent that behavior
76
+ e.preventDefault();
65
77
  this.#root.handleOpen();
66
78
  };
79
+ #onpointerup = (e) => {
80
+ if (this.#disabled.current)
81
+ return;
82
+ if (e.pointerType === "touch") {
83
+ e.preventDefault();
84
+ this.#root.handleOpen();
85
+ }
86
+ };
87
+ #onkeydown = (e) => {
88
+ if (this.#disabled.current)
89
+ return;
90
+ if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
91
+ e.preventDefault();
92
+ this.#root.handleOpen();
93
+ }
94
+ };
67
95
  props = $derived.by(() => ({
68
96
  id: this.#id.current,
69
97
  "aria-haspopup": "dialog",
70
98
  "aria-expanded": getAriaExpanded(this.#root.open.current),
71
99
  "aria-controls": this.#root.contentId,
72
100
  [this.#root.attrs.trigger]: "",
73
- onclick: this.#onclick,
101
+ onpointerdown: this.#onpointerdown,
102
+ onkeydown: this.#onkeydown,
103
+ onpointerup: this.#onpointerup,
74
104
  ...this.#root.sharedProps,
75
105
  }));
76
106
  }
@@ -79,25 +109,51 @@ class DialogCloseState {
79
109
  #ref;
80
110
  #root;
81
111
  #variant;
112
+ #disabled;
82
113
  #attr = $derived.by(() => this.#root.attrs[this.#variant.current]);
83
114
  constructor(props, root) {
84
115
  this.#root = root;
85
116
  this.#ref = props.ref;
86
117
  this.#id = props.id;
87
118
  this.#variant = props.variant;
119
+ this.#disabled = props.disabled;
88
120
  useRefById({
89
121
  id: this.#id,
90
122
  ref: this.#ref,
91
123
  deps: () => this.#root.open.current,
92
124
  });
93
125
  }
94
- #onclick = () => {
126
+ #onpointerdown = (e) => {
127
+ if (this.#disabled.current)
128
+ return;
129
+ if (e.pointerType === "touch")
130
+ return e.preventDefault();
131
+ if (e.button > 0)
132
+ return;
95
133
  this.#root.handleClose();
96
134
  };
135
+ #onpointerup = (e) => {
136
+ if (this.#disabled.current)
137
+ return;
138
+ if (e.pointerType === "touch") {
139
+ e.preventDefault();
140
+ this.#root.handleClose();
141
+ }
142
+ };
143
+ #onkeydown = (e) => {
144
+ if (this.#disabled.current)
145
+ return;
146
+ if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
147
+ e.preventDefault();
148
+ this.#root.handleClose();
149
+ }
150
+ };
97
151
  props = $derived.by(() => ({
98
152
  id: this.#id.current,
99
153
  [this.#attr]: "",
100
- onclick: this.#onclick,
154
+ onpointerdown: this.#onpointerdown,
155
+ onpointerup: this.#onpointerup,
156
+ onkeydown: this.#onkeydown,
101
157
  ...this.#root.sharedProps,
102
158
  }));
103
159
  }
@@ -226,10 +282,12 @@ class AlertDialogCancelState {
226
282
  #id;
227
283
  #ref;
228
284
  #root;
285
+ #disabled;
229
286
  constructor(props, root) {
230
287
  this.#id = props.id;
231
288
  this.#ref = props.ref;
232
289
  this.#root = root;
290
+ this.#disabled = props.disabled;
233
291
  useRefById({
234
292
  id: this.#id,
235
293
  ref: this.#ref,
@@ -239,13 +297,37 @@ class AlertDialogCancelState {
239
297
  },
240
298
  });
241
299
  }
242
- #onclick = () => {
300
+ #onpointerdown = (e) => {
301
+ if (this.#disabled.current)
302
+ return;
303
+ if (e.pointerType === "touch")
304
+ return e.preventDefault();
305
+ if (e.button > 0)
306
+ return;
243
307
  this.#root.handleClose();
244
308
  };
309
+ #onkeydown = (e) => {
310
+ if (this.#disabled.current)
311
+ return;
312
+ if (e.key === kbd.SPACE || e.key === kbd.ENTER) {
313
+ e.preventDefault();
314
+ this.#root.handleClose();
315
+ }
316
+ };
317
+ #onpointerup = (e) => {
318
+ if (this.#disabled.current)
319
+ return;
320
+ if (e.pointerType === "touch") {
321
+ e.preventDefault();
322
+ this.#root.handleClose();
323
+ }
324
+ };
245
325
  props = $derived.by(() => ({
246
326
  id: this.#id.current,
247
327
  [this.#root.attrs.cancel]: "",
248
- onclick: this.#onclick,
328
+ onpointerdown: this.#onpointerdown,
329
+ onpointerup: this.#onpointerup,
330
+ onkeydown: this.#onkeydown,
249
331
  ...this.#root.sharedProps,
250
332
  }));
251
333
  }
@@ -38,9 +38,6 @@
38
38
  isStatic={true}
39
39
  {...mergedProps}
40
40
  present={contentState.parentMenu.open.current || forceMount}
41
- onInteractOutsideStart={(e) => {
42
- contentState.handleInteractOutside(e);
43
- }}
44
41
  onInteractOutside={(e) => {
45
42
  contentState.handleInteractOutside(e);
46
43
  if (e.defaultPrevented) return;
@@ -38,9 +38,6 @@
38
38
  <PopperLayer
39
39
  {...mergedProps}
40
40
  present={contentState.parentMenu.open.current || forceMount}
41
- onInteractOutsideStart={(e) => {
42
- contentState.handleInteractOutside(e);
43
- }}
44
41
  onInteractOutside={(e) => {
45
42
  contentState.handleInteractOutside(e);
46
43
  if (e.defaultPrevented) return;
@@ -5,8 +5,6 @@
5
5
  import { useId } from "../../../internal/use-id.js";
6
6
  import { noop } from "../../../internal/noop.js";
7
7
  import PopperLayer from "../../utilities/popper-layer/popper-layer.svelte";
8
- import { isElement } from "../../../internal/is.js";
9
- import type { InteractOutsideEvent } from "../../utilities/dismissible-layer/types.js";
10
8
  import Mounted from "../../utilities/mounted.svelte";
11
9
 
12
10
  let {
@@ -33,20 +31,8 @@
33
31
  isMounted: box.with(() => isMounted),
34
32
  });
35
33
 
36
- function handleInteractOutsideStart(e: InteractOutsideEvent) {
37
- if (!isElement(e.target)) return;
38
- if (e.target.id === contentState.parentMenu.triggerNode?.id) {
39
- e.preventDefault();
40
- return;
41
- }
42
- if (e.target.closest(`#${contentState.parentMenu.triggerNode?.id}`)) {
43
- e.preventDefault();
44
- }
45
- }
46
-
47
34
  const mergedProps = $derived(
48
35
  mergeProps(restProps, contentState.props, {
49
- onInteractOutsideStart: handleInteractOutsideStart,
50
36
  style: { outline: "none" },
51
37
  })
52
38
  );
@@ -5,8 +5,6 @@
5
5
  import { useId } from "../../../internal/use-id.js";
6
6
  import { noop } from "../../../internal/noop.js";
7
7
  import PopperLayer from "../../utilities/popper-layer/popper-layer.svelte";
8
- import { isElement } from "../../../internal/is.js";
9
- import type { InteractOutsideEvent } from "../../utilities/dismissible-layer/types.js";
10
8
  import Mounted from "../../utilities/mounted.svelte";
11
9
  import { getFloatingContentCSSVars } from "../../../internal/floating-svelte/floating-utils.svelte.js";
12
10
 
@@ -34,20 +32,8 @@
34
32
  isMounted: box.with(() => isMounted),
35
33
  });
36
34
 
37
- function handleInteractOutsideStart(e: InteractOutsideEvent) {
38
- if (!isElement(e.target)) return;
39
- if (e.target.id === contentState.parentMenu.triggerNode?.id) {
40
- e.preventDefault();
41
- return;
42
- }
43
- if (e.target.closest(`#${contentState.parentMenu.triggerNode?.id}`)) {
44
- e.preventDefault();
45
- }
46
- }
47
-
48
35
  const mergedProps = $derived(
49
36
  mergeProps(restProps, contentState.props, {
50
- onInteractOutsideStart: handleInteractOutsideStart,
51
37
  style: { outline: "none" },
52
38
  })
53
39
  );
@@ -1,5 +1,4 @@
1
1
  import { IsFocusWithin } from "runed";
2
- import type { InteractOutsideEvent } from "../utilities/dismissible-layer/types.js";
3
2
  import { type GraceIntent } from "./utils.js";
4
3
  import type { ReadableBoxedValues, WritableBoxedValues } from "../../internal/box.svelte.js";
5
4
  import type { AnyFn, WithRefProps } from "../../internal/types.js";
@@ -60,7 +59,7 @@ declare class MenuContentState {
60
59
  onItemLeave: (e: PointerEvent) => void;
61
60
  onTriggerLeave: (e: PointerEvent) => boolean;
62
61
  onOpenAutoFocus: (e: Event) => void;
63
- handleInteractOutside: (e: InteractOutsideEvent) => void;
62
+ handleInteractOutside: (e: PointerEvent) => void;
64
63
  snippetProps: {
65
64
  open: boolean;
66
65
  };
@@ -305,6 +304,7 @@ declare class DropdownMenuTriggerState {
305
304
  readonly "data-disabled": "" | undefined;
306
305
  readonly "data-state": "open" | "closed";
307
306
  readonly onpointerdown: (e: PointerEvent) => void;
307
+ readonly onpointerup: (e: PointerEvent) => void;
308
308
  readonly onkeydown: (e: KeyboardEvent) => void;
309
309
  };
310
310
  }
@@ -678,7 +678,11 @@ class DropdownMenuTriggerState {
678
678
  });
679
679
  }
680
680
  #onpointerdown = (e) => {
681
- if (!this.#disabled.current && e.button === 0 && e.ctrlKey === false) {
681
+ if (this.#disabled.current)
682
+ return;
683
+ if (e.pointerType === "touch")
684
+ return e.preventDefault();
685
+ if (e.button === 0 && e.ctrlKey === false) {
682
686
  this.#parentMenu.toggleOpen();
683
687
  // prevent trigger focusing when opening to allow
684
688
  // the content to be given focus without competition
@@ -686,6 +690,14 @@ class DropdownMenuTriggerState {
686
690
  e.preventDefault();
687
691
  }
688
692
  };
693
+ #onpointerup = (e) => {
694
+ if (this.#disabled.current)
695
+ return;
696
+ if (e.pointerType === "touch") {
697
+ e.preventDefault();
698
+ this.#parentMenu.toggleOpen();
699
+ }
700
+ };
689
701
  #onkeydown = (e) => {
690
702
  if (this.#disabled.current)
691
703
  return;
@@ -715,6 +727,7 @@ class DropdownMenuTriggerState {
715
727
  [this.#parentMenu.root.getAttr("trigger")]: "",
716
728
  //
717
729
  onpointerdown: this.#onpointerdown,
730
+ onpointerup: this.#onpointerup,
718
731
  onkeydown: this.#onkeydown,
719
732
  }));
720
733
  }