ropav 0.1.5 → 0.1.7

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 (235) hide show
  1. package/README.md +10 -0
  2. package/dist/accordion.css +17 -17
  3. package/dist/accordion.js +125 -154
  4. package/dist/alert.css +18 -18
  5. package/dist/alert.js +20 -20
  6. package/dist/aria.js +14 -0
  7. package/dist/aspect-ratio.js +1 -1
  8. package/dist/attributes.js +9 -0
  9. package/dist/attributes2.js +29 -0
  10. package/dist/avatar.css +19 -19
  11. package/dist/avatar.js +10 -18
  12. package/dist/badge.css +16 -16
  13. package/dist/badge.js +6 -6
  14. package/dist/base.css +18 -13
  15. package/dist/button-group.js +1 -1
  16. package/dist/button-link.js +13 -14
  17. package/dist/button.css +32 -27
  18. package/dist/button.js +13 -14
  19. package/dist/{useButtonColor.js → button.scss_vue_type_style_index_0_src_true_lang.js} +3 -2
  20. package/dist/card.js +1 -1
  21. package/dist/checkbox.css +32 -32
  22. package/dist/checkbox.js +23 -29
  23. package/dist/collapse.css +6 -6
  24. package/dist/collapse.js +8 -7
  25. package/dist/collectionNavigation.js +2 -99
  26. package/dist/color-input.css +19 -19
  27. package/dist/color-input.js +15 -14
  28. package/dist/color-picker.css +40 -40
  29. package/dist/color-picker.js +222 -202
  30. package/dist/color-picker2.js +1 -9
  31. package/dist/color-swatch.css +4 -4
  32. package/dist/color-swatch.js +7 -9
  33. package/dist/color.js +5 -10
  34. package/dist/componentColors.js +165 -84
  35. package/dist/componentRef.js +26 -0
  36. package/dist/components/accordion/accordionContext.d.ts +3 -0
  37. package/dist/components/accordion/accordionModel.d.ts +14 -0
  38. package/dist/components/accordion/index.d.ts +2 -1
  39. package/dist/components/accordion/index.js +1 -1
  40. package/dist/components/accordion/types.d.ts +1 -2
  41. package/dist/components/accordion/useAccordion.d.ts +2 -2
  42. package/dist/components/accordion/useAccordionItem.d.ts +2 -0
  43. package/dist/components/checkbox/useCheckbox.d.ts +2 -3
  44. package/dist/components/color-input/color-input.d.ts +2 -2
  45. package/dist/components/color-input/useColorInput.d.ts +1 -1
  46. package/dist/components/color-picker/index.js +2 -2
  47. package/dist/components/color-picker/types.d.ts +2 -2
  48. package/dist/components/color-picker/useColorPickerSaturation.d.ts +22 -0
  49. package/dist/components/color-picker/useColorPickerSlider.d.ts +20 -0
  50. package/dist/components/color-picker/useColorPickerValue.d.ts +1 -1
  51. package/dist/components/dialog/{dialog-core.d.ts → dialogContext.d.ts} +2 -6
  52. package/dist/components/dialog/useDialogContentInteractions.d.ts +18 -0
  53. package/dist/components/dialog/useDialogRootState.d.ts +19 -0
  54. package/dist/components/dropdown-menu/dropdown-menu-content.d.ts +2 -2
  55. package/dist/components/dropdown-menu/dropdown-menu-item-row.d.ts +1 -1
  56. package/dist/components/dropdown-menu/dropdown-menu-items.d.ts +1 -1
  57. package/dist/components/dropdown-menu/dropdown-menu-model.d.ts +8 -0
  58. package/dist/components/dropdown-menu/dropdown-menu-root.d.ts +2 -2
  59. package/dist/components/dropdown-menu/dropdown-menu-sub-content.d.ts +2 -2
  60. package/dist/components/dropdown-menu/dropdown-menu-sub-trigger.d.ts +1 -1
  61. package/dist/components/dropdown-menu/dropdown-menu-sub.d.ts +1 -1
  62. package/dist/components/dropdown-menu/dropdown-menu.d.ts +3 -3
  63. package/dist/components/dropdown-menu/{dropdown-menu-primitive-core.d.ts → dropdownMenuContext.d.ts} +17 -19
  64. package/dist/components/dropdown-menu/dropdownMenuDataModel.d.ts +20 -0
  65. package/dist/components/dropdown-menu/dropdownMenuInteraction.d.ts +3 -0
  66. package/dist/components/dropdown-menu/dropdownMenuInteractionNavigation.d.ts +24 -0
  67. package/dist/components/dropdown-menu/dropdownMenuInteractionRegistry.d.ts +42 -0
  68. package/dist/components/dropdown-menu/dropdownMenuInteractionTypes.d.ts +85 -0
  69. package/dist/components/dropdown-menu/useDropdownMenu.d.ts +11 -11
  70. package/dist/components/dropdown-menu/useDropdownMenuContextTrigger.d.ts +9 -0
  71. package/dist/components/dropdown-menu/useDropdownMenuDataController.d.ts +34 -0
  72. package/dist/components/dropdown-menu/useDropdownMenuDataRegistration.d.ts +22 -0
  73. package/dist/components/dropdown-menu/useDropdownMenuHoverIntent.d.ts +6 -5
  74. package/dist/components/dropdown-menu/useDropdownMenuInteractionDismissal.d.ts +17 -0
  75. package/dist/components/dropdown-menu/useDropdownMenuInteractionKeyboard.d.ts +19 -0
  76. package/dist/components/dropdown-menu/useDropdownMenuPortalPosition.d.ts +0 -1
  77. package/dist/components/dropdown-menu/useDropdownMenuPresentation.d.ts +29 -0
  78. package/dist/components/dropdown-menu/useDropdownMenuPrimitiveContent.d.ts +2 -2
  79. package/dist/components/dropdown-menu/useDropdownMenuRenderItems.d.ts +1 -1
  80. package/dist/components/dropdown-menu/useDropdownMenuTargetBindings.d.ts +19 -0
  81. package/dist/components/floating/hoverDisclosureInteractionModel.d.ts +42 -0
  82. package/dist/components/floating/index.js +1 -1
  83. package/dist/components/floating/useFloatingPosition.d.ts +0 -1
  84. package/dist/components/floating/useHoverDisclosureInteractions.d.ts +32 -0
  85. package/dist/components/floating/useHoverDisclosureOpenState.d.ts +25 -0
  86. package/dist/components/floating/useHoverDisclosureTargetBinding.d.ts +27 -0
  87. package/dist/components/input/useInput.d.ts +1 -1
  88. package/dist/components/number-input/numberInputModel.d.ts +15 -0
  89. package/dist/components/number-input/useNumberInput.d.ts +4 -18
  90. package/dist/components/pagination/index.d.ts +1 -1
  91. package/dist/components/pagination/index.js +1 -1
  92. package/dist/components/pagination/paginationModel.d.ts +4 -0
  93. package/dist/components/pagination/types.d.ts +15 -0
  94. package/dist/components/pagination/usePagination.d.ts +3 -19
  95. package/dist/components/popover/usePopoverBindings.d.ts +16 -0
  96. package/dist/components/progress/progressModel.d.ts +8 -0
  97. package/dist/components/progress/useProgress.d.ts +1 -7
  98. package/dist/components/radio/useRadio.d.ts +3 -4
  99. package/dist/components/scroll-area/scroll-area.d.ts +1 -1
  100. package/dist/components/scroll-area/scrollAreaModel.d.ts +64 -0
  101. package/dist/components/scroll-area/types.d.ts +4 -0
  102. package/dist/components/scroll-area/useScrollArea.d.ts +70 -52
  103. package/dist/components/scroll-area/{useScrollAreaPresentation.d.ts → useScrollAreaAttributes.d.ts} +37 -14
  104. package/dist/components/scroll-area/useScrollAreaDerivedState.d.ts +8 -0
  105. package/dist/components/scroll-area/useScrollAreaMeasurement.d.ts +35 -0
  106. package/dist/components/scroll-area/useScrollAreaPointer.d.ts +10 -8
  107. package/dist/components/scroll-area/useScrollAreaVisibility.d.ts +15 -20
  108. package/dist/components/select/useSelect.d.ts +2 -1
  109. package/dist/components/select/useSelectNativeValue.d.ts +1 -7
  110. package/dist/components/slider/rangeSliderModel.d.ts +41 -0
  111. package/dist/components/slider/{sliderCore.d.ts → sliderModel.d.ts} +2 -2
  112. package/dist/components/slider/useRangeSlider.d.ts +9 -17
  113. package/dist/components/slider/useRangeSliderValueState.d.ts +40 -0
  114. package/dist/components/slider/useSlider.d.ts +8 -14
  115. package/dist/components/slider/useSliderPointerPreview.d.ts +22 -0
  116. package/dist/components/slider/useSliderTooltipState.d.ts +40 -0
  117. package/dist/components/slider/useSliderValueState.d.ts +33 -0
  118. package/dist/components/switch/useSwitch.d.ts +1 -1
  119. package/dist/components/teleport-provider/index.js +1 -1
  120. package/dist/components/teleport-provider/useTeleportTarget.d.ts +1 -2
  121. package/dist/components/textarea/useTextarea.d.ts +1 -1
  122. package/dist/components/toast/useToastLifecycle.d.ts +19 -0
  123. package/dist/components/tooltip/useTooltip.d.ts +1 -2
  124. package/dist/composables/index.d.ts +2 -0
  125. package/dist/composables/index.js +2 -0
  126. package/dist/composables/useControllableValue.d.ts +14 -0
  127. package/dist/css.js +12 -0
  128. package/dist/dialog.js +227 -216
  129. package/dist/dropdown-menu.js +1493 -1118
  130. package/dist/events.js +59 -0
  131. package/dist/field.css +8 -8
  132. package/dist/field.js +13 -25
  133. package/dist/floating.js +553 -312
  134. package/dist/floatingOffset.js +11 -0
  135. package/dist/focus-trap.js +7 -5
  136. package/dist/focusNavigation.js +37 -0
  137. package/dist/icon-button.css +12 -173
  138. package/dist/icon-button.js +8 -7
  139. package/dist/index.d.ts +5 -4
  140. package/dist/index.js +13 -11
  141. package/dist/input.css +24 -24
  142. package/dist/input.js +20 -32
  143. package/dist/interactive.js +18 -0
  144. package/dist/modal.js +11 -7
  145. package/dist/number-input.css +18 -18
  146. package/dist/number-input.js +15 -13
  147. package/dist/number.js +22 -0
  148. package/dist/overlay.js +3 -2
  149. package/dist/pagination.css +28 -28
  150. package/dist/pagination.js +67 -65
  151. package/dist/pointer.js +9 -0
  152. package/dist/popover.css +34 -34
  153. package/dist/popover.js +93 -91
  154. package/dist/progress.css +29 -21
  155. package/dist/progress.js +25 -21
  156. package/dist/query.js +40 -0
  157. package/dist/radio.css +18 -18
  158. package/dist/radio.js +31 -37
  159. package/dist/rafScheduler.js +34 -0
  160. package/dist/scroll-area.css +35 -26
  161. package/dist/scroll-area.js +553 -500
  162. package/dist/select.css +45 -45
  163. package/dist/select.js +70 -56
  164. package/dist/slider.css +191 -191
  165. package/dist/slider.js +695 -628
  166. package/dist/styles-api.d.ts +0 -1
  167. package/dist/styles-api.js +1 -4
  168. package/dist/switch.css +17 -17
  169. package/dist/switch.js +15 -16
  170. package/dist/tabs.css +46 -46
  171. package/dist/tabs.js +51 -68
  172. package/dist/text.js +17 -0
  173. package/dist/textarea.css +26 -26
  174. package/dist/textarea.js +22 -33
  175. package/dist/toast.css +26 -26
  176. package/dist/toast.js +157 -129
  177. package/dist/tooltip.css +12 -12
  178. package/dist/tooltip.js +46 -47
  179. package/dist/useCollapse.js +19 -17
  180. package/dist/useControlState.js +4 -7
  181. package/dist/useControllableValue.js +8 -2
  182. package/dist/useFloatingPosition.js +122 -1128
  183. package/dist/useFormControl.js +3 -2
  184. package/dist/useOverlayZIndex.js +2 -6
  185. package/dist/useRequiredInject.js +1 -1
  186. package/dist/useTeleportTarget.js +3 -14
  187. package/dist/useTypeahead.js +94 -0
  188. package/dist/utils/aria.d.ts +3 -0
  189. package/dist/utils/attributes.d.ts +2 -0
  190. package/dist/utils/bem.d.ts +3 -0
  191. package/dist/utils/collectionNavigation.d.ts +2 -0
  192. package/dist/utils/color.d.ts +7 -0
  193. package/dist/{components/color-picker/color-picker-utils.d.ts → utils/colorPicker.d.ts} +5 -4
  194. package/dist/utils/componentColors.d.ts +7 -0
  195. package/dist/utils/css.d.ts +3 -0
  196. package/dist/utils/dom/ancestry.d.ts +7 -0
  197. package/dist/utils/dom/attributes.d.ts +16 -0
  198. package/dist/utils/dom/componentRef.d.ts +4 -0
  199. package/dist/utils/dom/events.d.ts +5 -0
  200. package/dist/utils/dom/focusNavigation.d.ts +7 -0
  201. package/dist/utils/dom/interactive.d.ts +2 -0
  202. package/dist/utils/dom/pointer.d.ts +2 -0
  203. package/dist/utils/dom/query.d.ts +4 -0
  204. package/dist/utils/dom/scroll.d.ts +23 -0
  205. package/dist/utils/floatingOffset.d.ts +10 -0
  206. package/dist/utils/geometry.d.ts +14 -0
  207. package/dist/utils/indexPath.d.ts +6 -0
  208. package/dist/utils/number.d.ts +4 -0
  209. package/dist/utils/rafScheduler.d.ts +4 -0
  210. package/dist/utils/text.d.ts +2 -0
  211. package/docs/code-architecture.md +118 -0
  212. package/docs/public-composables-api.md +52 -0
  213. package/docs/public-floating-api.md +6 -5
  214. package/package.json +54 -46
  215. package/src/styles/_mixins.scss +4 -0
  216. package/src/styles/_tokens.scss +12 -12
  217. package/src/styles/_variables.scss +1 -1
  218. package/dist/button-link.css +0 -167
  219. package/dist/components/dropdown-menu/dropdown-menu-outside.d.ts +0 -3
  220. package/dist/components/dropdown-menu/dropdown-menu-utils.d.ts +0 -21
  221. package/dist/components/dropdown-menu/useDropdownMenuDisclosure.d.ts +0 -37
  222. package/dist/components/dropdown-menu/useDropdownMenuDom.d.ts +0 -29
  223. package/dist/components/dropdown-menu/useDropdownMenuInteractions.d.ts +0 -52
  224. package/dist/components/dropdown-menu/useDropdownMenuNavigation.d.ts +0 -17
  225. package/dist/components/dropdown-menu/useDropdownSubmenus.d.ts +0 -18
  226. package/dist/components/scroll-area/scrollAreaCore.d.ts +0 -25
  227. package/dist/components/scroll-area/useScrollAreaControls.d.ts +0 -25
  228. package/dist/components/scroll-area/useScrollAreaMetrics.d.ts +0 -35
  229. /package/dist/components/alert/{useAlertColor.d.ts → alertColor.d.ts} +0 -0
  230. /package/dist/components/avatar/{useAvatarColor.d.ts → avatarColor.d.ts} +0 -0
  231. /package/dist/components/badge/{useBadgeColor.d.ts → badgeColor.d.ts} +0 -0
  232. /package/dist/components/button/{useButtonColor.d.ts → buttonColor.d.ts} +0 -0
  233. /package/dist/components/color-swatch/{useColorSwatchColor.d.ts → colorSwatchColor.d.ts} +0 -0
  234. /package/dist/components/toast/{useToastColor.d.ts → toastColor.d.ts} +0 -0
  235. /package/dist/{composables → internal/composables}/useOverlayLayer.d.ts +0 -0
@@ -1,40 +1,49 @@
1
1
  import './dropdown-menu.css';
2
+ import { t as useControllableValue } from "./useControllableValue.js";
3
+ import { t as useStylesApi } from "./styles-api.js";
4
+ import { n as toPresenceAttribute, t as toOptionalAttribute } from "./attributes.js";
2
5
  import { t as bem } from "./bem.js";
3
- import { n as useStylesApi, t as presence } from "./styles-api.js";
6
+ import { t as isElement } from "./query.js";
4
7
  import { t as useRequiredInject } from "./useRequiredInject.js";
8
+ import { t as clamp } from "./number.js";
9
+ import { a as snapshotAttributes, i as restoreAttributes } from "./attributes2.js";
10
+ import { t as resolveHTMLElementRef } from "./componentRef.js";
11
+ import { n as isEventWithinElement, r as isEventWithinTargets, t as createCancelableCustomEvent } from "./events.js";
5
12
  import { n as useOverlayZIndex, r as useOverlayLayer } from "./useOverlayZIndex.js";
6
- import { n as useFloatingPosition, r as useFloatingTarget, t as isElementReference } from "./useFloatingPosition.js";
7
- import { n as useTeleportPositioningKey, r as useTeleportTarget, t as provideTeleportTarget } from "./useTeleportTarget.js";
8
- import { n as getNextEnabledIndex, r as useTypeahead, t as getEnabledIndexes } from "./collectionNavigation.js";
13
+ import { n as useFloatingTarget, t as useFloatingPosition } from "./useFloatingPosition.js";
14
+ import { n as useTeleportTarget, t as provideTeleportTarget } from "./useTeleportTarget.js";
15
+ import { t as useTypeahead } from "./useTypeahead.js";
9
16
  import { t as chevron_right_default } from "./chevron-right.js";
10
- import { VaporTeleport, VaporTransition, child, computed, createComponent, createComponentWithFallback, createDynamicComponent, createFor, createIf, createSlot, createTemplateRefSetter, defineVaporComponent, extend, isRef, mergeProps, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, ref, renderEffect, resolveComponent, setAttr, setDynamicProps, setInsertionState, setStaticTemplateRef, setStyle, setTemplateRefBinding, setText, shallowRef, template, toDisplayString, txt, unref, useAttrs, useId, watch } from "vue";
11
- //#region src/components/dropdown-menu/dropdown-menu-primitive-core.ts
12
- var rootKey = Symbol("dropdown-menu-root");
13
- var menuKey = Symbol("dropdown-menu-content");
14
- var subKey = Symbol("dropdown-menu-sub");
15
- var radioGroupKey = Symbol("dropdown-menu-radio-group");
16
- var checkedKey = Symbol("dropdown-menu-checked");
17
- function optionalAttr(value) {
18
- return value || void 0;
17
+ import { VaporTeleport, VaporTransition, child, computed, createComponent, createComponentWithFallback, createDynamicComponent, createFor, createIf, createSlot, createTemplateRefSetter, defineVaporComponent, extend, isRef, mergeProps, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, ref, renderEffect, resolveComponent, setAttr, setDynamicProps, setInsertionState, setStyle, setTemplateRefBinding, setText, shallowRef, template, toDisplayString, txt, unref, useAttrs, useId, watch } from "vue";
18
+ //#region src/utils/geometry.ts
19
+ function getClientPoint(event) {
20
+ return {
21
+ x: event.clientX,
22
+ y: event.clientY
23
+ };
19
24
  }
20
- function getFocusTarget(options) {
21
- if (typeof options === "string") return options;
22
- return options?.focus ?? "first";
25
+ function hasMeasuredRect(rect) {
26
+ return rect.width > 0 || rect.height > 0;
27
+ }
28
+ function isPointInRect(point, rect, padding = 0) {
29
+ return point.x >= rect.left - padding && point.x <= rect.right + padding && point.y >= rect.top - padding && point.y <= rect.bottom + padding;
23
30
  }
24
- function toHTMLElement(value, fallbackId) {
25
- if (typeof HTMLElement !== "undefined" && value instanceof HTMLElement) return value;
26
- if (typeof HTMLElement === "undefined") return null;
27
- const vdomElement = value?.$el;
28
- if (vdomElement instanceof HTMLElement) return vdomElement;
29
- const vaporBlock = value?.block;
30
- if (vaporBlock instanceof HTMLElement) return vaporBlock;
31
- return fallbackId && typeof document !== "undefined" ? document.getElementById(fallbackId) : null;
31
+ function getTriangleArea(a, b, c) {
32
+ return Math.abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2);
33
+ }
34
+ function isPointInTriangle(point, triangle, tolerance = .5) {
35
+ const [a, b, c] = triangle;
36
+ const area = getTriangleArea(a, b, c);
37
+ const area1 = getTriangleArea(point, b, c);
38
+ const area2 = getTriangleArea(a, point, c);
39
+ const area3 = getTriangleArea(a, b, point);
40
+ return Math.abs(area - (area1 + area2 + area3)) < tolerance;
32
41
  }
33
- function resolveHTMLElementRef(value, fallbackId, resolve) {
34
- const element = toHTMLElement(value, fallbackId);
35
- resolve(element);
36
- if (element || !value) return;
37
- nextTick(() => resolve(toHTMLElement(value, fallbackId)));
42
+ function clampPointToRect(point, rect) {
43
+ return {
44
+ x: clamp(point.x, rect.left, rect.right),
45
+ y: clamp(point.y, rect.top, rect.bottom)
46
+ };
38
47
  }
39
48
  function createPointRect(point) {
40
49
  return {
@@ -49,202 +58,72 @@ function createPointRect(point) {
49
58
  toJSON: () => ({})
50
59
  };
51
60
  }
61
+ //#endregion
62
+ //#region src/components/dropdown-menu/dropdownMenuContext.ts
63
+ var rootKey = Symbol("dropdown-menu-root");
64
+ var menuKey = Symbol("dropdown-menu-content");
65
+ var subKey = Symbol("dropdown-menu-sub");
66
+ var radioGroupKey = Symbol("dropdown-menu-radio-group");
67
+ var checkedKey = Symbol("dropdown-menu-checked");
52
68
  function createVirtualAnchor(point) {
53
69
  return { getBoundingClientRect: () => createPointRect(point) };
54
70
  }
55
- function createCustomEvent(type, detail, originalEvent) {
56
- const target = originalEvent.target;
57
- return new (((target instanceof Node ? target.ownerDocument?.defaultView : null)?.CustomEvent) ?? CustomEvent)(type, {
58
- bubbles: false,
59
- cancelable: true,
60
- detail
61
- });
62
- }
63
71
  function createSelectEvent(originalEvent, value) {
64
- return createCustomEvent("dropdown-menu-select", {
72
+ return createCancelableCustomEvent("dropdown-menu-select", {
65
73
  originalEvent,
66
74
  value
67
75
  }, originalEvent);
68
76
  }
69
- function createOutsideEvent(originalEvent) {
70
- return createCustomEvent("dropdown-menu-interact-outside", { originalEvent }, originalEvent);
71
- }
72
- function sortItems(items) {
73
- return [...items].sort((left, right) => {
74
- const leftElement = left.element();
75
- const rightElement = right.element();
76
- if (!leftElement || !rightElement || leftElement === rightElement) return 0;
77
- const position = leftElement.compareDocumentPosition(rightElement);
78
- if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
79
- if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
80
- return 0;
81
- });
82
- }
83
- function getPlacementSide(placement) {
84
- return placement.split("-")[0];
85
- }
86
- function getOpenDirection(sub) {
87
- return getPlacementSide(sub.actualPlacement.value) === "left" ? "ArrowLeft" : "ArrowRight";
88
- }
89
- function getCloseDirection(menu) {
90
- return getPlacementSide(menu.actualPlacement.value) === "left" ? "ArrowRight" : "ArrowLeft";
91
- }
92
- function blockDocumentClick(event) {
93
- if (event.cancelable) event.preventDefault();
94
- event.stopPropagation();
95
- document.removeEventListener("click", blockDocumentClick, true);
96
- }
97
- function blockNextDocumentClick() {
98
- if (typeof document === "undefined") return;
99
- document.addEventListener("click", blockDocumentClick, true);
100
- window.setTimeout(() => document.removeEventListener("click", blockDocumentClick, true), 1e3);
101
- }
102
77
  function createMenuContext(options) {
103
- const items = shallowRef([]);
104
- const activeId = ref(null);
105
- const submenus = /* @__PURE__ */ new Set();
106
- function getEnabledItems() {
107
- return sortItems(items.value).filter((item) => !item.disabled());
108
- }
109
- function registerItem(item) {
110
- items.value = [...items.value.filter((current) => current.id !== item.id), item];
111
- if ((options.parentSub?.isOpen.value ?? options.root.isOpen.value) && activeId.value == null && !item.disabled()) {
112
- activeId.value = item.id;
113
- nextTick(() => {
114
- options.element.value?.focus();
115
- item.element()?.scrollIntoView?.({ block: "nearest" });
116
- });
117
- }
118
- }
119
- function unregisterItem(id) {
120
- items.value = items.value.filter((item) => item.id !== id);
121
- if (activeId.value === id) activeId.value = null;
122
- }
123
- function registerSub(sub) {
124
- submenus.add(sub);
125
- }
126
- function unregisterSub(sub) {
127
- submenus.delete(sub);
128
- }
129
- function closeSubmenus(except) {
130
- for (const submenu of submenus) if (submenu !== except) submenu.close(false);
131
- }
132
- function setActive(id) {
133
- const item = items.value.find((current) => current.id === id);
134
- if (!item || item.disabled()) return;
135
- activeId.value = id;
136
- nextTick(() => item.element()?.scrollIntoView?.({ block: "nearest" }));
137
- }
138
- function focus(target = "first") {
139
- const enabledItems = getEnabledItems();
140
- const item = target === "last" ? enabledItems[enabledItems.length - 1] : enabledItems[0];
141
- if (!item) {
142
- activeId.value = null;
143
- options.element.value?.focus();
144
- return false;
145
- }
146
- activeId.value = item.id;
147
- options.element.value?.focus();
148
- item.element()?.scrollIntoView?.({ block: "nearest" });
149
- return true;
150
- }
151
- function focusElement() {
152
- options.element.value?.focus();
153
- }
154
- function move(direction) {
155
- const enabledItems = getEnabledItems();
156
- if (enabledItems.length === 0) return;
157
- const currentIndex = enabledItems.findIndex((item) => item.id === activeId.value);
158
- const item = enabledItems[((currentIndex < 0 ? direction === 1 ? -1 : 0 : currentIndex) + direction + enabledItems.length) % enabledItems.length];
159
- if (item) setActive(item.id);
160
- }
161
- const typeahead = useTypeahead({
162
- items: () => sortItems(items.value),
163
- activeIndex: () => sortItems(items.value).findIndex((item) => item.id === activeId.value),
164
- getKey: (item) => item.id,
165
- getTextValue: (item) => item.textValue(),
166
- isDisabled: (item) => item.disabled(),
167
- onMatch: (item) => setActive(item.id)
78
+ const id = options.parentSub?.menuId ?? options.root.interaction.rootMenuId;
79
+ const interaction = options.root.interaction;
80
+ const activeId = interaction.getActiveId(id);
81
+ const cleanupMenu = interaction.registerMenu({
82
+ id,
83
+ parentItemId: () => options.parentSub?.trigger.value?.id,
84
+ element: () => options.element.value,
85
+ placement: () => options.actualPlacement.value,
86
+ isOpen: () => options.parentSub?.isOpen.value ?? options.root.isOpen.value,
87
+ setOpen: options.parentSub?.setOpen,
88
+ stopKeyPropagation: true,
89
+ onEscape: options.onEscape
168
90
  });
169
- watch(computed(() => options.parentSub?.isOpen.value ?? options.root.isOpen.value), (open) => {
170
- if (!open) typeahead.reset();
171
- }, { flush: "sync" });
172
- function onHorizontalKeydown(event, active) {
173
- if (options.parentSub && event.key === getCloseDirection(context)) {
174
- event.preventDefault();
175
- event.stopPropagation();
176
- options.parentSub.close(true);
177
- return;
178
- }
179
- if (!active?.submenu || event.key !== getOpenDirection(active.submenu)) return;
180
- event.preventDefault();
181
- event.stopPropagation();
182
- active.submenu.open("first");
183
- }
184
- function onKeydown(event) {
185
- if (typeahead.handleKey(event)) {
186
- event.stopPropagation();
187
- return;
188
- }
189
- const active = items.value.find((item) => item.id === activeId.value);
190
- switch (event.key) {
191
- case "ArrowDown":
192
- event.preventDefault();
193
- event.stopPropagation();
194
- move(1);
195
- break;
196
- case "ArrowUp":
197
- event.preventDefault();
198
- event.stopPropagation();
199
- move(-1);
200
- break;
201
- case "Home":
202
- event.preventDefault();
203
- event.stopPropagation();
204
- focus("first");
205
- break;
206
- case "End":
207
- event.preventDefault();
208
- event.stopPropagation();
209
- focus("last");
210
- break;
211
- case "ArrowRight":
212
- case "ArrowLeft":
213
- onHorizontalKeydown(event, active);
214
- break;
215
- case "Enter":
216
- case " ":
217
- event.preventDefault();
218
- event.stopPropagation();
219
- active?.activate(event);
220
- break;
221
- case "Escape":
222
- event.preventDefault();
223
- event.stopPropagation();
224
- options.onEscape(event);
225
- break;
226
- case "Tab":
227
- options.root.close({ returnFocus: false });
228
- break;
229
- }
91
+ function registerItem(item) {
92
+ interaction.registerItem({
93
+ get id() {
94
+ return item.id;
95
+ },
96
+ menuId: id,
97
+ element: item.element,
98
+ textValue: item.textValue,
99
+ disabled: item.disabled,
100
+ submenuId: () => item.submenu?.menuId,
101
+ submenuDirection: () => item.submenu?.actualPlacement.value.startsWith("left") ? "left" : "right",
102
+ select: item.activate,
103
+ closeOnSelect: item.closeOnSelect
104
+ });
230
105
  }
231
106
  const context = {
232
107
  root: options.root,
108
+ id,
233
109
  element: options.element,
234
110
  activeId,
235
111
  actualPlacement: options.actualPlacement,
236
112
  parentSub: options.parentSub,
237
113
  registerItem,
238
- unregisterItem,
239
- registerSub,
240
- unregisterSub,
241
- closeSubmenus,
242
- setActive,
243
- isActive: (id) => activeId.value === id,
244
- focus,
245
- focusElement,
246
- onKeydown
114
+ unregisterItem: interaction.unregisterItem,
115
+ closeSubmenus: (except) => interaction.closeSubmenus(id, except?.menuId),
116
+ setActive: (itemId) => {
117
+ interaction.setActive(itemId);
118
+ },
119
+ isActive: interaction.isActive,
120
+ focus: (target = "first") => interaction.focusMenu(id, target),
121
+ focusElement: () => interaction.focusMenuElement(id),
122
+ activate: interaction.activateItem,
123
+ hover: interaction.hoverItem,
124
+ onKeydown: (event) => interaction.onMenuKeydown(id, event)
247
125
  };
126
+ onBeforeUnmount(cleanupMenu);
248
127
  return context;
249
128
  }
250
129
  function usePrimitiveItem(componentName, props, emitSelect, options) {
@@ -254,12 +133,12 @@ function usePrimitiveItem(componentName, props, emitSelect, options) {
254
133
  const element = ref(null);
255
134
  const isDisabled = computed(() => Boolean(menu.root.disabled.value || props.disabled));
256
135
  const focused = computed(() => menu.isActive(id.value));
257
- function activate(originalEvent) {
258
- if (isDisabled.value) return;
136
+ function emitSelection(originalEvent) {
137
+ if (isDisabled.value) return void 0;
259
138
  const selectEvent = createSelectEvent(originalEvent, props.value);
260
139
  emitSelect(selectEvent);
261
140
  options.afterSelect?.();
262
- if (!selectEvent.defaultPrevented && (props.closeOnSelect ?? options.defaultCloseOnSelect)) menu.root.close({ returnFocus: true });
141
+ return selectEvent;
263
142
  }
264
143
  const registration = {
265
144
  get id() {
@@ -268,27 +147,33 @@ function usePrimitiveItem(componentName, props, emitSelect, options) {
268
147
  element: () => element.value,
269
148
  textValue: () => props.textValue ?? element.value?.textContent?.trim() ?? "",
270
149
  disabled: () => isDisabled.value,
271
- activate
150
+ activate: emitSelection,
151
+ closeOnSelect: () => props.closeOnSelect ?? options.defaultCloseOnSelect
272
152
  };
273
153
  menu.registerItem(registration);
274
154
  watch(id, (nextId, previousId) => {
155
+ const wasActive = menu.activeId.value === previousId;
275
156
  menu.unregisterItem(previousId);
276
157
  menu.registerItem(registration);
277
- if (menu.activeId.value === previousId) menu.activeId.value = nextId;
278
- });
158
+ if (wasActive) menu.setActive(nextId);
159
+ }, { flush: "sync" });
160
+ watch(isDisabled, () => menu.root.interaction.reconcile(menu.id));
279
161
  onBeforeUnmount(() => menu.unregisterItem(id.value));
280
162
  function setElement(value) {
281
163
  resolveHTMLElementRef(value, id.value, (resolved) => {
282
164
  element.value = resolved;
165
+ menu.root.interaction.reconcile(menu.id);
283
166
  });
284
167
  }
168
+ function activate(originalEvent) {
169
+ menu.activate(id.value, originalEvent);
170
+ }
285
171
  function onPointerenter() {
286
172
  if (isDisabled.value) return;
287
- menu.closeSubmenus();
288
- menu.setActive(id.value);
173
+ menu.hover(id.value);
289
174
  }
290
175
  function select() {
291
- activate(new Event("select"));
176
+ menu.activate(id.value, new Event("select"));
292
177
  }
293
178
  return {
294
179
  menu,
@@ -339,14 +224,17 @@ var dropdown_menu_checkbox_item_default = /* @__PURE__ */ defineVaporComponent({
339
224
  const props = __props;
340
225
  const emit = __emit;
341
226
  const attrs = useAttrs();
342
- const uncontrolled = ref(props.defaultValue);
343
- const value = computed(() => props.modelValue ?? uncontrolled.value);
227
+ const controllable = useControllableValue({
228
+ modelValue: () => props.modelValue,
229
+ defaultValue: () => props.defaultValue,
230
+ onChange: (value) => emit("update:modelValue", value)
231
+ });
232
+ const value = controllable.value;
344
233
  const checked = computed(() => value.value === "indeterminate" ? "mixed" : value.value);
345
234
  const state = computed(() => value.value === "indeterminate" ? "indeterminate" : value.value ? "checked" : "unchecked");
346
235
  function toggleChecked() {
347
236
  const nextValue = value.value === "indeterminate" ? true : !value.value;
348
- if (props.modelValue === void 0) uncontrolled.value = nextValue;
349
- emit("update:modelValue", nextValue);
237
+ controllable.setValue(nextValue);
350
238
  }
351
239
  provide(checkedKey, {
352
240
  state,
@@ -365,12 +253,12 @@ var dropdown_menu_checkbox_item_default = /* @__PURE__ */ defineVaporComponent({
365
253
  type: props.as === "button" ? "button" : void 0,
366
254
  role: "menuitemcheckbox",
367
255
  tabindex: -1,
368
- disabled: props.as === "button" ? optionalAttr(isDisabled.value) : void 0,
369
- "aria-disabled": optionalAttr(isDisabled.value),
256
+ disabled: props.as === "button" ? toOptionalAttribute(isDisabled.value) : void 0,
257
+ "aria-disabled": toOptionalAttribute(isDisabled.value),
370
258
  "aria-checked": checked.value,
371
- "data-disabled": optionalAttr(isDisabled.value),
372
- "data-focused": optionalAttr(focused.value),
373
- "data-highlighted": optionalAttr(focused.value),
259
+ "data-disabled": toOptionalAttribute(isDisabled.value),
260
+ "data-focused": toOptionalAttribute(focused.value),
261
+ "data-highlighted": toOptionalAttribute(focused.value),
374
262
  "data-state": state.value,
375
263
  class: bem("rp-dropdown-menu__item", {
376
264
  focused: focused.value,
@@ -392,31 +280,6 @@ var dropdown_menu_checkbox_item_default = /* @__PURE__ */ defineVaporComponent({
392
280
  }
393
281
  });
394
282
  //#endregion
395
- //#region src/components/dropdown-menu/dropdown-menu-outside.ts
396
- function isNode(value) {
397
- return typeof value === "object" && value !== null && "nodeType" in value;
398
- }
399
- function isEventWithinElement(event, element) {
400
- const path = event.composedPath();
401
- if (path.length > 0) return path.some((entry) => entry === element || isNode(entry) && element.contains(entry));
402
- return isNode(event.target) && element.contains(event.target);
403
- }
404
- function isEventWithinTargets(event, targets) {
405
- for (const source of targets) {
406
- const target = isRef(source) ? source.value : source;
407
- if (!target) continue;
408
- if (typeof target === "string") {
409
- if (typeof document === "undefined") continue;
410
- try {
411
- if ([...document.querySelectorAll(target)].some((element) => isEventWithinElement(event, element))) return true;
412
- } catch {}
413
- continue;
414
- }
415
- if (isEventWithinElement(event, target)) return true;
416
- }
417
- return false;
418
- }
419
- //#endregion
420
283
  //#region src/components/dropdown-menu/useDropdownMenuPrimitiveContent.ts
421
284
  function useDropdownMenuPrimitiveContent(props, attrs, events, submenu) {
422
285
  const componentName = submenu ? "RpDropdownMenuSubContent" : "RpDropdownMenuContent";
@@ -430,20 +293,23 @@ function useDropdownMenuPrimitiveContent(props, attrs, events, submenu) {
430
293
  const actualPlacement = ref(props.placement ?? defaultPlacement);
431
294
  const isOpen = computed(() => sub ? sub.isOpen.value : root.isOpen.value);
432
295
  const reference = computed(() => sub ? sub.trigger.value : root.reference.value);
433
- const teleportPositioningKey = useTeleportPositioningKey();
434
296
  const menu = createMenuContext({
435
297
  root,
436
298
  element,
437
299
  actualPlacement,
438
300
  parentSub: sub,
439
301
  onEscape(event) {
440
- const customEvent = createCustomEvent("dropdown-menu-escape-key-down", { originalEvent: event }, event);
302
+ const customEvent = createCancelableCustomEvent("dropdown-menu-escape-key-down", { originalEvent: event }, event);
441
303
  events.escapeKeyDown(customEvent);
442
- if (customEvent.defaultPrevented) return;
443
- if (sub) sub.close(true);
444
- else root.close({ returnFocus: true });
304
+ return !customEvent.defaultPrevented;
445
305
  }
446
306
  });
307
+ const cleanupDismissal = submenu ? void 0 : root.interaction.registerDismissal({
308
+ ignoredTargets: () => props.ignore ?? [],
309
+ pointerDownOutside: events.pointerDownOutside,
310
+ focusOutside: events.focusOutside,
311
+ interactOutside: events.interactOutside
312
+ });
447
313
  const floating = useFloatingPosition({
448
314
  reference,
449
315
  floating: element,
@@ -456,8 +322,7 @@ function useDropdownMenuPrimitiveContent(props, attrs, events, submenu) {
456
322
  flipOptions: () => props.flipOptions,
457
323
  shift: () => props.shift !== false,
458
324
  collisionPadding: () => props.collisionPadding ?? 8,
459
- autoUpdateOptions: () => props.autoUpdateOptions,
460
- restartKey: teleportPositioningKey
325
+ autoUpdateOptions: () => props.autoUpdateOptions
461
326
  });
462
327
  watch(floating.actualPlacement, (value) => {
463
328
  actualPlacement.value = value;
@@ -478,44 +343,9 @@ function useDropdownMenuPrimitiveContent(props, attrs, events, submenu) {
478
343
  else if (resolved) layerBranchCleanup = root.layer.registerBranch(resolved);
479
344
  });
480
345
  }
481
- function emitOutside(type, originalEvent) {
482
- const outsideEvent = createOutsideEvent(originalEvent);
483
- if (type === "pointer") events.pointerDownOutside(outsideEvent);
484
- else events.focusOutside(outsideEvent);
485
- events.interactOutside(outsideEvent);
486
- return outsideEvent;
487
- }
488
- function onDocumentPointer(event) {
489
- if (!root.layer.isTopLayer() || root.isInside(event) || isEventWithinTargets(event, props.ignore ?? [])) return;
490
- const outsideEvent = emitOutside("pointer", event);
491
- if (root.modal.value) {
492
- if (event.cancelable) event.preventDefault();
493
- event.stopPropagation();
494
- if (event.type === "pointerdown") blockNextDocumentClick();
495
- }
496
- if (!outsideEvent.defaultPrevented) root.close({ returnFocus: root.modal.value });
497
- }
498
- function onDocumentFocus(event) {
499
- if (!root.layer.isTopLayer() || root.isInside(event) || isEventWithinTargets(event, props.ignore ?? [])) return;
500
- const outsideEvent = emitOutside("focus", event);
501
- if (root.modal.value) {
502
- if (event.cancelable) event.preventDefault();
503
- event.stopPropagation();
504
- }
505
- if (!outsideEvent.defaultPrevented) root.close({ returnFocus: root.modal.value });
506
- else if (root.modal.value) nextTick(menu.focusElement);
507
- }
508
- function setDocumentListeners(active) {
509
- if (submenu || typeof document === "undefined") return;
510
- const method = active ? "addEventListener" : "removeEventListener";
511
- document[method]("pointerdown", onDocumentPointer, true);
512
- document[method]("focusin", onDocumentFocus, true);
513
- }
514
346
  watch(isOpen, (open) => {
515
- setDocumentListeners(open);
516
347
  if (!open) {
517
- menu.activeId.value = null;
518
- menu.closeSubmenus();
348
+ if (sub) root.interaction.closeMenu(menu.id, false);
519
349
  return;
520
350
  }
521
351
  nextTick(() => {
@@ -532,7 +362,7 @@ function useDropdownMenuPrimitiveContent(props, attrs, events, submenu) {
532
362
  if (isOpen.value) menu.focus((sub ? sub.pendingFocus.value : root.pendingFocus.value) || "first");
533
363
  });
534
364
  onBeforeUnmount(() => {
535
- setDocumentListeners(false);
365
+ cleanupDismissal?.();
536
366
  layerBranchCleanup?.();
537
367
  if (element.value) root.unregisterInside(element.value);
538
368
  if (!sub && root.layer.element.value === element.value) root.layer.element.value = null;
@@ -545,7 +375,7 @@ function useDropdownMenuPrimitiveContent(props, attrs, events, submenu) {
545
375
  id: id.value,
546
376
  role: "menu",
547
377
  tabindex: -1,
548
- hidden: optionalAttr(!isOpen.value),
378
+ hidden: toOptionalAttribute(!isOpen.value),
549
379
  class: submenu ? bem("rp-dropdown-menu__submenu", {
550
380
  compound: true,
551
381
  open: isOpen.value
@@ -640,15 +470,16 @@ var dropdown_menu_content_default = /* @__PURE__ */ defineVaporComponent({
640
470
  focusOutside: (event) => emit("focusOutside", event),
641
471
  interactOutside: (event) => emit("interactOutside", event)
642
472
  }, false);
473
+ const templateRefs = { arrow: arrowElement };
643
474
  const _setTemplateRef = createTemplateRefSetter();
644
475
  return createIf(() => unref(shouldRender), () => {
645
476
  const n6 = createDynamicComponent(() => __props.as, { $: [() => unref(rootAttrs)] }, extend(() => {
646
477
  return [createIf(() => __props.arrow, () => {
647
478
  const n4 = t0$2();
648
- _setTemplateRef(n4, arrowElement, null, "arrowElement");
649
479
  renderEffect(() => {
650
480
  setAttr(n4, "data-side", unref(placementSide));
651
481
  setStyle(n4, unref(floating).arrowStyle.value);
482
+ _setTemplateRef(n4, templateRefs.arrow);
652
483
  });
653
484
  return n4;
654
485
  }, null, 129), createSlot("default", {
@@ -663,6 +494,117 @@ var dropdown_menu_content_default = /* @__PURE__ */ defineVaporComponent({
663
494
  }
664
495
  });
665
496
  //#endregion
497
+ //#region src/components/dropdown-menu/useDropdownMenuContextTrigger.ts
498
+ function useDropdownMenuContextTrigger(props) {
499
+ const attrs = useAttrs();
500
+ const root = useRequiredInject(rootKey, "RpDropdownMenuContextTrigger");
501
+ const generatedId = useId();
502
+ const id = computed(() => props.id ?? `${generatedId}-context-trigger`);
503
+ const element = ref(null);
504
+ const isDisabled = computed(() => root.disabled.value || props.disabled);
505
+ let timer;
506
+ let pointerId;
507
+ let startPoint;
508
+ let longPressOpened = false;
509
+ let suppressUntil = 0;
510
+ function setElement(value) {
511
+ resolveHTMLElementRef(value, id.value, (resolved) => {
512
+ const previous = element.value;
513
+ if (previous) root.unregisterInside(previous);
514
+ element.value = resolved;
515
+ if (resolved) root.registerInside(resolved);
516
+ if (!root.triggerId.value) root.triggerId.value = id.value;
517
+ });
518
+ }
519
+ function clearLongPress() {
520
+ if (timer) clearTimeout(timer);
521
+ timer = void 0;
522
+ pointerId = void 0;
523
+ startPoint = void 0;
524
+ element.value?.ownerDocument.defaultView?.removeEventListener("scroll", clearLongPress, true);
525
+ }
526
+ function openAt(point) {
527
+ root.setReturnFocus(element.value);
528
+ root.openAt(point);
529
+ }
530
+ function onContextmenu(event) {
531
+ if (isDisabled.value) return;
532
+ event.preventDefault();
533
+ if (Date.now() < suppressUntil) return;
534
+ openAt({
535
+ x: event.clientX,
536
+ y: event.clientY
537
+ });
538
+ }
539
+ function onKeydown(event) {
540
+ if (isDisabled.value) return;
541
+ if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10")) return;
542
+ event.preventDefault();
543
+ const rect = element.value?.getBoundingClientRect();
544
+ openAt({
545
+ x: rect?.left ?? 0,
546
+ y: rect?.bottom ?? 0
547
+ });
548
+ }
549
+ function onPointerdown(event) {
550
+ if (isDisabled.value || event.pointerType !== "touch" && event.pointerType !== "pen") return;
551
+ clearLongPress();
552
+ pointerId = event.pointerId;
553
+ startPoint = {
554
+ x: event.clientX,
555
+ y: event.clientY
556
+ };
557
+ longPressOpened = false;
558
+ (element.value?.ownerDocument.defaultView)?.addEventListener("scroll", clearLongPress, true);
559
+ timer = setTimeout(() => {
560
+ if (!startPoint) return;
561
+ longPressOpened = true;
562
+ suppressUntil = Date.now() + 1e3;
563
+ openAt(startPoint);
564
+ timer = void 0;
565
+ }, props.longPressDelay);
566
+ }
567
+ function onPointermove(event) {
568
+ if (event.pointerId !== pointerId || !startPoint) return;
569
+ if (Math.hypot(event.clientX - startPoint.x, event.clientY - startPoint.y) > (props.longPressTolerance ?? 0)) clearLongPress();
570
+ }
571
+ function finishPointer(event) {
572
+ if (event.pointerId !== pointerId) return;
573
+ if (longPressOpened) event.preventDefault();
574
+ clearLongPress();
575
+ }
576
+ function onClick(event) {
577
+ if (Date.now() >= suppressUntil) return;
578
+ event.preventDefault();
579
+ event.stopPropagation();
580
+ }
581
+ const rootAttrs = computed(() => mergeProps(attrs, {
582
+ id: id.value,
583
+ "aria-controls": root.contentId.value,
584
+ "aria-haspopup": "menu",
585
+ "aria-disabled": toOptionalAttribute(isDisabled.value),
586
+ "data-state": root.isOpen.value ? "open" : "closed",
587
+ "data-disabled": toOptionalAttribute(isDisabled.value),
588
+ onClick,
589
+ onContextmenu,
590
+ onKeydown,
591
+ onPointerdown,
592
+ onPointermove,
593
+ onPointerup: finishPointer,
594
+ onPointercancel: finishPointer
595
+ }));
596
+ onBeforeUnmount(() => {
597
+ clearLongPress();
598
+ if (element.value) root.unregisterInside(element.value);
599
+ if (root.triggerId.value === id.value) root.triggerId.value = void 0;
600
+ });
601
+ return {
602
+ isOpen: root.isOpen,
603
+ rootAttrs,
604
+ setElement
605
+ };
606
+ }
607
+ //#endregion
666
608
  //#region src/components/dropdown-menu/dropdown-menu-context-trigger.vue
667
609
  var dropdown_menu_context_trigger_default = /* @__PURE__ */ defineVaporComponent({
668
610
  name: "RpDropdownMenuContextTrigger",
@@ -679,114 +621,11 @@ var dropdown_menu_context_trigger_default = /* @__PURE__ */ defineVaporComponent
679
621
  longPressTolerance: { default: 10 }
680
622
  },
681
623
  setup(__props) {
682
- const props = __props;
683
- const attrs = useAttrs();
684
- const root = useRequiredInject(rootKey, "RpDropdownMenuContextTrigger");
685
- const generatedId = useId();
686
- const id = computed(() => props.id ?? `${generatedId}-context-trigger`);
687
- const element = ref(null);
688
- const isDisabled = computed(() => root.disabled.value || props.disabled);
689
- let timer;
690
- let pointerId;
691
- let startPoint;
692
- let longPressOpened = false;
693
- let suppressUntil = 0;
694
- function setElement(value) {
695
- resolveHTMLElementRef(value, id.value, (resolved) => {
696
- const previous = element.value;
697
- if (previous) root.unregisterInside(previous);
698
- element.value = resolved;
699
- if (resolved) root.registerInside(resolved);
700
- if (!root.triggerId.value) root.triggerId.value = id.value;
701
- });
702
- }
703
- function clearLongPress() {
704
- if (timer) clearTimeout(timer);
705
- timer = void 0;
706
- pointerId = void 0;
707
- startPoint = void 0;
708
- element.value?.ownerDocument.defaultView?.removeEventListener("scroll", clearLongPress, true);
709
- }
710
- function openAt(point) {
711
- root.setReturnFocus(element.value);
712
- root.openAt(point);
713
- }
714
- function onContextmenu(event) {
715
- if (isDisabled.value) return;
716
- event.preventDefault();
717
- if (Date.now() < suppressUntil) return;
718
- openAt({
719
- x: event.clientX,
720
- y: event.clientY
721
- });
722
- }
723
- function onKeydown(event) {
724
- if (isDisabled.value) return;
725
- if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10")) return;
726
- event.preventDefault();
727
- const rect = element.value?.getBoundingClientRect();
728
- openAt({
729
- x: rect?.left ?? 0,
730
- y: rect?.bottom ?? 0
731
- });
732
- }
733
- function onPointerdown(event) {
734
- if (isDisabled.value || event.pointerType !== "touch" && event.pointerType !== "pen") return;
735
- clearLongPress();
736
- pointerId = event.pointerId;
737
- startPoint = {
738
- x: event.clientX,
739
- y: event.clientY
740
- };
741
- longPressOpened = false;
742
- (element.value?.ownerDocument.defaultView)?.addEventListener("scroll", clearLongPress, true);
743
- timer = setTimeout(() => {
744
- if (!startPoint) return;
745
- longPressOpened = true;
746
- suppressUntil = Date.now() + 1e3;
747
- openAt(startPoint);
748
- timer = void 0;
749
- }, props.longPressDelay);
750
- }
751
- function onPointermove(event) {
752
- if (event.pointerId !== pointerId || !startPoint) return;
753
- if (Math.hypot(event.clientX - startPoint.x, event.clientY - startPoint.y) > props.longPressTolerance) clearLongPress();
754
- }
755
- function finishPointer(event) {
756
- if (event.pointerId !== pointerId) return;
757
- if (longPressOpened) event.preventDefault();
758
- clearLongPress();
759
- }
760
- function onClick(event) {
761
- if (Date.now() >= suppressUntil) return;
762
- event.preventDefault();
763
- event.stopPropagation();
764
- }
765
- const rootAttrs = computed(() => mergeProps(attrs, {
766
- id: id.value,
767
- "aria-controls": root.contentId.value,
768
- "aria-expanded": root.isOpen.value,
769
- "aria-haspopup": "menu",
770
- "aria-disabled": optionalAttr(isDisabled.value),
771
- "data-state": root.isOpen.value ? "open" : "closed",
772
- "data-disabled": optionalAttr(isDisabled.value),
773
- onClick,
774
- onContextmenu,
775
- onKeydown,
776
- onPointerdown,
777
- onPointermove,
778
- onPointerup: finishPointer,
779
- onPointercancel: finishPointer
780
- }));
781
- onBeforeUnmount(() => {
782
- clearLongPress();
783
- if (element.value) root.unregisterInside(element.value);
784
- if (root.triggerId.value === id.value) root.triggerId.value = void 0;
785
- });
786
- const n1 = createDynamicComponent(() => __props.as, { $: [() => rootAttrs.value] }, extend(() => {
787
- return createSlot("default", { isOpen: () => unref(root).isOpen.value });
624
+ const { isOpen, rootAttrs, setElement } = useDropdownMenuContextTrigger(__props);
625
+ const n1 = createDynamicComponent(() => __props.as, { $: [() => unref(rootAttrs)] }, extend(() => {
626
+ return createSlot("default", { isOpen: () => unref(isOpen) });
788
627
  }, { _: 8 }), 1);
789
- setTemplateRefBinding(n1, () => setElement);
628
+ setTemplateRefBinding(n1, () => setElement, void 0, void 0, "setElement");
790
629
  return n1;
791
630
  }
792
631
  });
@@ -828,11 +667,11 @@ var dropdown_menu_item_default = /* @__PURE__ */ defineVaporComponent({
828
667
  type: props.as === "button" ? "button" : void 0,
829
668
  role: "menuitem",
830
669
  tabindex: -1,
831
- disabled: props.as === "button" ? optionalAttr(isDisabled.value) : void 0,
832
- "aria-disabled": optionalAttr(isDisabled.value),
833
- "data-disabled": optionalAttr(isDisabled.value),
834
- "data-focused": optionalAttr(focused.value),
835
- "data-highlighted": optionalAttr(focused.value),
670
+ disabled: props.as === "button" ? toOptionalAttribute(isDisabled.value) : void 0,
671
+ "aria-disabled": toOptionalAttribute(isDisabled.value),
672
+ "data-disabled": toOptionalAttribute(isDisabled.value),
673
+ "data-focused": toOptionalAttribute(focused.value),
674
+ "data-highlighted": toOptionalAttribute(focused.value),
836
675
  class: bem("rp-dropdown-menu__item", {
837
676
  focused: focused.value,
838
677
  disabled: isDisabled.value,
@@ -928,7 +767,7 @@ var dropdown_menu_portal_default = /* @__PURE__ */ defineVaporComponent({
928
767
  const props = __props;
929
768
  const teleportTo = provideTeleportTarget({ get teleportTo() {
930
769
  return props.to ?? props.teleportTo;
931
- } }, () => Boolean(props.teleport && !props.disabled));
770
+ } });
932
771
  return createComponent(VaporTeleport, {
933
772
  to: () => unref(teleportTo),
934
773
  disabled: () => __props.disabled || !__props.teleport
@@ -957,12 +796,17 @@ var dropdown_menu_radio_group_default = /* @__PURE__ */ defineVaporComponent({
957
796
  const emit = __emit;
958
797
  const attrs = useAttrs();
959
798
  useRequiredInject(menuKey, "RpDropdownMenuRadioGroup");
960
- const uncontrolled = ref(props.defaultValue);
961
- const value = computed(() => props.modelValue === void 0 ? uncontrolled.value : props.modelValue);
799
+ const controllable = useControllableValue({
800
+ modelValue: () => props.modelValue,
801
+ defaultValue: () => props.defaultValue,
802
+ onChange: (value) => {
803
+ if (value !== null) emit("update:modelValue", value);
804
+ }
805
+ });
806
+ const value = controllable.value;
962
807
  function select(nextValue) {
963
808
  if (value.value === nextValue) return;
964
- if (props.modelValue === void 0) uncontrolled.value = nextValue;
965
- emit("update:modelValue", nextValue);
809
+ controllable.setValue(nextValue);
966
810
  }
967
811
  provide(radioGroupKey, {
968
812
  value,
@@ -976,7 +820,7 @@ var dropdown_menu_radio_group_default = /* @__PURE__ */ defineVaporComponent({
976
820
  "aria-labelledby": props.ariaLabelledby
977
821
  }));
978
822
  return createDynamicComponent(() => __props.as, { $: [() => rootAttrs.value] }, extend(() => {
979
- return createSlot("default", { value: () => value.value });
823
+ return createSlot("default", { value: () => unref(value) });
980
824
  }, { _: 8 }), 1);
981
825
  }
982
826
  });
@@ -1030,12 +874,12 @@ var dropdown_menu_radio_item_default = /* @__PURE__ */ defineVaporComponent({
1030
874
  type: props.as === "button" ? "button" : void 0,
1031
875
  role: "menuitemradio",
1032
876
  tabindex: -1,
1033
- disabled: props.as === "button" ? optionalAttr(isDisabled.value) : void 0,
1034
- "aria-disabled": optionalAttr(isDisabled.value),
877
+ disabled: props.as === "button" ? toOptionalAttribute(isDisabled.value) : void 0,
878
+ "aria-disabled": toOptionalAttribute(isDisabled.value),
1035
879
  "aria-checked": checked.value,
1036
- "data-disabled": optionalAttr(isDisabled.value),
1037
- "data-focused": optionalAttr(focused.value),
1038
- "data-highlighted": optionalAttr(focused.value),
880
+ "data-disabled": toOptionalAttribute(isDisabled.value),
881
+ "data-focused": toOptionalAttribute(focused.value),
882
+ "data-highlighted": toOptionalAttribute(focused.value),
1039
883
  "data-state": state.value,
1040
884
  class: bem("rp-dropdown-menu__item", {
1041
885
  focused: focused.value,
@@ -1057,19 +901,655 @@ var dropdown_menu_radio_item_default = /* @__PURE__ */ defineVaporComponent({
1057
901
  }
1058
902
  });
1059
903
  //#endregion
1060
- //#region src/components/dropdown-menu/dropdown-menu-root.vue
1061
- var dropdown_menu_root_default = /* @__PURE__ */ defineVaporComponent({
1062
- name: "RpDropdownMenuRoot",
1063
- __name: "dropdown-menu-root",
1064
- props: {
1065
- id: {},
1066
- open: {
1067
- type: Boolean,
1068
- default: void 0
1069
- },
1070
- defaultOpen: {
1071
- type: Boolean,
1072
- default: false
904
+ //#region src/components/dropdown-menu/dropdownMenuInteractionNavigation.ts
905
+ function createDropdownMenuInteractionNavigation({ host, registry, closeRoot }) {
906
+ const state = registry.state;
907
+ function closeMenu(menuId, focusParent = false) {
908
+ const menu = registry.getMenu(menuId);
909
+ const menuState = state.getMenuState(menuId);
910
+ if (!menu && !menuState || menuId === host.rootMenuId) return false;
911
+ const wasOpen = registry.isMenuOpen(menuId);
912
+ closeSubmenus(menuId);
913
+ const shouldRestoreParent = focusParent || registry.activeMenuId.value === menuId;
914
+ if (wasOpen) state.setMenuOpen(menuId, false);
915
+ state.clearPendingMenuFocus(menuId);
916
+ state.clearMenuState(menuId);
917
+ if (shouldRestoreParent) {
918
+ const parentItem = state.getParentItem(menuId);
919
+ if (!parentItem || !registry.setActive(parentItem.id, focusParent)) {
920
+ const parentMenuId = parentItem?.menuId ?? host.rootMenuId;
921
+ registry.activeMenuId.value = parentMenuId;
922
+ registry.activeItemId.value = state.getActiveIdValue(parentMenuId);
923
+ if (focusParent) registry.focusMenuElement(parentMenuId);
924
+ }
925
+ }
926
+ return wasOpen;
927
+ }
928
+ function closeSubmenus(menuId, exceptMenuId) {
929
+ for (const item of state.getItems(menuId)) {
930
+ const submenuId = item.submenuId?.();
931
+ if (!submenuId || submenuId === exceptMenuId) continue;
932
+ closeMenu(submenuId, false);
933
+ }
934
+ }
935
+ function closeAllSubmenus() {
936
+ closeSubmenus(host.rootMenuId);
937
+ for (const menuId of state.getMenuIds()) if (menuId !== host.rootMenuId && registry.isMenuOpen(menuId)) closeMenu(menuId, false);
938
+ }
939
+ function focusMenu(menuId, target = "first") {
940
+ const enabledItems = state.getEnabledItems(menuId);
941
+ const item = target === "last" ? enabledItems.at(-1) : enabledItems[0];
942
+ registry.focusMenuElement(menuId);
943
+ if (!item) {
944
+ state.clearMenuState(menuId);
945
+ registry.activeMenuId.value = menuId;
946
+ const parentItem = state.getParentItem(menuId);
947
+ if (parentItem) registry.activeItemId.value = parentItem.id;
948
+ return false;
949
+ }
950
+ return registry.setActive(item.id);
951
+ }
952
+ function openMenu(menuId, focus = "first") {
953
+ const menu = registry.getMenu(menuId);
954
+ const menuState = state.getMenuState(menuId);
955
+ if (!menu && !menuState || menuId === host.rootMenuId) return false;
956
+ const parentItem = state.getParentItem(menuId);
957
+ if (!parentItem || parentItem.disabled()) return false;
958
+ closeSubmenus(parentItem.menuId, menuId);
959
+ state.setPendingMenuFocus(menuId, focus);
960
+ state.setMenuOpen(menuId, true);
961
+ if (focus && menu) focusMenu(menuId, focus);
962
+ return true;
963
+ }
964
+ function selectItem(id, originalEvent = new Event("select")) {
965
+ const item = registry.getItem(id);
966
+ if (!item || item.disabled()) return;
967
+ const selectEvent = item.select?.(originalEvent);
968
+ if (selectEvent && !selectEvent.defaultPrevented && (item.closeOnSelect?.() ?? true)) closeRoot({ focusTrigger: true });
969
+ }
970
+ function activateItem(id, originalEvent = new Event("select")) {
971
+ const item = registry.getItem(id);
972
+ if (!item || item.disabled()) return;
973
+ const submenuId = item.submenuId?.();
974
+ if (submenuId) {
975
+ openMenu(submenuId, "first");
976
+ return;
977
+ }
978
+ selectItem(id, originalEvent);
979
+ }
980
+ function hoverItem(id, openSubmenu = false) {
981
+ const item = registry.getItem(id);
982
+ if (!item || item.disabled()) return;
983
+ registry.setActive(id);
984
+ const submenuId = item.submenuId?.();
985
+ if (openSubmenu && submenuId) openMenu(submenuId, false);
986
+ else closeSubmenus(item.menuId, submenuId && registry.isMenuOpen(submenuId) ? submenuId : void 0);
987
+ }
988
+ function reconcileOpenSubmenus() {
989
+ for (const menuId of state.getMenuIds()) {
990
+ if (menuId === host.rootMenuId || !registry.isMenuOpen(menuId)) continue;
991
+ const parentItem = state.getParentItem(menuId);
992
+ if (!parentItem || parentItem.disabled()) closeMenu(menuId, false);
993
+ }
994
+ }
995
+ function reconcile(menuId) {
996
+ const menuIds = menuId ? [menuId] : state.getRegisteredMenuIds();
997
+ for (const id of menuIds) {
998
+ const activeId = state.getActiveIdValue(id);
999
+ if (!activeId) continue;
1000
+ const currentItems = state.getItems(id);
1001
+ const activeIndex = currentItems.findIndex((item) => item.id === activeId);
1002
+ const activeItem = currentItems[activeIndex];
1003
+ if (activeItem && !activeItem.disabled()) continue;
1004
+ state.chooseNeighbor(id, Math.max(0, activeIndex));
1005
+ }
1006
+ reconcileOpenSubmenus();
1007
+ }
1008
+ return {
1009
+ closeMenu,
1010
+ closeSubmenus,
1011
+ closeAllSubmenus,
1012
+ focusMenu,
1013
+ openMenu,
1014
+ selectItem,
1015
+ activateItem,
1016
+ hoverItem,
1017
+ reconcile,
1018
+ reconcileOpenSubmenus
1019
+ };
1020
+ }
1021
+ //#endregion
1022
+ //#region src/components/dropdown-menu/dropdownMenuInteractionRegistry.ts
1023
+ function compareItems(left, right) {
1024
+ const leftElement = left.element();
1025
+ const rightElement = right.element();
1026
+ if (leftElement && rightElement && leftElement !== rightElement && typeof Node !== "undefined") {
1027
+ const position = leftElement.compareDocumentPosition(rightElement);
1028
+ if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
1029
+ if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
1030
+ }
1031
+ return (left.order?.() ?? 0) - (right.order?.() ?? 0);
1032
+ }
1033
+ function scrollItemIntoView(item) {
1034
+ nextTick(() => item.element()?.scrollIntoView?.({ block: "nearest" }));
1035
+ }
1036
+ function createDropdownMenuInteractionRegistry(host, navigation) {
1037
+ const menus = /* @__PURE__ */ new Map();
1038
+ const menuStates = /* @__PURE__ */ new Map();
1039
+ const items = /* @__PURE__ */ new Map();
1040
+ const activeIds = ref(/* @__PURE__ */ new Map());
1041
+ const activeItemId = ref(null);
1042
+ const activeMenuId = ref(host.rootMenuId);
1043
+ const pendingRootFocus = ref("first");
1044
+ const pendingMenuFocus = /* @__PURE__ */ new Map();
1045
+ function getMenu(id) {
1046
+ return menus.get(id);
1047
+ }
1048
+ function getMenuState(id) {
1049
+ return menuStates.get(id);
1050
+ }
1051
+ function getItem(id) {
1052
+ return items.get(id);
1053
+ }
1054
+ function getItems(menuId) {
1055
+ return [...items.values()].filter((item) => item.menuId === menuId).sort(compareItems);
1056
+ }
1057
+ function getEnabledItems(menuId) {
1058
+ return getItems(menuId).filter((item) => !item.disabled());
1059
+ }
1060
+ function getActiveIdValue(menuId) {
1061
+ return activeIds.value.get(menuId) ?? null;
1062
+ }
1063
+ function setActiveId(menuId, id) {
1064
+ const next = new Map(activeIds.value);
1065
+ if (id) next.set(menuId, id);
1066
+ else next.delete(menuId);
1067
+ activeIds.value = next;
1068
+ }
1069
+ function getActiveId(menuId) {
1070
+ return computed(() => getActiveIdValue(menuId));
1071
+ }
1072
+ function isMenuOpen(menuId) {
1073
+ if (menuId === host.rootMenuId) return host.isOpen.value;
1074
+ return menuStates.get(menuId)?.isOpen() ?? menus.get(menuId)?.isOpen() ?? false;
1075
+ }
1076
+ function focusMenuElement(menuId) {
1077
+ nextTick(() => {
1078
+ const menu = menus.get(menuId);
1079
+ (menu?.focusTarget?.() ?? menu?.element())?.focus();
1080
+ });
1081
+ }
1082
+ function clearMenuState(menuId) {
1083
+ const activeId = getActiveIdValue(menuId);
1084
+ if (activeId && activeItemId.value === activeId) activeItemId.value = null;
1085
+ setActiveId(menuId, null);
1086
+ }
1087
+ function setActive(id, focusElement = false) {
1088
+ if (id === null) {
1089
+ clearMenuState(activeMenuId.value);
1090
+ activeItemId.value = null;
1091
+ return true;
1092
+ }
1093
+ const item = items.get(id);
1094
+ if (!item || item.disabled()) return false;
1095
+ setActiveId(item.menuId, item.id);
1096
+ activeItemId.value = item.id;
1097
+ activeMenuId.value = item.menuId;
1098
+ pendingMenuFocus.delete(item.menuId);
1099
+ if (focusElement) focusMenuElement(item.menuId);
1100
+ scrollItemIntoView(item);
1101
+ return true;
1102
+ }
1103
+ function isActive(id) {
1104
+ return getActiveIdValue(items.get(id)?.menuId ?? "") === id;
1105
+ }
1106
+ function getParentItem(menuId) {
1107
+ const parentItemId = menus.get(menuId)?.parentItemId?.();
1108
+ if (parentItemId) {
1109
+ const parentItem = items.get(parentItemId);
1110
+ if (parentItem) return parentItem;
1111
+ }
1112
+ return [...items.values()].find((item) => item.submenuId?.() === menuId);
1113
+ }
1114
+ function setMenuOpen(menuId, open) {
1115
+ const state = menuStates.get(menuId);
1116
+ if (state) state.setOpen(open);
1117
+ else menus.get(menuId)?.setOpen?.(open);
1118
+ }
1119
+ function chooseNeighbor(menuId, previousIndex) {
1120
+ const currentItems = getItems(menuId);
1121
+ const nextCandidates = currentItems.slice(Math.max(0, previousIndex));
1122
+ const previousCandidates = [];
1123
+ for (let index = Math.max(0, previousIndex) - 1; index >= 0; index -= 1) {
1124
+ const item = currentItems[index];
1125
+ if (item) previousCandidates.push(item);
1126
+ }
1127
+ const next = [...nextCandidates, ...previousCandidates].find((item) => !item.disabled());
1128
+ if (next) setActive(next.id);
1129
+ else {
1130
+ clearMenuState(menuId);
1131
+ focusMenuElement(menuId);
1132
+ }
1133
+ }
1134
+ function unregisterItem(id) {
1135
+ const item = items.get(id);
1136
+ if (!item) return;
1137
+ const previousIndex = getItems(item.menuId).findIndex((current) => current.id === id);
1138
+ const submenuId = item.submenuId?.();
1139
+ if (submenuId) navigation.closeMenu(submenuId, false);
1140
+ items.delete(id);
1141
+ if (getActiveIdValue(item.menuId) === id) chooseNeighbor(item.menuId, previousIndex);
1142
+ navigation.reconcileOpenSubmenus();
1143
+ }
1144
+ function registerItem(registration) {
1145
+ const previous = items.get(registration.id);
1146
+ if (previous && previous.menuId !== registration.menuId && getActiveIdValue(previous.menuId) === previous.id) clearMenuState(previous.menuId);
1147
+ items.set(registration.id, registration);
1148
+ if (isMenuOpen(registration.menuId) && !getActiveIdValue(registration.menuId) && !registration.disabled()) {
1149
+ const enabledItems = getEnabledItems(registration.menuId);
1150
+ const candidate = pendingMenuFocus.get(registration.menuId) === "last" ? enabledItems.at(-1) : enabledItems[0];
1151
+ if (candidate) setActive(candidate.id);
1152
+ } else navigation.reconcile(registration.menuId);
1153
+ return () => {
1154
+ if (items.get(registration.id) === registration) unregisterItem(registration.id);
1155
+ };
1156
+ }
1157
+ function unregisterMenu(id) {
1158
+ if (!menus.get(id)) return;
1159
+ navigation.closeSubmenus(id);
1160
+ if (id !== host.rootMenuId) navigation.closeMenu(id, false);
1161
+ clearMenuState(id);
1162
+ menus.delete(id);
1163
+ }
1164
+ function registerMenu(registration) {
1165
+ menus.set(registration.id, registration);
1166
+ const pending = pendingMenuFocus.get(registration.id);
1167
+ if (registration.isOpen() && pending) nextTick(() => navigation.focusMenu(registration.id, pending));
1168
+ return () => {
1169
+ if (menus.get(registration.id) === registration) unregisterMenu(registration.id);
1170
+ };
1171
+ }
1172
+ function registerMenuState(registration) {
1173
+ menuStates.set(registration.id, registration);
1174
+ return () => {
1175
+ if (menuStates.get(registration.id) !== registration) return;
1176
+ menuStates.delete(registration.id);
1177
+ pendingMenuFocus.delete(registration.id);
1178
+ if (!menus.has(registration.id)) clearMenuState(registration.id);
1179
+ };
1180
+ }
1181
+ return {
1182
+ activeItemId,
1183
+ activeMenuId,
1184
+ pendingRootFocus,
1185
+ registerMenu,
1186
+ unregisterMenu,
1187
+ registerMenuState,
1188
+ registerItem,
1189
+ unregisterItem,
1190
+ getActiveId,
1191
+ getItem,
1192
+ getMenu,
1193
+ isActive,
1194
+ isMenuOpen,
1195
+ setActive,
1196
+ focusMenuElement,
1197
+ state: {
1198
+ getMenuState,
1199
+ getItems,
1200
+ getEnabledItems,
1201
+ getActiveIdValue,
1202
+ clearMenuState,
1203
+ getParentItem,
1204
+ setMenuOpen,
1205
+ chooseNeighbor,
1206
+ getMenuIds: () => /* @__PURE__ */ new Set([...menus.keys(), ...menuStates.keys()]),
1207
+ getRegisteredMenuIds: () => [...menus.keys()],
1208
+ getPendingMenuFocus: (menuId) => pendingMenuFocus.get(menuId),
1209
+ setPendingMenuFocus: (menuId, focus) => pendingMenuFocus.set(menuId, focus),
1210
+ clearPendingMenuFocus: (menuId) => pendingMenuFocus.delete(menuId)
1211
+ }
1212
+ };
1213
+ }
1214
+ //#endregion
1215
+ //#region src/components/dropdown-menu/useDropdownMenuInteractionDismissal.ts
1216
+ function createOutsideEvent(originalEvent) {
1217
+ return createCancelableCustomEvent("dropdown-menu-interact-outside", { originalEvent }, originalEvent);
1218
+ }
1219
+ function blockDocumentClick(event) {
1220
+ if (event.cancelable) event.preventDefault();
1221
+ event.stopPropagation();
1222
+ document.removeEventListener("click", blockDocumentClick, true);
1223
+ }
1224
+ function blockNextDocumentClick() {
1225
+ if (typeof document === "undefined") return;
1226
+ document.addEventListener("click", blockDocumentClick, true);
1227
+ window.setTimeout(() => document.removeEventListener("click", blockDocumentClick, true), 1e3);
1228
+ }
1229
+ function useDropdownMenuInteractionDismissal({ host, registry, closeRoot }) {
1230
+ const inside = /* @__PURE__ */ new Set();
1231
+ let dismissal;
1232
+ let isListening = false;
1233
+ function registerInside(element) {
1234
+ inside.add(element);
1235
+ }
1236
+ function unregisterInside(element) {
1237
+ inside.delete(element);
1238
+ }
1239
+ function isInside(event) {
1240
+ return [...inside].some((element) => isEventWithinElement(event, element));
1241
+ }
1242
+ function emitOutside(type, originalEvent) {
1243
+ const outsideEvent = createOutsideEvent(originalEvent);
1244
+ if (type === "pointer") dismissal?.pointerDownOutside?.(outsideEvent);
1245
+ else dismissal?.focusOutside?.(outsideEvent);
1246
+ dismissal?.interactOutside?.(outsideEvent);
1247
+ return outsideEvent;
1248
+ }
1249
+ function blockModalInteraction(event) {
1250
+ if (!host.modal.value) return;
1251
+ if (event.cancelable) event.preventDefault();
1252
+ event.stopPropagation();
1253
+ if (event.type === "pointerdown") blockNextDocumentClick();
1254
+ }
1255
+ function shouldIgnoreOutside(event) {
1256
+ const ignoredTargets = (dismissal?.ignoredTargets() ?? []).map((target) => isRef(target) ? target.value : target);
1257
+ return isInside(event) || isEventWithinTargets(event, ignoredTargets);
1258
+ }
1259
+ function onDocumentPointer(event) {
1260
+ if (!host.isTopLayer() || shouldIgnoreOutside(event)) return;
1261
+ const outsideEvent = emitOutside("pointer", event);
1262
+ blockModalInteraction(event);
1263
+ if (!outsideEvent.defaultPrevented) closeRoot({ focusTrigger: host.modal.value });
1264
+ }
1265
+ function onDocumentFocus(event) {
1266
+ if (!host.isTopLayer() || shouldIgnoreOutside(event)) return;
1267
+ const outsideEvent = emitOutside("focus", event);
1268
+ blockModalInteraction(event);
1269
+ if (!outsideEvent.defaultPrevented) closeRoot({ focusTrigger: host.modal.value });
1270
+ else if (host.modal.value) registry.focusMenuElement(registry.activeMenuId.value);
1271
+ }
1272
+ function setDocumentListeners(active) {
1273
+ if (typeof document === "undefined" || active === isListening) return;
1274
+ isListening = active;
1275
+ const method = active ? "addEventListener" : "removeEventListener";
1276
+ document[method]("pointerdown", onDocumentPointer, true);
1277
+ document[method]("focusin", onDocumentFocus, true);
1278
+ }
1279
+ function registerDismissal(registration) {
1280
+ dismissal = registration;
1281
+ setDocumentListeners(host.isOpen.value);
1282
+ return () => {
1283
+ if (dismissal !== registration) return;
1284
+ dismissal = void 0;
1285
+ setDocumentListeners(false);
1286
+ };
1287
+ }
1288
+ watch(host.isOpen, (open) => setDocumentListeners(open && Boolean(dismissal)));
1289
+ onBeforeUnmount(() => setDocumentListeners(false));
1290
+ return {
1291
+ registerInside,
1292
+ unregisterInside,
1293
+ registerDismissal
1294
+ };
1295
+ }
1296
+ //#endregion
1297
+ //#region src/components/dropdown-menu/useDropdownMenuInteractionKeyboard.ts
1298
+ function getPlacementSide(placement) {
1299
+ return placement.split("-")[0];
1300
+ }
1301
+ function useDropdownMenuInteractionKeyboard({ host, registry, navigation, openRoot, closeRoot }) {
1302
+ const state = registry.state;
1303
+ const typeaheadMenuId = ref(host.rootMenuId);
1304
+ const typeahead = useTypeahead({
1305
+ items: () => state.getItems(typeaheadMenuId.value),
1306
+ activeIndex: () => state.getItems(typeaheadMenuId.value).findIndex((item) => item.id === state.getActiveIdValue(typeaheadMenuId.value)),
1307
+ getKey: (item) => item.id,
1308
+ getTextValue: (item) => item.textValue(),
1309
+ isDisabled: (item) => item.disabled(),
1310
+ scopeKey: () => typeaheadMenuId.value,
1311
+ onMatch(item) {
1312
+ registry.setActive(item.id);
1313
+ navigation.closeSubmenus(item.menuId);
1314
+ }
1315
+ });
1316
+ function move(menuId, direction) {
1317
+ const enabledItems = state.getEnabledItems(menuId);
1318
+ if (enabledItems.length === 0) {
1319
+ state.clearMenuState(menuId);
1320
+ return;
1321
+ }
1322
+ const currentIndex = enabledItems.findIndex((item) => item.id === state.getActiveIdValue(menuId));
1323
+ const item = enabledItems[((currentIndex < 0 ? direction === 1 ? -1 : 0 : currentIndex) + direction + enabledItems.length) % enabledItems.length];
1324
+ if (item) {
1325
+ registry.setActive(item.id);
1326
+ navigation.closeSubmenus(menuId);
1327
+ }
1328
+ }
1329
+ function handleHorizontalKey(menuId, event, activeItem) {
1330
+ const menu = registry.getMenu(menuId);
1331
+ const isRight = event.key === "ArrowRight";
1332
+ if (menuId !== host.rootMenuId && menu) {
1333
+ if (isRight === (getPlacementSide(menu.placement()) === "left")) {
1334
+ event.preventDefault();
1335
+ if (menu.stopKeyPropagation) event.stopPropagation();
1336
+ navigation.closeMenu(menuId, true);
1337
+ return;
1338
+ }
1339
+ }
1340
+ const submenuId = activeItem?.submenuId?.();
1341
+ if (!activeItem || activeItem.disabled() || !submenuId) return;
1342
+ const submenu = registry.getMenu(submenuId);
1343
+ if (isRight !== (activeItem.submenuDirection?.() !== "left" && (!submenu || getPlacementSide(submenu.placement()) !== "left"))) return;
1344
+ event.preventDefault();
1345
+ if (menu?.stopKeyPropagation) event.stopPropagation();
1346
+ navigation.openMenu(submenuId, "first");
1347
+ }
1348
+ function onMenuKeydown(menuId, event) {
1349
+ const menu = registry.getMenu(menuId);
1350
+ typeaheadMenuId.value = menuId;
1351
+ if (typeahead.handleKey(event)) {
1352
+ if (menu?.stopKeyPropagation) event.stopPropagation();
1353
+ return;
1354
+ }
1355
+ const activeId = state.getActiveIdValue(menuId);
1356
+ const activeItem = activeId ? registry.getItem(activeId) : void 0;
1357
+ const stop = () => {
1358
+ if (menu?.stopKeyPropagation) event.stopPropagation();
1359
+ };
1360
+ switch (event.key) {
1361
+ case "ArrowDown":
1362
+ event.preventDefault();
1363
+ stop();
1364
+ move(menuId, 1);
1365
+ break;
1366
+ case "ArrowUp":
1367
+ event.preventDefault();
1368
+ stop();
1369
+ move(menuId, -1);
1370
+ break;
1371
+ case "Home":
1372
+ event.preventDefault();
1373
+ stop();
1374
+ navigation.focusMenu(menuId, "first");
1375
+ navigation.closeSubmenus(menuId);
1376
+ break;
1377
+ case "End":
1378
+ event.preventDefault();
1379
+ stop();
1380
+ navigation.focusMenu(menuId, "last");
1381
+ navigation.closeSubmenus(menuId);
1382
+ break;
1383
+ case "ArrowRight":
1384
+ case "ArrowLeft":
1385
+ handleHorizontalKey(menuId, event, activeItem);
1386
+ break;
1387
+ case "Enter":
1388
+ case " ":
1389
+ event.preventDefault();
1390
+ stop();
1391
+ if (activeItem) navigation.activateItem(activeItem.id, event);
1392
+ break;
1393
+ case "Escape":
1394
+ event.preventDefault();
1395
+ stop();
1396
+ if (menu?.onEscape && !menu.onEscape(event)) return;
1397
+ if (!navigation.closeMenu(menuId, true)) closeRoot({ focusTrigger: true });
1398
+ break;
1399
+ case "Tab":
1400
+ closeRoot();
1401
+ break;
1402
+ }
1403
+ }
1404
+ function onTriggerKeydown(event, beforeOpen) {
1405
+ if (host.disabled.value) return;
1406
+ switch (event.key) {
1407
+ case "Enter":
1408
+ case " ":
1409
+ case "ArrowDown":
1410
+ event.preventDefault();
1411
+ beforeOpen?.();
1412
+ openRoot("first");
1413
+ break;
1414
+ case "ArrowUp":
1415
+ event.preventDefault();
1416
+ beforeOpen?.();
1417
+ openRoot("last");
1418
+ break;
1419
+ case "Escape":
1420
+ closeRoot({ focusTrigger: true });
1421
+ break;
1422
+ }
1423
+ }
1424
+ return {
1425
+ onMenuKeydown,
1426
+ onTriggerKeydown,
1427
+ resetTypeahead: typeahead.reset
1428
+ };
1429
+ }
1430
+ //#endregion
1431
+ //#region src/components/dropdown-menu/dropdownMenuInteraction.ts
1432
+ function resolveOpenFocusTarget(options) {
1433
+ if (typeof options === "string") return options;
1434
+ return options?.focus ?? "first";
1435
+ }
1436
+ function useDropdownMenuInteraction(host) {
1437
+ let navigation;
1438
+ const registry = createDropdownMenuInteractionRegistry(host, {
1439
+ closeMenu: (menuId, focusParent) => navigation.closeMenu(menuId, focusParent),
1440
+ closeSubmenus: (menuId, exceptMenuId) => navigation.closeSubmenus(menuId, exceptMenuId),
1441
+ focusMenu: (menuId, target) => navigation.focusMenu(menuId, target),
1442
+ reconcile: (menuId) => navigation.reconcile(menuId),
1443
+ reconcileOpenSubmenus: () => navigation.reconcileOpenSubmenus()
1444
+ });
1445
+ function open(options) {
1446
+ if (host.disabled.value) return;
1447
+ host.beforeOpen?.();
1448
+ registry.pendingRootFocus.value = resolveOpenFocusTarget(options);
1449
+ host.setOpen(true);
1450
+ navigation.focusMenu(host.rootMenuId, registry.pendingRootFocus.value);
1451
+ }
1452
+ function close(options = {}) {
1453
+ host.beforeClose?.();
1454
+ host.setOpen(false);
1455
+ registry.pendingRootFocus.value = false;
1456
+ navigation.closeAllSubmenus();
1457
+ registry.state.clearMenuState(host.rootMenuId);
1458
+ registry.activeItemId.value = null;
1459
+ registry.activeMenuId.value = host.rootMenuId;
1460
+ keyboard.resetTypeahead();
1461
+ if (options.focusTrigger || options.returnFocus) host.focusTrigger();
1462
+ }
1463
+ function toggle() {
1464
+ if (host.isOpen.value) close({ returnFocus: true });
1465
+ else open();
1466
+ }
1467
+ function onTriggerClick(beforeToggle) {
1468
+ beforeToggle?.();
1469
+ toggle();
1470
+ }
1471
+ navigation = createDropdownMenuInteractionNavigation({
1472
+ host,
1473
+ registry,
1474
+ closeRoot: close
1475
+ });
1476
+ const keyboard = useDropdownMenuInteractionKeyboard({
1477
+ host,
1478
+ registry,
1479
+ navigation,
1480
+ openRoot: open,
1481
+ closeRoot: close
1482
+ });
1483
+ const dismissal = useDropdownMenuInteractionDismissal({
1484
+ host,
1485
+ registry,
1486
+ closeRoot: close
1487
+ });
1488
+ watch(host.disabled, (disabled) => {
1489
+ if (disabled) close();
1490
+ });
1491
+ watch(host.isOpen, (isOpen) => {
1492
+ if (!isOpen) {
1493
+ navigation.closeAllSubmenus();
1494
+ registry.state.clearMenuState(host.rootMenuId);
1495
+ registry.activeItemId.value = null;
1496
+ registry.activeMenuId.value = host.rootMenuId;
1497
+ keyboard.resetTypeahead();
1498
+ return;
1499
+ }
1500
+ const focus = registry.pendingRootFocus.value || "first";
1501
+ navigation.focusMenu(host.rootMenuId, focus);
1502
+ }, { immediate: true });
1503
+ return {
1504
+ rootMenuId: host.rootMenuId,
1505
+ activeItemId: registry.activeItemId,
1506
+ activeMenuId: registry.activeMenuId,
1507
+ pendingRootFocus: registry.pendingRootFocus,
1508
+ registerMenu: registry.registerMenu,
1509
+ unregisterMenu: registry.unregisterMenu,
1510
+ registerMenuState: registry.registerMenuState,
1511
+ registerItem: registry.registerItem,
1512
+ unregisterItem: registry.unregisterItem,
1513
+ registerInside: dismissal.registerInside,
1514
+ unregisterInside: dismissal.unregisterInside,
1515
+ registerDismissal: dismissal.registerDismissal,
1516
+ getActiveId: registry.getActiveId,
1517
+ getItem: registry.getItem,
1518
+ getMenu: registry.getMenu,
1519
+ isActive: registry.isActive,
1520
+ isMenuOpen: registry.isMenuOpen,
1521
+ setActive: registry.setActive,
1522
+ focusMenu: navigation.focusMenu,
1523
+ focusMenuElement: registry.focusMenuElement,
1524
+ open,
1525
+ close,
1526
+ toggle,
1527
+ openMenu: navigation.openMenu,
1528
+ closeMenu: navigation.closeMenu,
1529
+ closeSubmenus: navigation.closeSubmenus,
1530
+ selectItem: navigation.selectItem,
1531
+ activateItem: navigation.activateItem,
1532
+ hoverItem: navigation.hoverItem,
1533
+ reconcile: navigation.reconcile,
1534
+ onTriggerClick,
1535
+ onTriggerKeydown: keyboard.onTriggerKeydown,
1536
+ onMenuKeydown: keyboard.onMenuKeydown
1537
+ };
1538
+ }
1539
+ //#endregion
1540
+ //#region src/components/dropdown-menu/dropdown-menu-root.vue
1541
+ var dropdown_menu_root_default = /* @__PURE__ */ defineVaporComponent({
1542
+ name: "RpDropdownMenuRoot",
1543
+ __name: "dropdown-menu-root",
1544
+ props: {
1545
+ id: {},
1546
+ open: {
1547
+ type: Boolean,
1548
+ default: void 0
1549
+ },
1550
+ defaultOpen: {
1551
+ type: Boolean,
1552
+ default: false
1073
1553
  },
1074
1554
  baseZIndex: {},
1075
1555
  disabled: {
@@ -1089,8 +1569,12 @@ var dropdown_menu_root_default = /* @__PURE__ */ defineVaporComponent({
1089
1569
  const emit = __emit;
1090
1570
  const generatedId = useId();
1091
1571
  const id = computed(() => props.id ?? `${generatedId}-dropdown-menu`);
1092
- const uncontrolledOpen = ref(props.defaultOpen);
1093
- const isOpen = computed(() => props.open ?? uncontrolledOpen.value);
1572
+ const controllableOpen = useControllableValue({
1573
+ modelValue: () => props.open,
1574
+ defaultValue: () => props.defaultOpen,
1575
+ onChange: (nextOpen) => emit("update:open", nextOpen)
1576
+ });
1577
+ const isOpen = controllableOpen.value;
1094
1578
  const disabled = computed(() => props.disabled);
1095
1579
  const modal = computed(() => props.modal);
1096
1580
  const trigger = ref(null);
@@ -1099,9 +1583,7 @@ var dropdown_menu_root_default = /* @__PURE__ */ defineVaporComponent({
1099
1583
  const activeReference = shallowRef(null);
1100
1584
  const { reference: configuredReference } = useFloatingTarget(() => props.target ?? props.virtualAnchor, trigger);
1101
1585
  const reference = computed(() => activeReference.value ?? configuredReference.value);
1102
- const pendingFocus = ref("first");
1103
1586
  const contentElement = ref(null);
1104
- const inside = /* @__PURE__ */ new Set();
1105
1587
  let returnFocusElement = null;
1106
1588
  const baseZIndex = useOverlayZIndex({
1107
1589
  baseZIndex: () => props.baseZIndex,
@@ -1115,43 +1597,37 @@ var dropdown_menu_root_default = /* @__PURE__ */ defineVaporComponent({
1115
1597
  });
1116
1598
  function setOpen(value) {
1117
1599
  if (value && disabled.value) return;
1118
- const previous = isOpen.value;
1119
- if (props.open === void 0) uncontrolledOpen.value = value;
1120
- if (previous !== value) emit("update:open", value);
1600
+ if (isOpen.value !== value) controllableOpen.setValue(value);
1121
1601
  }
1122
1602
  function rememberFocus() {
1123
1603
  if (typeof document === "undefined" || returnFocusElement) return;
1124
1604
  const activeElement = document.activeElement;
1125
1605
  if (activeElement instanceof HTMLElement) returnFocusElement = activeElement;
1126
1606
  }
1127
- function open(options) {
1128
- if (disabled.value) return;
1129
- rememberFocus();
1130
- pendingFocus.value = getFocusTarget(options);
1131
- setOpen(true);
1132
- }
1133
1607
  function focusReturnTarget() {
1134
1608
  const target = returnFocusElement ?? trigger.value;
1135
1609
  nextTick(() => target?.focus());
1136
1610
  }
1137
- function close(options = {}) {
1138
- setOpen(false);
1139
- pendingFocus.value = false;
1140
- if (options.focusTrigger || options.returnFocus) focusReturnTarget();
1141
- }
1142
- function toggle() {
1143
- if (isOpen.value) close({ returnFocus: true });
1144
- else open();
1145
- }
1611
+ const interaction = useDropdownMenuInteraction({
1612
+ rootMenuId: `${generatedId}-interaction-root`,
1613
+ isOpen,
1614
+ disabled,
1615
+ modal,
1616
+ setOpen,
1617
+ isTopLayer: layer.isTopLayer,
1618
+ focusTrigger: focusReturnTarget,
1619
+ beforeOpen: rememberFocus
1620
+ });
1621
+ const { pendingRootFocus: pendingFocus, open, close, toggle } = interaction;
1146
1622
  function openAt(point, options) {
1147
1623
  activeReference.value = createVirtualAnchor(point);
1148
1624
  open(options);
1149
1625
  }
1150
1626
  function setTrigger(element, nextId) {
1151
- if (trigger.value && trigger.value !== element) inside.delete(trigger.value);
1627
+ if (trigger.value && trigger.value !== element) interaction.unregisterInside(trigger.value);
1152
1628
  trigger.value = element;
1153
1629
  triggerId.value = nextId;
1154
- if (element) inside.add(element);
1630
+ if (element) interaction.registerInside(element);
1155
1631
  }
1156
1632
  const context = {
1157
1633
  id,
@@ -1164,6 +1640,7 @@ var dropdown_menu_root_default = /* @__PURE__ */ defineVaporComponent({
1164
1640
  reference,
1165
1641
  pendingFocus,
1166
1642
  layer,
1643
+ interaction,
1167
1644
  open,
1168
1645
  close,
1169
1646
  toggle,
@@ -1176,13 +1653,10 @@ var dropdown_menu_root_default = /* @__PURE__ */ defineVaporComponent({
1176
1653
  returnFocusElement = element;
1177
1654
  },
1178
1655
  registerInside(element) {
1179
- inside.add(element);
1656
+ interaction.registerInside(element);
1180
1657
  },
1181
1658
  unregisterInside(element) {
1182
- inside.delete(element);
1183
- },
1184
- isInside(event) {
1185
- return [...inside].some((element) => isEventWithinElement(event, element));
1659
+ interaction.unregisterInside(element);
1186
1660
  }
1187
1661
  };
1188
1662
  const slotProps = computed(() => ({
@@ -1192,9 +1666,6 @@ var dropdown_menu_root_default = /* @__PURE__ */ defineVaporComponent({
1192
1666
  toggle,
1193
1667
  openAt
1194
1668
  }));
1195
- watch(disabled, (value) => {
1196
- if (value) close();
1197
- });
1198
1669
  watch(isOpen, (value) => {
1199
1670
  if (!value) activeReference.value = null;
1200
1671
  }, { flush: "sync" });
@@ -1253,48 +1724,47 @@ var dropdown_menu_sub_default = /* @__PURE__ */ defineVaporComponent({
1253
1724
  const props = __props;
1254
1725
  const emit = __emit;
1255
1726
  const parentMenu = useRequiredInject(menuKey, "RpDropdownMenuSub");
1256
- const uncontrolled = ref(props.defaultOpen);
1257
- const isOpen = computed(() => props.open ?? uncontrolled.value);
1727
+ const menuId = `${useId()}-interaction-menu`;
1728
+ const controllableOpen = useControllableValue({
1729
+ modelValue: () => props.open,
1730
+ defaultValue: () => props.defaultOpen,
1731
+ onChange: (open) => emit("update:open", open)
1732
+ });
1733
+ const isOpen = controllableOpen.value;
1258
1734
  const trigger = ref(null);
1259
1735
  const contentId = ref();
1260
1736
  const actualPlacement = ref("right-start");
1261
1737
  const pendingFocus = ref(false);
1262
1738
  function setOpen(value) {
1263
- const previous = isOpen.value;
1264
- if (props.open === void 0) uncontrolled.value = value;
1265
- if (previous !== value) emit("update:open", value);
1739
+ if (isOpen.value !== value) controllableOpen.setValue(value);
1266
1740
  }
1741
+ const cleanupMenuState = parentMenu.root.interaction.registerMenuState({
1742
+ id: menuId,
1743
+ isOpen: () => isOpen.value,
1744
+ setOpen
1745
+ });
1267
1746
  const context = {
1747
+ menuId,
1268
1748
  isOpen,
1269
1749
  trigger,
1270
1750
  contentId,
1271
1751
  actualPlacement,
1272
1752
  pendingFocus,
1273
1753
  menu: null,
1754
+ setOpen,
1274
1755
  open(focus = "first") {
1275
- parentMenu.closeSubmenus(context);
1276
1756
  pendingFocus.value = focus;
1277
- setOpen(true);
1278
- if (focus) nextTick(() => context.menu?.focus(focus));
1757
+ if (!parentMenu.root.interaction.openMenu(menuId, focus)) setOpen(true);
1279
1758
  },
1280
1759
  close(focusParent = false) {
1281
- setOpen(false);
1282
1760
  pendingFocus.value = false;
1283
- context.menu?.closeSubmenus();
1284
- if (focusParent) {
1285
- parentMenu.setActive(trigger.value?.id ?? "");
1286
- parentMenu.focusElement();
1287
- }
1761
+ if (!parentMenu.root.interaction.closeMenu(menuId, focusParent)) setOpen(false);
1288
1762
  }
1289
1763
  };
1290
- parentMenu.registerSub(context);
1291
1764
  provide(subKey, context);
1292
- watch(parentMenu.root.isOpen, (open) => {
1293
- if (!open) context.close(false);
1294
- });
1295
- onBeforeUnmount(() => parentMenu.unregisterSub(context));
1765
+ onBeforeUnmount(cleanupMenuState);
1296
1766
  return createSlot("default", {
1297
- isOpen: () => isOpen.value,
1767
+ isOpen: () => unref(isOpen),
1298
1768
  open: () => context.open,
1299
1769
  close: () => context.close
1300
1770
  });
@@ -1390,10 +1860,17 @@ var dropdown_menu_sub_trigger_default = /* @__PURE__ */ defineVaporComponent({
1390
1860
  element: () => sub.trigger.value,
1391
1861
  textValue: () => props.textValue ?? sub.trigger.value?.textContent?.trim() ?? "",
1392
1862
  disabled: () => isDisabled.value,
1393
- activate: () => sub.open("first"),
1863
+ activate: () => void 0,
1394
1864
  submenu: sub
1395
1865
  };
1396
1866
  menu.registerItem(registration);
1867
+ watch(id, (nextId, previousId) => {
1868
+ const wasActive = menu.activeId.value === previousId;
1869
+ menu.unregisterItem(previousId);
1870
+ menu.registerItem(registration);
1871
+ if (wasActive) menu.setActive(nextId);
1872
+ }, { flush: "sync" });
1873
+ watch(isDisabled, () => menu.root.interaction.reconcile(menu.id));
1397
1874
  onBeforeUnmount(() => menu.unregisterItem(id.value));
1398
1875
  function setElement(value) {
1399
1876
  resolveHTMLElementRef(value, id.value, (resolved) => {
@@ -1402,25 +1879,24 @@ var dropdown_menu_sub_trigger_default = /* @__PURE__ */ defineVaporComponent({
1402
1879
  }
1403
1880
  function onMouseenter() {
1404
1881
  if (isDisabled.value) return;
1405
- menu.setActive(id.value);
1406
- sub.open(false);
1882
+ menu.hover(id.value, true);
1407
1883
  }
1408
1884
  function onClick(event) {
1409
- if (!event.defaultPrevented && !isDisabled.value) sub.open("first");
1885
+ if (!event.defaultPrevented && !isDisabled.value) menu.activate(id.value, event);
1410
1886
  }
1411
1887
  const rootAttrs = computed(() => mergeProps(attrs, {
1412
1888
  id: id.value,
1413
1889
  type: props.as === "button" ? "button" : void 0,
1414
1890
  role: "menuitem",
1415
1891
  tabindex: -1,
1416
- disabled: props.as === "button" ? optionalAttr(isDisabled.value) : void 0,
1417
- "aria-disabled": optionalAttr(isDisabled.value),
1892
+ disabled: props.as === "button" ? toOptionalAttribute(isDisabled.value) : void 0,
1893
+ "aria-disabled": toOptionalAttribute(isDisabled.value),
1418
1894
  "aria-haspopup": "menu",
1419
1895
  "aria-expanded": sub.isOpen.value,
1420
1896
  "aria-controls": sub.contentId.value,
1421
- "data-disabled": optionalAttr(isDisabled.value),
1422
- "data-focused": optionalAttr(focused.value),
1423
- "data-highlighted": optionalAttr(focused.value),
1897
+ "data-disabled": toOptionalAttribute(isDisabled.value),
1898
+ "data-focused": toOptionalAttribute(focused.value),
1899
+ "data-highlighted": toOptionalAttribute(focused.value),
1424
1900
  "data-state": sub.isOpen.value ? "open" : "closed",
1425
1901
  class: bem("rp-dropdown-menu__item", {
1426
1902
  focused: focused.value,
@@ -1472,41 +1948,28 @@ var dropdown_menu_trigger_default = /* @__PURE__ */ defineVaporComponent({
1472
1948
  root.setTrigger(resolved, id.value);
1473
1949
  });
1474
1950
  }
1475
- function openWithReference(focus = "first") {
1951
+ function prepareReference() {
1476
1952
  root.setReference(element.value);
1477
1953
  root.setReturnFocus(element.value);
1478
- root.open({ focus });
1479
1954
  }
1480
1955
  function onClick(event) {
1481
1956
  if (event.defaultPrevented || isDisabled.value) return;
1482
- root.setReference(element.value);
1483
- root.setReturnFocus(element.value);
1484
- root.toggle();
1957
+ root.interaction.onTriggerClick(prepareReference);
1485
1958
  }
1486
1959
  function onKeydown(event) {
1487
1960
  if (isDisabled.value) return;
1488
- if (event.key === "ArrowUp") {
1489
- event.preventDefault();
1490
- openWithReference("last");
1491
- } else if ([
1492
- "Enter",
1493
- " ",
1494
- "ArrowDown"
1495
- ].includes(event.key)) {
1496
- event.preventDefault();
1497
- openWithReference("first");
1498
- } else if (event.key === "Escape") root.close({ focusTrigger: true });
1961
+ root.interaction.onTriggerKeydown(event, prepareReference);
1499
1962
  }
1500
1963
  const rootAttrs = computed(() => mergeProps(attrs, {
1501
1964
  id: id.value,
1502
1965
  type: props.as === "button" ? "button" : void 0,
1503
- disabled: props.as === "button" ? optionalAttr(isDisabled.value) : void 0,
1966
+ disabled: props.as === "button" ? toOptionalAttribute(isDisabled.value) : void 0,
1504
1967
  "aria-controls": root.contentId.value,
1505
1968
  "aria-expanded": root.isOpen.value,
1506
1969
  "aria-haspopup": "menu",
1507
- "aria-disabled": props.as === "button" ? void 0 : optionalAttr(isDisabled.value),
1970
+ "aria-disabled": props.as === "button" ? void 0 : toOptionalAttribute(isDisabled.value),
1508
1971
  "data-state": root.isOpen.value ? "open" : "closed",
1509
- "data-disabled": optionalAttr(isDisabled.value),
1972
+ "data-disabled": toOptionalAttribute(isDisabled.value),
1510
1973
  onClick,
1511
1974
  onKeydown
1512
1975
  }));
@@ -1518,31 +1981,10 @@ var dropdown_menu_trigger_default = /* @__PURE__ */ defineVaporComponent({
1518
1981
  return n1;
1519
1982
  }
1520
1983
  });
1521
- var DEFAULT_FOCUS_TARGET = "first";
1522
- function getOpenFocusTarget(options) {
1523
- if (typeof options === "string") return options;
1524
- return options?.focus ?? "first";
1525
- }
1526
- function getItemClass(item, focused, disabled, hasSubmenu, submenuOpen) {
1527
- return bem("rp-dropdown-menu__item", {
1528
- focused,
1529
- disabled,
1530
- destructive: item.destructive,
1531
- submenu: hasSubmenu,
1532
- open: submenuOpen
1533
- });
1534
- }
1535
- function hasItemSubmenu(item) {
1536
- return (item.children?.length ?? 0) > 0;
1537
- }
1538
- function hasNestedItems(items) {
1539
- return items.some(hasItemSubmenu);
1540
- }
1541
- function getPathKey(path) {
1542
- return path.length === 0 ? "root" : path.join("-");
1543
- }
1544
- function getParentPath(path) {
1545
- return path.slice(0, -1);
1984
+ //#endregion
1985
+ //#region src/utils/indexPath.ts
1986
+ function getPathKey(path) {
1987
+ return path.length === 0 ? "root" : path.join("-");
1546
1988
  }
1547
1989
  function arePathsEqual(a, b) {
1548
1990
  return a.length === b.length && a.every((part, index) => part === b[index]);
@@ -1553,17 +1995,21 @@ function isPathPrefix(prefix, path) {
1553
1995
  function normalizePath(indexOrPath) {
1554
1996
  return Array.isArray(indexOrPath) ? indexOrPath : [indexOrPath];
1555
1997
  }
1556
- function getEventPoint(event) {
1557
- return {
1558
- x: event.clientX,
1559
- y: event.clientY
1560
- };
1998
+ var DEFAULT_FOCUS_TARGET = "first";
1999
+ function getItemClass(item, focused, disabled, hasSubmenu, submenuOpen) {
2000
+ return bem("rp-dropdown-menu__item", {
2001
+ focused,
2002
+ disabled,
2003
+ destructive: item.destructive,
2004
+ submenu: hasSubmenu,
2005
+ open: submenuOpen
2006
+ });
1561
2007
  }
1562
- function hasMeasuredRect(rect) {
1563
- return rect.width > 0 || rect.height > 0;
2008
+ function hasItemSubmenu(item) {
2009
+ return (item.children?.length ?? 0) > 0;
1564
2010
  }
1565
- function isPointInRect(point, rect, padding = 0) {
1566
- return point.x >= rect.left - padding && point.x <= rect.right + padding && point.y >= rect.top - padding && point.y <= rect.bottom + padding;
2011
+ function hasNestedItems(items) {
2012
+ return items.some(hasItemSubmenu);
1567
2013
  }
1568
2014
  //#endregion
1569
2015
  //#region src/components/dropdown-menu/dropdown-menu-item-row.vue?vue&type=script&setup=true&vapor=true&lang.ts
@@ -1595,6 +2041,10 @@ var dropdown_menu_item_row_default = /* @__PURE__ */ defineVaporComponent({
1595
2041
  const slotProps = computed(() => props.context.getItemSlotProps(props.item, path.value, focused.value, disabled.value, submenuOpen.value));
1596
2042
  const itemElement = ref(null);
1597
2043
  const submenuElement = ref(null);
2044
+ const templateRefs = {
2045
+ item: itemElement,
2046
+ submenu: submenuElement
2047
+ };
1598
2048
  const { actualPlacement, floatingStyle } = useFloatingPosition({
1599
2049
  reference: itemElement,
1600
2050
  floating: submenuElement,
@@ -1620,14 +2070,13 @@ var dropdown_menu_item_row_default = /* @__PURE__ */ defineVaporComponent({
1620
2070
  function getChildKey(index, child) {
1621
2071
  return `${getPathKey([...path.value, index])}:${String(child.value)}`;
1622
2072
  }
1623
- const _setTemplateRef = createTemplateRefSetter();
1624
2073
  const _component_DropdownMenuItemRow__self = resolveComponent("DropdownMenuItemRow", true);
1625
2074
  const n10 = t1$1();
1626
2075
  const n1 = child(n10);
1627
2076
  renderEffect(() => setDynamicProps(n1, [publicItemProps.value]));
1628
2077
  setInsertionState(n1, null, 0);
1629
2078
  createSlot("item", { $: [() => slotProps.value] });
1630
- setStaticTemplateRef(n1, itemElement, null, "itemElement");
2079
+ setTemplateRefBinding(n1, () => templateRefs.item);
1631
2080
  setInsertionState(n10, null, 1);
1632
2081
  createIf(() => hasSubmenu.value && submenuOpen.value, () => {
1633
2082
  const n9 = t0$1();
@@ -1647,7 +2096,7 @@ var dropdown_menu_item_row_default = /* @__PURE__ */ defineVaporComponent({
1647
2096
  return createSlot("item", { $: [() => childSlotProps] });
1648
2097
  }, { _: 8 }) });
1649
2098
  }, (child, childIndex) => getChildKey(childIndex, child), 3);
1650
- _setTemplateRef(n9, submenuElement, null, "submenuElement");
2099
+ setTemplateRefBinding(n9, () => templateRefs.submenu);
1651
2100
  return n9;
1652
2101
  });
1653
2102
  return n10;
@@ -1681,153 +2130,167 @@ var dropdown_menu_items_default = /* @__PURE__ */ defineVaporComponent({
1681
2130
  }
1682
2131
  });
1683
2132
  //#endregion
1684
- //#region src/components/dropdown-menu/useDropdownMenuDisclosure.ts
1685
- function useDropdownMenuDisclosure({ props, emit, rootRef, menuRef, targetRef, uncontrolledOpen, isDisabled, isOpen, isVisible, focusedPath, resetFocus, resetSubmenus, resetHoverIntent, focusItem, focusMenu, focusTrigger, layer }) {
1686
- function setOpen(nextOpen) {
1687
- if (nextOpen && isDisabled.value) return;
1688
- const previousOpen = isOpen.value;
1689
- if (props.open === void 0) uncontrolledOpen.value = nextOpen;
1690
- if (previousOpen !== nextOpen) emit.openChange?.(nextOpen);
1691
- }
1692
- function resetMenuFocus() {
1693
- resetFocus();
1694
- resetSubmenus();
1695
- resetHoverIntent();
1696
- }
1697
- function open(options) {
1698
- if (isDisabled.value) return;
1699
- setOpen(true);
1700
- focusItem(getOpenFocusTarget(options));
1701
- resetSubmenus();
1702
- resetHoverIntent();
1703
- focusMenu();
1704
- }
1705
- function close(options = {}) {
1706
- setOpen(false);
1707
- resetMenuFocus();
1708
- if (options.focusTrigger) focusTrigger();
1709
- }
1710
- function toggle() {
1711
- if (isVisible.value) close({ focusTrigger: true });
1712
- else open();
1713
- }
1714
- watch(isDisabled, (disabled) => {
1715
- if (disabled) close();
1716
- });
1717
- watch(isVisible, (visible) => {
1718
- if (visible) {
1719
- if (focusedPath.value.length === 0) focusItem(DEFAULT_FOCUS_TARGET);
1720
- focusMenu();
1721
- } else resetMenuFocus();
1722
- });
1723
- function emitOutside(type, originalEvent) {
1724
- const outsideEvent = createOutsideEvent(originalEvent);
1725
- if (type === "pointer") emit.pointerDownOutside?.(outsideEvent);
1726
- else emit.focusOutside?.(outsideEvent);
1727
- emit.interactOutside?.(outsideEvent);
1728
- return outsideEvent;
1729
- }
1730
- function isInside(event) {
1731
- return isEventWithinTargets(event, [
1732
- rootRef,
1733
- menuRef,
1734
- targetRef,
1735
- ...props.ignore ?? []
1736
- ]);
1737
- }
1738
- function blockModalInteraction(event) {
1739
- if (!props.modal) return;
1740
- if (event.cancelable) event.preventDefault();
1741
- event.stopPropagation();
1742
- if (event.type === "pointerdown") blockNextDocumentClick();
1743
- }
1744
- function onDocumentPointerdown(event) {
1745
- if (!layer.isTopLayer()) return;
1746
- if (isInside(event)) return;
1747
- const outsideEvent = emitOutside("pointer", event);
1748
- blockModalInteraction(event);
1749
- if (!outsideEvent.defaultPrevented) close({ focusTrigger: Boolean(props.modal) });
1750
- }
1751
- function onDocumentFocus(event) {
1752
- if (!layer.isTopLayer()) return;
1753
- if (isInside(event)) return;
1754
- const outsideEvent = emitOutside("focus", event);
1755
- blockModalInteraction(event);
1756
- if (!outsideEvent.defaultPrevented) close({ focusTrigger: Boolean(props.modal) });
1757
- else if (props.modal) focusMenu();
1758
- }
1759
- let isListening = false;
1760
- function setDocumentListeners(active) {
1761
- if (typeof document === "undefined" || active === isListening) return;
1762
- isListening = active;
1763
- const method = active ? "addEventListener" : "removeEventListener";
1764
- document[method]("pointerdown", onDocumentPointerdown, true);
1765
- document[method]("focusin", onDocumentFocus, true);
2133
+ //#region src/components/dropdown-menu/dropdownMenuDataModel.ts
2134
+ function createDataIndexState(rootMenuId) {
2135
+ return {
2136
+ entries: [],
2137
+ itemIdByPath: /* @__PURE__ */ new Map(),
2138
+ pathByItemId: /* @__PURE__ */ new Map(),
2139
+ menuIdByPath: /* @__PURE__ */ new Map([["root", rootMenuId]]),
2140
+ pathByMenuId: /* @__PURE__ */ new Map([[rootMenuId, []]]),
2141
+ primaryItemId: /* @__PURE__ */ new WeakMap(),
2142
+ occurrences: /* @__PURE__ */ new Map()
2143
+ };
2144
+ }
2145
+ function getOccurrenceItemId(item, state, getStableItemId) {
2146
+ const occurrence = state.occurrences.get(item) ?? 0;
2147
+ state.occurrences.set(item, occurrence + 1);
2148
+ const stableItemId = getStableItemId(item);
2149
+ return {
2150
+ occurrence,
2151
+ itemId: occurrence === 0 ? stableItemId : `${stableItemId}-copy-${occurrence}`
2152
+ };
2153
+ }
2154
+ function indexItems(items, parentPath, ownerMenuId, state, getStableItemId) {
2155
+ for (const [order, item] of items.entries()) {
2156
+ const path = [...parentPath, order];
2157
+ const pathKey = getPathKey(path);
2158
+ const { itemId, occurrence } = getOccurrenceItemId(item, state, getStableItemId);
2159
+ const submenuMenuId = hasItemSubmenu(item) ? `${itemId}-menu` : void 0;
2160
+ state.entries.push({
2161
+ item,
2162
+ path,
2163
+ itemId,
2164
+ ownerMenuId,
2165
+ order,
2166
+ submenuMenuId
2167
+ });
2168
+ state.itemIdByPath.set(pathKey, itemId);
2169
+ state.pathByItemId.set(itemId, path);
2170
+ if (occurrence === 0) state.primaryItemId.set(item, itemId);
2171
+ if (!submenuMenuId) continue;
2172
+ state.menuIdByPath.set(pathKey, submenuMenuId);
2173
+ state.pathByMenuId.set(submenuMenuId, path);
2174
+ indexItems(item.children ?? [], path, submenuMenuId, state, getStableItemId);
1766
2175
  }
1767
- watch(isVisible, setDocumentListeners, { immediate: true });
1768
- onBeforeUnmount(() => setDocumentListeners(false));
2176
+ }
2177
+ function buildDropdownMenuDataIndex(items, rootMenuId, getStableItemId) {
2178
+ const state = createDataIndexState(rootMenuId);
2179
+ indexItems(items, [], rootMenuId, state, getStableItemId);
1769
2180
  return {
1770
- open,
1771
- close,
1772
- toggle,
1773
- resetMenuFocus
2181
+ entries: state.entries,
2182
+ itemIdByPath: state.itemIdByPath,
2183
+ pathByItemId: state.pathByItemId,
2184
+ menuIdByPath: state.menuIdByPath,
2185
+ pathByMenuId: state.pathByMenuId,
2186
+ primaryItemId: state.primaryItemId
1774
2187
  };
1775
2188
  }
2189
+ function getDropdownMenuDataCollectionState(items) {
2190
+ const state = [];
2191
+ function collect(currentItems) {
2192
+ state.push(currentItems.length);
2193
+ for (const item of currentItems) {
2194
+ const children = item.children;
2195
+ state.push(item, Boolean(item.disabled), children);
2196
+ if (children) collect(children);
2197
+ }
2198
+ }
2199
+ collect(items);
2200
+ return state;
2201
+ }
1776
2202
  //#endregion
1777
- //#region src/components/dropdown-menu/useDropdownMenuDom.ts
1778
- function useDropdownMenuDom({ menuId, rootRef, menuRef, targetRef, placement, focusedPath }) {
1779
- const activeDescendantId = computed(() => focusedPath.value.length === 0 ? void 0 : getItemId(focusedPath.value));
1780
- function focusMenu() {
1781
- nextTick(() => menuRef.value?.focus());
2203
+ //#region src/components/dropdown-menu/useDropdownMenuDataRegistration.ts
2204
+ function getCurrentPath(index, entry) {
2205
+ return index.pathByItemId.get(entry.itemId) ?? entry.path;
2206
+ }
2207
+ function useDropdownMenuDataRegistration({ props, items, rootMenuId, interaction, menuRef, getItemElement, getSubmenuElement, isSubmenuOpeningLeft, onSelect }) {
2208
+ const openMenuIds = ref(/* @__PURE__ */ new Set());
2209
+ const itemIdentity = /* @__PURE__ */ new WeakMap();
2210
+ let nextItemIdentity = 0;
2211
+ let registeredItemIds = /* @__PURE__ */ new Set();
2212
+ let registeredMenuIds = /* @__PURE__ */ new Set();
2213
+ function getStableItemId(item) {
2214
+ const existing = itemIdentity.get(item);
2215
+ if (existing) return existing;
2216
+ nextItemIdentity += 1;
2217
+ const id = `${rootMenuId}-item-${nextItemIdentity}`;
2218
+ itemIdentity.set(item, id);
2219
+ return id;
2220
+ }
2221
+ const registrationIndex = shallowRef(buildDropdownMenuDataIndex([], rootMenuId, getStableItemId));
2222
+ const collectionState = computed(() => getDropdownMenuDataCollectionState(items.value));
2223
+ function setMenuOpen(id, open) {
2224
+ const next = new Set(openMenuIds.value);
2225
+ if (open) next.add(id);
2226
+ else next.delete(id);
2227
+ openMenuIds.value = next;
2228
+ }
2229
+ function createMenuRegistration(index, entry) {
2230
+ const submenuMenuId = entry.submenuMenuId;
2231
+ if (!submenuMenuId) return void 0;
2232
+ return {
2233
+ id: submenuMenuId,
2234
+ parentItemId: () => entry.itemId,
2235
+ element: () => getSubmenuElement(getCurrentPath(index, entry)),
2236
+ focusTarget: () => menuRef.value,
2237
+ placement: () => {
2238
+ return getSubmenuElement(getCurrentPath(index, entry))?.dataset.placement ?? "right-start";
2239
+ },
2240
+ isOpen: () => openMenuIds.value.has(submenuMenuId),
2241
+ setOpen: (open) => setMenuOpen(submenuMenuId, open)
2242
+ };
1782
2243
  }
1783
- function focusTrigger() {
1784
- nextTick(() => {
1785
- if (targetRef.value instanceof HTMLElement) {
1786
- targetRef.value.focus();
1787
- return;
2244
+ function createSelectEvent(entry, originalEvent) {
2245
+ const event = new CustomEvent("dropdown-menu-select", {
2246
+ cancelable: true,
2247
+ detail: {
2248
+ originalEvent,
2249
+ value: entry.item.value
1788
2250
  }
1789
- (rootRef.value?.querySelector("[aria-haspopup=\"menu\"]"))?.focus();
2251
+ });
2252
+ onSelect?.(entry.item, event);
2253
+ return event;
2254
+ }
2255
+ function registerEntry(index, entry) {
2256
+ const menuRegistration = createMenuRegistration(index, entry);
2257
+ if (menuRegistration) interaction.registerMenu(menuRegistration);
2258
+ interaction.registerItem({
2259
+ id: entry.itemId,
2260
+ menuId: entry.ownerMenuId,
2261
+ element: () => getItemElement(getCurrentPath(index, entry)),
2262
+ textValue: () => entry.item.label,
2263
+ disabled: () => Boolean(entry.item.disabled || props.disabled),
2264
+ order: () => entry.order,
2265
+ submenuId: () => entry.submenuMenuId,
2266
+ submenuDirection: () => isSubmenuOpeningLeft(getCurrentPath(index, entry)) ? "left" : "right",
2267
+ select: (originalEvent) => createSelectEvent(entry, originalEvent),
2268
+ closeOnSelect: () => props.closeOnSelect !== false
1790
2269
  });
1791
2270
  }
1792
- function getItemId(indexOrPath) {
1793
- return `${menuId.value}-item-${getPathKey(normalizePath(indexOrPath))}`;
1794
- }
1795
- function getSubmenuId(path) {
1796
- return `${menuId.value}-submenu-${getPathKey(path)}`;
1797
- }
1798
- function getItemElement(path) {
1799
- return document.getElementById(getItemId(path));
1800
- }
1801
- function getSubmenuElement(path) {
1802
- return document.getElementById(getSubmenuId(path));
1803
- }
1804
- function isSubmenuOpeningLeft(path) {
1805
- const submenuRect = getSubmenuElement(path)?.getBoundingClientRect();
1806
- const itemRect = getItemElement(path)?.getBoundingClientRect();
1807
- if (submenuRect && itemRect && hasMeasuredRect(submenuRect) && hasMeasuredRect(itemRect)) return submenuRect.right <= itemRect.left;
1808
- return placement.value.endsWith("end");
1809
- }
1810
- function getMenuActiveDescendant(menuPath) {
1811
- return arePathsEqual(getParentPath(focusedPath.value), menuPath) ? getItemId(focusedPath.value) : void 0;
2271
+ function unregisterStaleEntries(nextRegisteredItemIds, nextRegisteredMenuIds) {
2272
+ for (const id of registeredItemIds) if (!nextRegisteredItemIds.has(id)) interaction.unregisterItem(id);
2273
+ for (const id of registeredMenuIds) {
2274
+ if (nextRegisteredMenuIds.has(id)) continue;
2275
+ interaction.unregisterMenu(id);
2276
+ setMenuOpen(id, false);
2277
+ }
1812
2278
  }
1813
- watch(focusedPath, (path) => {
1814
- if (path.length === 0) return;
1815
- const targetPath = [...path];
1816
- nextTick(() => {
1817
- if (!arePathsEqual(targetPath, focusedPath.value)) return;
1818
- getItemElement(targetPath)?.scrollIntoView?.({ block: "nearest" });
1819
- });
1820
- });
2279
+ function registerItems() {
2280
+ const nextIndex = buildDropdownMenuDataIndex(items.value, rootMenuId, getStableItemId);
2281
+ const nextRegisteredItemIds = new Set(nextIndex.entries.map((entry) => entry.itemId));
2282
+ const nextRegisteredMenuIds = new Set(nextIndex.entries.flatMap((entry) => entry.submenuMenuId ? [entry.submenuMenuId] : []));
2283
+ for (const entry of nextIndex.entries) registerEntry(nextIndex, entry);
2284
+ unregisterStaleEntries(nextRegisteredItemIds, nextRegisteredMenuIds);
2285
+ registeredItemIds = nextRegisteredItemIds;
2286
+ registeredMenuIds = nextRegisteredMenuIds;
2287
+ registrationIndex.value = nextIndex;
2288
+ interaction.reconcile();
2289
+ }
2290
+ watch(collectionState, registerItems, { immediate: true });
1821
2291
  return {
1822
- activeDescendantId,
1823
- focusMenu,
1824
- focusTrigger,
1825
- getItemId,
1826
- getSubmenuId,
1827
- getItemElement,
1828
- getSubmenuElement,
1829
- isSubmenuOpeningLeft,
1830
- getMenuActiveDescendant
2292
+ openMenuIds,
2293
+ registrationIndex
1831
2294
  };
1832
2295
  }
1833
2296
  //#endregion
@@ -1835,26 +2298,6 @@ function useDropdownMenuDom({ menuId, rootRef, menuRef, targetRef, placement, fo
1835
2298
  var SAFE_TRIANGLE_PADDING = 2;
1836
2299
  var SAFE_TRIANGLE_ORIGIN_OFFSET = 2;
1837
2300
  var SAFE_TRIANGLE_TIMEOUT = 400;
1838
- function getTriangleArea(a, b, c) {
1839
- return Math.abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2);
1840
- }
1841
- function isPointInTriangle(point, triangle) {
1842
- const [a, b, c] = triangle;
1843
- const area = getTriangleArea(a, b, c);
1844
- const area1 = getTriangleArea(point, b, c);
1845
- const area2 = getTriangleArea(a, point, c);
1846
- const area3 = getTriangleArea(a, b, point);
1847
- return Math.abs(area - (area1 + area2 + area3)) < .5;
1848
- }
1849
- function clamp(value, min, max) {
1850
- return Math.min(Math.max(value, min), max);
1851
- }
1852
- function clampPointToRect(point, rect) {
1853
- return {
1854
- x: clamp(point.x, rect.left, rect.right),
1855
- y: clamp(point.y, rect.top, rect.bottom)
1856
- };
1857
- }
1858
2301
  function getDropdownMenuSafeTriangle({ itemRect, submenuRect, origin }) {
1859
2302
  const measuredItemRect = itemRect && hasMeasuredRect(itemRect) ? itemRect : null;
1860
2303
  const opensRight = !measuredItemRect || submenuRect.left >= measuredItemRect.right;
@@ -1951,7 +2394,7 @@ function useDropdownMenuHoverIntent({ openSubmenuPath, getItemElement, getSubmen
1951
2394
  }, SAFE_TRIANGLE_TIMEOUT);
1952
2395
  }
1953
2396
  function handleItemHover(item, path, event, commit) {
1954
- const point = event ? getEventPoint(event) : void 0;
2397
+ const point = event ? getClientPoint(event) : void 0;
1955
2398
  latestPointerPoint = point ?? latestPointerPoint;
1956
2399
  if (point && shouldDelayHover(path, point)) {
1957
2400
  delayHoveredItem(item, path, commit);
@@ -1960,7 +2403,7 @@ function useDropdownMenuHoverIntent({ openSubmenuPath, getItemElement, getSubmen
1960
2403
  commit(item, path, point);
1961
2404
  }
1962
2405
  function handleMenuMousemove(event, commit) {
1963
- const point = getEventPoint(event);
2406
+ const point = getClientPoint(event);
1964
2407
  latestPointerPoint = point;
1965
2408
  updateSafeTriangleOrigin(point);
1966
2409
  if (!pendingHover) return;
@@ -1978,221 +2421,131 @@ function useDropdownMenuHoverIntent({ openSubmenuPath, getItemElement, getSubmen
1978
2421
  };
1979
2422
  }
1980
2423
  //#endregion
1981
- //#region src/components/dropdown-menu/useDropdownMenuInteractions.ts
1982
- function useDropdownMenuInteractions({ props, emit, isDisabled, focusedPath, openSubmenuPath, getItemAtPath, focusItem, moveFocus, handleTypeahead, submenus, hoverIntent, disclosure, isSubmenuOpeningLeft }) {
1983
- function activateItem(item, path, event) {
1984
- if (item.disabled) return;
1985
- if (hasItemSubmenu(item)) {
1986
- openSubmenu(path, DEFAULT_FOCUS_TARGET, event instanceof MouseEvent ? getEventPoint(event) : void 0);
1987
- return;
1988
- }
1989
- selectItem(item, event);
2424
+ //#region src/components/dropdown-menu/useDropdownMenuDataController.ts
2425
+ function useDropdownMenuDataController({ props, items, rootMenuId, menuId, menuRef, actualPlacement, interaction, onSelect }) {
2426
+ function getItemId(path) {
2427
+ return `${menuId.value}-item-${getPathKey(path)}`;
1990
2428
  }
1991
- function selectItem(item, originalEvent = new Event("select")) {
1992
- if (item.disabled) return;
1993
- const event = new CustomEvent("dropdown-menu-select", {
1994
- cancelable: true,
1995
- detail: {
1996
- originalEvent,
1997
- value: item.value
1998
- }
1999
- });
2000
- emit.select?.(item, event);
2001
- if (!event.defaultPrevented && props.closeOnSelect !== false) disclosure.close({ focusTrigger: true });
2429
+ function getSubmenuId(path) {
2430
+ return `${menuId.value}-submenu-${getPathKey(path)}`;
2431
+ }
2432
+ function getItemElement(path) {
2433
+ return typeof document === "undefined" ? null : document.getElementById(getItemId(path));
2434
+ }
2435
+ function getSubmenuElement(path) {
2436
+ return typeof document === "undefined" ? null : document.getElementById(getSubmenuId(path));
2437
+ }
2438
+ function isSubmenuOpeningLeft(path) {
2439
+ const submenuRect = getSubmenuElement(path)?.getBoundingClientRect();
2440
+ const itemRect = getItemElement(path)?.getBoundingClientRect();
2441
+ if (submenuRect && itemRect && hasMeasuredRect(submenuRect) && hasMeasuredRect(itemRect)) return submenuRect.right <= itemRect.left;
2442
+ return actualPlacement.value.endsWith("end");
2443
+ }
2444
+ const { openMenuIds, registrationIndex } = useDropdownMenuDataRegistration({
2445
+ props,
2446
+ items,
2447
+ rootMenuId,
2448
+ interaction,
2449
+ menuRef,
2450
+ getItemElement,
2451
+ getSubmenuElement,
2452
+ isSubmenuOpeningLeft,
2453
+ onSelect
2454
+ });
2455
+ function getDataItemId(path) {
2456
+ return registrationIndex.value.itemIdByPath.get(getPathKey(path));
2002
2457
  }
2003
- function closeSubmenusAfter(menuPath) {
2004
- submenus.closeSubmenusAfter(menuPath);
2005
- hoverIntent.resetHoverIntent();
2458
+ const focusedPath = computed({
2459
+ get() {
2460
+ const id = interaction.activeItemId.value;
2461
+ return id ? [...registrationIndex.value.pathByItemId.get(id) ?? []] : [];
2462
+ },
2463
+ set(path) {
2464
+ interaction.setActive(path.length > 0 ? getDataItemId(path) ?? null : null);
2465
+ }
2466
+ });
2467
+ const focusedIndex = computed(() => focusedPath.value[0] ?? -1);
2468
+ const activeDescendantId = computed(() => focusedPath.value.length === 0 ? void 0 : getItemId(focusedPath.value));
2469
+ const openSubmenuPath = computed(() => {
2470
+ let deepest = [];
2471
+ for (const id of openMenuIds.value) {
2472
+ const path = registrationIndex.value.pathByMenuId.get(id);
2473
+ if (path && path.length > deepest.length) deepest = path;
2474
+ }
2475
+ return [...deepest];
2476
+ });
2477
+ function isItemFocused(indexOrPath) {
2478
+ const id = getDataItemId(normalizePath(indexOrPath));
2479
+ return Boolean(id && interaction.activeItemId.value === id);
2480
+ }
2481
+ function isSubmenuOpen(path) {
2482
+ const itemId = getDataItemId(path);
2483
+ const submenuId = itemId ? interaction.getItem(itemId)?.submenuId?.() : void 0;
2484
+ return Boolean(submenuId && interaction.isMenuOpen(submenuId));
2006
2485
  }
2007
2486
  function openSubmenu(path, focus = DEFAULT_FOCUS_TARGET, point) {
2008
- const opened = submenus.openSubmenu(path, focus);
2009
- if (opened) hoverIntent.trackSubmenuOpen(path, point);
2010
- return opened;
2487
+ const itemId = getDataItemId(path);
2488
+ const submenuId = itemId ? interaction.getItem(itemId)?.submenuId?.() : void 0;
2489
+ if (!submenuId || !interaction.openMenu(submenuId, focus)) return false;
2490
+ hoverIntent.trackSubmenuOpen(path, point);
2491
+ return true;
2011
2492
  }
2012
2493
  function closeSubmenu(path) {
2013
- const closed = submenus.closeSubmenu(path);
2014
- if (closed) hoverIntent.resetHoverIntent();
2015
- return closed;
2016
- }
2017
- function closeCurrentSubmenu() {
2018
- const closed = submenus.closeCurrentSubmenu();
2494
+ const itemId = getDataItemId(path);
2495
+ const submenuId = itemId ? interaction.getItem(itemId)?.submenuId?.() : void 0;
2496
+ const closed = submenuId ? interaction.closeMenu(submenuId, false) : false;
2019
2497
  if (closed) hoverIntent.resetHoverIntent();
2020
2498
  return closed;
2021
2499
  }
2022
2500
  function commitHoveredItem(item, path, point) {
2501
+ const itemId = getDataItemId(path);
2502
+ const itemRegistration = itemId ? interaction.getItem(itemId) : void 0;
2503
+ if (!itemRegistration) return;
2023
2504
  hoverIntent.clearPendingHover();
2024
- focusedPath.value = [...path];
2505
+ interaction.setActive(itemRegistration.id);
2025
2506
  if (hasItemSubmenu(item)) openSubmenu(path, false, point);
2026
- else closeSubmenusAfter(getParentPath(path));
2507
+ else interaction.closeSubmenus(itemRegistration.menuId);
2027
2508
  }
2509
+ const hoverIntent = useDropdownMenuHoverIntent({
2510
+ openSubmenuPath,
2511
+ getItemElement,
2512
+ getSubmenuElement
2513
+ });
2028
2514
  function focusHoveredItem(item, indexOrPath, event) {
2029
2515
  if (item.disabled) return;
2030
- const path = normalizePath(indexOrPath);
2031
- hoverIntent.handleItemHover(item, path, event, commitHoveredItem);
2032
- }
2033
- function selectFocusedItem(event) {
2034
- const item = getItemAtPath(focusedPath.value);
2035
- if (!item) return;
2036
- activateItem(item, focusedPath.value, event);
2037
- }
2038
- function moveFocusedItem(direction) {
2039
- const menuPath = moveFocus(direction);
2040
- if (menuPath) closeSubmenusAfter(menuPath);
2041
- }
2042
- function onTriggerClick() {
2043
- disclosure.toggle();
2044
- }
2045
- function onTriggerKeydown(event) {
2046
- if (isDisabled.value) return;
2047
- switch (event.key) {
2048
- case "Enter":
2049
- case " ":
2050
- case "ArrowDown":
2051
- event.preventDefault();
2052
- disclosure.open();
2053
- break;
2054
- case "ArrowUp":
2055
- event.preventDefault();
2056
- disclosure.open({ focus: "last" });
2057
- break;
2058
- case "Escape":
2059
- disclosure.close({ focusTrigger: true });
2060
- break;
2061
- }
2062
- }
2063
- function onMenuKeydown(event) {
2064
- hoverIntent.resetHoverIntent();
2065
- if (handleTypeahead(event)) return;
2066
- switch (event.key) {
2067
- case "ArrowDown":
2068
- event.preventDefault();
2069
- moveFocusedItem(1);
2070
- break;
2071
- case "ArrowUp":
2072
- event.preventDefault();
2073
- moveFocusedItem(-1);
2074
- break;
2075
- case "Home":
2076
- event.preventDefault();
2077
- focusItem("first", getParentPath(focusedPath.value));
2078
- break;
2079
- case "End":
2080
- event.preventDefault();
2081
- focusItem("last", getParentPath(focusedPath.value));
2082
- break;
2083
- case "ArrowRight":
2084
- case "ArrowLeft":
2085
- onMenuHorizontalKeydown(event);
2086
- break;
2087
- case "Enter":
2088
- case " ":
2089
- event.preventDefault();
2090
- selectFocusedItem(event);
2091
- break;
2092
- case "Escape":
2093
- event.preventDefault();
2094
- if (!closeCurrentSubmenu()) disclosure.close({ focusTrigger: true });
2095
- break;
2096
- case "Tab":
2097
- disclosure.close();
2098
- break;
2099
- }
2100
- }
2101
- function onMenuHorizontalKeydown(event) {
2102
- const isRight = event.key === "ArrowRight";
2103
- const currentMenuPath = getParentPath(focusedPath.value);
2104
- const closePath = focusedPath.value.length > 1 ? currentMenuPath : arePathsEqual(focusedPath.value, openSubmenuPath.value) ? focusedPath.value : void 0;
2105
- if (closePath && isRight === isSubmenuOpeningLeft(closePath)) {
2106
- event.preventDefault();
2107
- closeSubmenu(closePath);
2108
- return;
2109
- }
2110
- const item = getItemAtPath(focusedPath.value);
2111
- if (!item || item.disabled || !hasItemSubmenu(item)) return;
2112
- if (isRight === isSubmenuOpeningLeft(focusedPath.value)) return;
2113
- event.preventDefault();
2114
- openSubmenu(focusedPath.value);
2115
- }
2116
- function onMenuMousemove(event) {
2117
- hoverIntent.handleMenuMousemove(event, commitHoveredItem);
2118
- }
2119
- function onMenuMouseleave() {
2120
- hoverIntent.resetHoverIntent();
2121
- }
2122
- return {
2123
- activateItem,
2124
- selectItem,
2125
- openSubmenu,
2126
- closeSubmenu,
2127
- focusHoveredItem,
2128
- onTriggerClick,
2129
- onTriggerKeydown,
2130
- onMenuKeydown,
2131
- onMenuMousemove,
2132
- onMenuMouseleave
2133
- };
2134
- }
2135
- //#endregion
2136
- //#region src/components/dropdown-menu/useDropdownMenuNavigation.ts
2137
- function useDropdownMenuNavigation(items) {
2138
- const focusedPath = ref([]);
2139
- const focusedIndex = computed(() => focusedPath.value[0] ?? -1);
2140
- function getItemsAtPath(path) {
2141
- let currentItems = items.value;
2142
- for (const index of path) {
2143
- const item = currentItems[index];
2144
- if (!item) return [];
2145
- currentItems = item.children ?? [];
2146
- }
2147
- return currentItems;
2516
+ hoverIntent.handleItemHover(item, normalizePath(indexOrPath), event, commitHoveredItem);
2148
2517
  }
2149
- function getItemAtPath(path) {
2150
- if (path.length === 0) return void 0;
2151
- return getItemsAtPath(getParentPath(path))[path[path.length - 1]];
2152
- }
2153
- function focusItem(target, menuPath = []) {
2154
- const enabledIndexes = getEnabledIndexes(getItemsAtPath(menuPath), (item) => Boolean(item.disabled));
2155
- const index = target === "last" ? enabledIndexes[enabledIndexes.length - 1] : enabledIndexes[0];
2156
- if (index === void 0) {
2157
- if (menuPath.length === 0) focusedPath.value = [];
2158
- return false;
2159
- }
2160
- focusedPath.value = [...menuPath, index];
2161
- return true;
2518
+ function selectItem(item, originalEvent = new Event("select")) {
2519
+ const id = registrationIndex.value.primaryItemId.get(item);
2520
+ if (id) interaction.selectItem(id, originalEvent);
2162
2521
  }
2163
- function resetFocus() {
2164
- focusedPath.value = [];
2522
+ function activateItem(_item, path, event) {
2523
+ const id = getDataItemId(path);
2524
+ if (id) interaction.activateItem(id, event);
2165
2525
  }
2166
- function setFocusedIndex(index, menuPath = []) {
2167
- const item = getItemsAtPath(menuPath)[index];
2168
- if (!item || item.disabled) return false;
2169
- focusedPath.value = [...menuPath, index];
2170
- return true;
2171
- }
2172
- function isItemFocused(indexOrPath) {
2173
- return arePathsEqual(normalizePath(indexOrPath), focusedPath.value);
2174
- }
2175
- function moveFocus(direction) {
2176
- const menuPath = getParentPath(focusedPath.value);
2177
- const itemsAtPath = getItemsAtPath(menuPath);
2178
- const nextIndex = getNextEnabledIndex(itemsAtPath, focusedPath.value.length === 0 ? direction === 1 ? -1 : itemsAtPath.length : focusedPath.value[focusedPath.value.length - 1], direction, (item) => Boolean(item.disabled));
2179
- if (nextIndex === void 0) {
2180
- focusedPath.value = [];
2181
- return;
2182
- }
2183
- focusedPath.value = [...menuPath, nextIndex];
2184
- return menuPath;
2526
+ function getMenuActiveDescendant(path) {
2527
+ const pathKey = getPathKey(path);
2528
+ const activeId = interaction.getActiveId(registrationIndex.value.menuIdByPath.get(pathKey) ?? rootMenuId).value;
2529
+ const activePath = activeId ? registrationIndex.value.pathByItemId.get(activeId) : void 0;
2530
+ return activePath ? getItemId(activePath) : void 0;
2185
2531
  }
2186
2532
  return {
2187
2533
  focusedPath,
2188
2534
  focusedIndex,
2189
- getItemsAtPath,
2190
- getItemAtPath,
2191
- focusItem,
2192
- resetFocus,
2193
- setFocusedIndex,
2535
+ activeDescendantId,
2536
+ getItemId,
2537
+ getSubmenuId,
2538
+ getItemElement,
2539
+ getMenuActiveDescendant,
2194
2540
  isItemFocused,
2195
- moveFocus
2541
+ isSubmenuOpen,
2542
+ openSubmenu,
2543
+ closeSubmenu,
2544
+ focusHoveredItem,
2545
+ onMenuMousemove: (event) => hoverIntent.handleMenuMousemove(event, commitHoveredItem),
2546
+ resetHoverIntent: hoverIntent.resetHoverIntent,
2547
+ selectItem,
2548
+ activateItem
2196
2549
  };
2197
2550
  }
2198
2551
  //#endregion
@@ -2210,8 +2563,7 @@ function useDropdownMenuPortalPosition(options) {
2210
2563
  flipOptions: () => options.props.flipOptions,
2211
2564
  shift: () => options.props.shift !== false,
2212
2565
  collisionPadding: () => options.props.collisionPadding ?? 8,
2213
- autoUpdateOptions: () => options.props.autoUpdateOptions,
2214
- restartKey: options.restartKey
2566
+ autoUpdateOptions: () => options.props.autoUpdateOptions
2215
2567
  });
2216
2568
  return {
2217
2569
  actualPlacement: floating.actualPlacement,
@@ -2220,6 +2572,67 @@ function useDropdownMenuPortalPosition(options) {
2220
2572
  };
2221
2573
  }
2222
2574
  //#endregion
2575
+ //#region src/components/dropdown-menu/useDropdownMenuPresentation.ts
2576
+ function useDropdownMenuPresentation({ props, rootMenuId, menuId, isDisabled, isVisible, isExplicitTarget, actualPlacement, contentHasSubmenu, activeDescendantId, interaction, resetHoverIntent, onMenuMousemove, open, close, toggle }) {
2577
+ const rootClass = computed(() => bem("rp-dropdown-menu", {
2578
+ [`placement-${actualPlacement.value}`]: true,
2579
+ target: isExplicitTarget.value,
2580
+ open: isVisible.value,
2581
+ disabled: isDisabled.value
2582
+ }));
2583
+ const contentClass = computed(() => bem("rp-dropdown-menu__content", { "has-submenu": contentHasSubmenu.value }));
2584
+ const triggerProps = computed(() => ({
2585
+ "aria-controls": isDisabled.value ? void 0 : menuId.value,
2586
+ "aria-expanded": isDisabled.value ? void 0 : isVisible.value,
2587
+ "aria-haspopup": "menu",
2588
+ disabled: isDisabled.value || void 0,
2589
+ onClick: () => interaction.onTriggerClick(),
2590
+ onKeydown: (event) => interaction.onTriggerKeydown(event)
2591
+ }));
2592
+ function getKeyboardMenuId(event) {
2593
+ const activeItem = interaction.activeItemId.value ? interaction.getItem(interaction.activeItemId.value) : void 0;
2594
+ const openSubmenuId = activeItem?.submenuId?.();
2595
+ if (openSubmenuId && interaction.isMenuOpen(openSubmenuId)) {
2596
+ if (event.key === "Escape") return openSubmenuId;
2597
+ const opensLeft = activeItem?.submenuDirection?.() === "left" || interaction.getMenu(openSubmenuId)?.placement().startsWith("left");
2598
+ if (event.key === (opensLeft ? "ArrowRight" : "ArrowLeft")) return openSubmenuId;
2599
+ }
2600
+ if ([
2601
+ "ArrowLeft",
2602
+ "ArrowRight",
2603
+ "Escape"
2604
+ ].includes(event.key) && interaction.activeMenuId.value !== rootMenuId) return interaction.activeMenuId.value;
2605
+ return activeItem?.menuId ?? interaction.activeMenuId.value;
2606
+ }
2607
+ function onMenuKeydown(event) {
2608
+ resetHoverIntent();
2609
+ interaction.onMenuKeydown(getKeyboardMenuId(event), event);
2610
+ }
2611
+ return {
2612
+ rootClass,
2613
+ contentClass,
2614
+ triggerProps,
2615
+ contentProps: computed(() => ({
2616
+ id: menuId.value,
2617
+ role: "menu",
2618
+ tabindex: -1,
2619
+ "aria-label": props.ariaLabel || void 0,
2620
+ "aria-activedescendant": activeDescendantId.value,
2621
+ onKeydown: onMenuKeydown,
2622
+ onMousemove: onMenuMousemove,
2623
+ onMouseleave: resetHoverIntent
2624
+ })),
2625
+ slotProps: computed(() => ({
2626
+ triggerProps: triggerProps.value,
2627
+ isOpen: isVisible.value,
2628
+ open,
2629
+ close,
2630
+ toggle
2631
+ })),
2632
+ onMenuKeydown
2633
+ };
2634
+ }
2635
+ //#endregion
2223
2636
  //#region src/components/dropdown-menu/useDropdownMenuRenderItems.ts
2224
2637
  function useDropdownMenuRenderItems({ props, actualPlacement, items, getItemId, getSubmenuId, getMenuActiveDescendant, getItemElement, isItemFocused, isSubmenuOpen, activateItem, focusHoveredItem, selectItem, openSubmenu, closeSubmenu, close }) {
2225
2638
  const renderedItems = computed(() => getRenderedItems(items.value));
@@ -2262,9 +2675,9 @@ function useDropdownMenuRenderItems({ props, actualPlacement, items, getItemId,
2262
2675
  "aria-expanded": hasSubmenu ? resolvedSubmenuOpen : void 0,
2263
2676
  "aria-haspopup": hasSubmenu ? "menu" : void 0,
2264
2677
  "aria-disabled": disabled || void 0,
2265
- "data-disabled": disabled ? "" : void 0,
2266
- "data-highlighted": focused ? "" : void 0,
2267
- "data-submenu": hasSubmenu ? "" : void 0,
2678
+ "data-disabled": toPresenceAttribute(disabled),
2679
+ "data-highlighted": toPresenceAttribute(focused),
2680
+ "data-submenu": toPresenceAttribute(hasSubmenu),
2268
2681
  onClick: (event) => activateItem(item, path, event),
2269
2682
  onMouseenter: (event) => focusHoveredItem(item, path, event)
2270
2683
  };
@@ -2322,45 +2735,66 @@ function useDropdownMenuRenderItems({ props, actualPlacement, items, getItemId,
2322
2735
  };
2323
2736
  }
2324
2737
  //#endregion
2325
- //#region src/components/dropdown-menu/useDropdownSubmenus.ts
2326
- function useDropdownSubmenus({ focusedPath, getItemAtPath, focusItem }) {
2327
- const openSubmenuPath = ref([]);
2328
- function resetSubmenus() {
2329
- openSubmenuPath.value = [];
2330
- }
2331
- function isSubmenuOpen(path) {
2332
- return path.length > 0 && isPathPrefix(path, openSubmenuPath.value);
2333
- }
2334
- function closeSubmenusAfter(menuPath) {
2335
- openSubmenuPath.value = [...menuPath];
2336
- }
2337
- function openSubmenu(path, focus = DEFAULT_FOCUS_TARGET) {
2338
- const item = getItemAtPath(path);
2339
- if (!item || item.disabled || !hasItemSubmenu(item)) return false;
2340
- openSubmenuPath.value = [...path];
2341
- if (focus && !focusItem(focus, path)) focusedPath.value = [...path];
2342
- return true;
2343
- }
2344
- function closeSubmenu(path) {
2345
- if (!isSubmenuOpen(path)) return false;
2346
- openSubmenuPath.value = getParentPath(path);
2347
- if (isPathPrefix(path, focusedPath.value) && focusedPath.value.length > path.length) focusedPath.value = [...path];
2348
- return true;
2349
- }
2350
- function closeCurrentSubmenu() {
2351
- const path = focusedPath.value.length > 1 ? getParentPath(focusedPath.value) : focusedPath.value;
2352
- if (path.length === 0 || !isSubmenuOpen(path)) return false;
2353
- return closeSubmenu(path);
2354
- }
2355
- return {
2356
- openSubmenuPath,
2357
- resetSubmenus,
2358
- isSubmenuOpen,
2359
- closeSubmenusAfter,
2360
- openSubmenu,
2361
- closeSubmenu,
2362
- closeCurrentSubmenu
2363
- };
2738
+ //#region src/components/dropdown-menu/useDropdownMenuTargetBindings.ts
2739
+ var TARGET_ATTRIBUTES = [
2740
+ "aria-controls",
2741
+ "aria-expanded",
2742
+ "aria-haspopup",
2743
+ "aria-disabled"
2744
+ ];
2745
+ function syncTargetAttributes(target, id, visible, disabled) {
2746
+ if (disabled) {
2747
+ target.removeAttribute("aria-controls");
2748
+ target.removeAttribute("aria-expanded");
2749
+ target.removeAttribute("aria-haspopup");
2750
+ target.setAttribute("aria-disabled", "true");
2751
+ return;
2752
+ }
2753
+ target.setAttribute("aria-controls", id);
2754
+ target.setAttribute("aria-expanded", String(visible));
2755
+ target.setAttribute("aria-haspopup", "menu");
2756
+ target.removeAttribute("aria-disabled");
2757
+ }
2758
+ function useDropdownMenuTargetBindings({ interaction, rootRef, menuRef, targetElement, isExplicitTarget, menuId, isVisible, isDisabled, ignoredTargets, pointerDownOutside, focusOutside, interactOutside }) {
2759
+ function watchInside(source) {
2760
+ watch(source, (element, previous) => {
2761
+ if (previous) interaction.unregisterInside(previous);
2762
+ if (element) interaction.registerInside(element);
2763
+ }, { immediate: true });
2764
+ }
2765
+ watchInside(rootRef);
2766
+ watchInside(menuRef);
2767
+ watchInside(targetElement);
2768
+ const cleanupDismissal = interaction.registerDismissal({
2769
+ ignoredTargets,
2770
+ pointerDownOutside,
2771
+ focusOutside,
2772
+ interactOutside
2773
+ });
2774
+ watch([isExplicitTarget, targetElement], ([explicit, target], _previous, onCleanup) => {
2775
+ if (!explicit || !target) return;
2776
+ const onClick = () => interaction.onTriggerClick();
2777
+ const onKeydown = (event) => interaction.onTriggerKeydown(event);
2778
+ target.addEventListener("click", onClick);
2779
+ target.addEventListener("keydown", onKeydown);
2780
+ onCleanup(() => {
2781
+ target.removeEventListener("click", onClick);
2782
+ target.removeEventListener("keydown", onKeydown);
2783
+ });
2784
+ }, { flush: "sync" });
2785
+ watch([
2786
+ isExplicitTarget,
2787
+ targetElement,
2788
+ menuId,
2789
+ isVisible,
2790
+ isDisabled
2791
+ ], ([explicit, target, id, visible, disabled], _previous, onCleanup) => {
2792
+ if (!explicit || !target) return;
2793
+ const snapshot = snapshotAttributes(target, TARGET_ATTRIBUTES);
2794
+ syncTargetAttributes(target, id, visible, disabled);
2795
+ onCleanup(() => restoreAttributes(target, snapshot));
2796
+ }, { flush: "sync" });
2797
+ onBeforeUnmount(cleanupDismissal);
2364
2798
  }
2365
2799
  //#endregion
2366
2800
  //#region src/components/dropdown-menu/useDropdownMenu.ts
@@ -2368,18 +2802,23 @@ function useDropdownMenu(props, emit = {}) {
2368
2802
  const rootRef = ref(null);
2369
2803
  const menuRef = ref(null);
2370
2804
  const arrowRef = ref(null);
2371
- const uncontrolledOpen = ref(false);
2372
2805
  const generatedId = useId();
2806
+ const interactionRootMenuId = `${generatedId}-interaction-root`;
2807
+ const controllableOpen = useControllableValue({
2808
+ modelValue: () => props.open,
2809
+ defaultValue: () => false,
2810
+ onChange: (open) => emit.openChange?.(open)
2811
+ });
2373
2812
  const menuId = computed(() => props.id ?? `${generatedId}-menu`);
2374
2813
  const placement = computed(() => props.placement ?? "bottom-start");
2375
2814
  const teleportTo = useTeleportTarget(() => props.teleportTo);
2376
- const teleportPositioningKey = useTeleportPositioningKey();
2377
2815
  const shouldTeleport = computed(() => props.teleport !== false);
2378
2816
  const { isExplicitTarget, reference, resolvedTarget } = useFloatingTarget(() => props.target, rootRef);
2379
- const targetElement = computed(() => isElementReference(resolvedTarget.value) ? resolvedTarget.value : null);
2817
+ const targetElement = computed(() => isElement(resolvedTarget.value) ? resolvedTarget.value : null);
2380
2818
  const visibleItems = computed(() => props.items ?? []);
2381
2819
  const isDisabled = computed(() => Boolean(props.disabled));
2382
- const isOpen = computed(() => props.open ?? uncontrolledOpen.value);
2820
+ const modal = computed(() => Boolean(props.modal));
2821
+ const isOpen = controllableOpen.value;
2383
2822
  const isVisible = computed(() => isOpen.value && !isDisabled.value);
2384
2823
  const layer = useOverlayLayer({
2385
2824
  active: isVisible,
@@ -2398,173 +2837,107 @@ function useDropdownMenu(props, emit = {}) {
2398
2837
  menuRef,
2399
2838
  arrowRef,
2400
2839
  isVisible,
2401
- placement,
2402
- restartKey: () => [
2403
- shouldTeleport.value,
2404
- teleportTo.value,
2405
- teleportPositioningKey.value
2406
- ]
2840
+ placement
2407
2841
  });
2408
2842
  const placementSide = computed(() => actualPlacement.value.split("-")[0]);
2409
- const { focusedPath, focusedIndex, getItemsAtPath, getItemAtPath, focusItem, resetFocus, setFocusedIndex, isItemFocused, moveFocus } = useDropdownMenuNavigation(visibleItems);
2410
- const submenus = useDropdownSubmenus({
2411
- focusedPath,
2412
- getItemAtPath,
2413
- focusItem
2843
+ function focusTrigger() {
2844
+ nextTick(() => {
2845
+ if (targetElement.value instanceof HTMLElement) {
2846
+ targetElement.value.focus();
2847
+ return;
2848
+ }
2849
+ rootRef.value?.querySelector("[aria-haspopup=\"menu\"]")?.focus();
2850
+ });
2851
+ }
2852
+ function setOpen(nextOpen) {
2853
+ if (nextOpen && isDisabled.value) return;
2854
+ if (isOpen.value !== nextOpen) controllableOpen.setValue(nextOpen);
2855
+ }
2856
+ let resetHoverIntent;
2857
+ const interaction = useDropdownMenuInteraction({
2858
+ rootMenuId: interactionRootMenuId,
2859
+ isOpen: isVisible,
2860
+ disabled: isDisabled,
2861
+ modal,
2862
+ setOpen,
2863
+ isTopLayer: layer.isTopLayer,
2864
+ focusTrigger,
2865
+ beforeOpen: () => resetHoverIntent(),
2866
+ beforeClose: () => resetHoverIntent()
2414
2867
  });
2415
- const { openSubmenuPath, isSubmenuOpen } = submenus;
2416
- const activeMenuPath = computed(() => getParentPath(focusedPath.value));
2417
- const typeahead = useTypeahead({
2418
- items: () => getItemsAtPath(activeMenuPath.value),
2419
- activeIndex: () => focusedPath.value.at(-1) ?? -1,
2420
- getKey: (item) => item.value,
2421
- getTextValue: (item) => item.label,
2422
- isDisabled: (item) => Boolean(item.disabled),
2423
- scopeKey: () => activeMenuPath.value.join("/"),
2424
- onMatch(_item, index) {
2425
- const menuPath = activeMenuPath.value;
2426
- if (setFocusedIndex(index, menuPath)) submenus.closeSubmenusAfter(menuPath);
2427
- }
2868
+ const open = interaction.open;
2869
+ const close = (options = {}) => interaction.close(options);
2870
+ const toggle = interaction.toggle;
2871
+ const cleanupRootMenu = interaction.registerMenu({
2872
+ id: interactionRootMenuId,
2873
+ element: () => menuRef.value,
2874
+ placement: () => actualPlacement.value,
2875
+ isOpen: () => isVisible.value
2428
2876
  });
2429
- const { activeDescendantId, focusMenu, focusTrigger, getItemId, getSubmenuId, getItemElement, getSubmenuElement, isSubmenuOpeningLeft, getMenuActiveDescendant } = useDropdownMenuDom({
2877
+ const dataController = useDropdownMenuDataController({
2878
+ props,
2879
+ items: visibleItems,
2880
+ rootMenuId: interactionRootMenuId,
2430
2881
  menuId,
2431
- rootRef,
2432
2882
  menuRef,
2433
- targetRef: targetElement,
2434
- placement: actualPlacement,
2435
- focusedPath
2436
- });
2437
- const hoverIntent = useDropdownMenuHoverIntent({
2438
- openSubmenuPath,
2439
- getItemElement,
2440
- getSubmenuElement
2883
+ actualPlacement,
2884
+ interaction,
2885
+ onSelect: (item, event) => emit.select?.(item, event)
2441
2886
  });
2442
- const disclosure = useDropdownMenuDisclosure({
2887
+ resetHoverIntent = dataController.resetHoverIntent;
2888
+ const { renderedItems, renderContext, getItemProps, getItemSlotProps } = useDropdownMenuRenderItems({
2443
2889
  props,
2444
- emit,
2445
- rootRef,
2446
- menuRef,
2447
- targetRef: targetElement,
2448
- uncontrolledOpen,
2449
- isDisabled,
2450
- isOpen,
2451
- isVisible,
2452
- focusedPath,
2453
- resetFocus,
2454
- resetSubmenus: submenus.resetSubmenus,
2455
- resetHoverIntent: hoverIntent.resetHoverIntent,
2456
- focusItem,
2457
- focusMenu,
2458
- focusTrigger,
2459
- layer
2890
+ actualPlacement,
2891
+ items: visibleItems,
2892
+ getItemId: dataController.getItemId,
2893
+ getSubmenuId: dataController.getSubmenuId,
2894
+ getMenuActiveDescendant: dataController.getMenuActiveDescendant,
2895
+ getItemElement: dataController.getItemElement,
2896
+ isItemFocused: dataController.isItemFocused,
2897
+ isSubmenuOpen: dataController.isSubmenuOpen,
2898
+ activateItem: dataController.activateItem,
2899
+ focusHoveredItem: dataController.focusHoveredItem,
2900
+ selectItem: dataController.selectItem,
2901
+ openSubmenu: dataController.openSubmenu,
2902
+ closeSubmenu: dataController.closeSubmenu,
2903
+ close
2460
2904
  });
2461
- const { open, close, toggle } = disclosure;
2462
- const { activateItem, selectItem, openSubmenu, closeSubmenu, focusHoveredItem, onTriggerClick, onTriggerKeydown, onMenuKeydown, onMenuMousemove, onMenuMouseleave } = useDropdownMenuInteractions({
2905
+ const presentation = useDropdownMenuPresentation({
2463
2906
  props,
2464
- emit,
2907
+ rootMenuId: interactionRootMenuId,
2908
+ menuId,
2465
2909
  isDisabled,
2466
- focusedPath,
2467
- openSubmenuPath,
2468
- getItemAtPath,
2469
- focusItem,
2470
- moveFocus,
2471
- handleTypeahead: typeahead.handleKey,
2472
- submenus,
2473
- hoverIntent,
2474
- disclosure,
2475
- isSubmenuOpeningLeft
2476
- });
2477
- const rootClass = computed(() => bem("rp-dropdown-menu", {
2478
- [`placement-${actualPlacement.value}`]: true,
2479
- target: isExplicitTarget.value,
2480
- open: isVisible.value,
2481
- disabled: isDisabled.value
2482
- }));
2483
- const contentClass = computed(() => bem("rp-dropdown-menu__content", { "has-submenu": contentHasSubmenu.value }));
2484
- const triggerProps = computed(() => ({
2485
- "aria-controls": isDisabled.value ? void 0 : menuId.value,
2486
- "aria-expanded": isDisabled.value ? void 0 : isVisible.value,
2487
- "aria-haspopup": "menu",
2488
- disabled: isDisabled.value || void 0,
2489
- onClick: onTriggerClick,
2490
- onKeydown: onTriggerKeydown
2491
- }));
2492
- const contentProps = computed(() => ({
2493
- id: menuId.value,
2494
- role: "menu",
2495
- tabindex: -1,
2496
- "aria-label": props.ariaLabel || void 0,
2497
- "aria-activedescendant": activeDescendantId.value,
2498
- onKeydown: onMenuKeydown,
2499
- onMousemove: onMenuMousemove,
2500
- onMouseleave: onMenuMouseleave
2501
- }));
2502
- const slotProps = computed(() => ({
2503
- triggerProps: triggerProps.value,
2504
- isOpen: isVisible.value,
2910
+ isVisible,
2911
+ isExplicitTarget,
2912
+ actualPlacement,
2913
+ contentHasSubmenu,
2914
+ activeDescendantId: dataController.activeDescendantId,
2915
+ interaction,
2916
+ resetHoverIntent,
2917
+ onMenuMousemove: dataController.onMenuMousemove,
2505
2918
  open,
2506
2919
  close,
2507
2920
  toggle
2508
- }));
2509
- const { renderedItems, renderContext, getItemProps, getItemSlotProps } = useDropdownMenuRenderItems({
2510
- props,
2511
- actualPlacement,
2512
- items: visibleItems,
2513
- getItemId,
2514
- getSubmenuId,
2515
- getMenuActiveDescendant,
2516
- getItemElement,
2517
- isItemFocused,
2518
- isSubmenuOpen,
2519
- activateItem,
2520
- focusHoveredItem,
2521
- selectItem,
2522
- openSubmenu,
2523
- closeSubmenu,
2524
- close
2525
2921
  });
2526
- watch([isExplicitTarget, targetElement], ([explicit, target], _previous, onCleanup) => {
2527
- if (!explicit || !target) return;
2528
- target.addEventListener("click", onTriggerClick);
2529
- target.addEventListener("keydown", onTriggerKeydown);
2530
- onCleanup(() => {
2531
- target.removeEventListener("click", onTriggerClick);
2532
- target.removeEventListener("keydown", onTriggerKeydown);
2533
- });
2534
- }, { flush: "sync" });
2535
- watch(isVisible, (visible) => {
2536
- if (!visible) typeahead.reset();
2537
- }, { flush: "sync" });
2538
- watch([
2539
- isExplicitTarget,
2922
+ useDropdownMenuTargetBindings({
2923
+ interaction,
2924
+ rootRef,
2925
+ menuRef,
2540
2926
  targetElement,
2927
+ isExplicitTarget,
2541
2928
  menuId,
2542
2929
  isVisible,
2543
- isDisabled
2544
- ], ([explicit, target, id, visible, disabled], _previous, onCleanup) => {
2545
- if (!explicit || !target) return;
2546
- const snapshot = new Map([
2547
- "aria-controls",
2548
- "aria-expanded",
2549
- "aria-haspopup",
2550
- "aria-disabled"
2551
- ].map((attribute) => [attribute, target.getAttribute(attribute)]));
2552
- if (disabled) {
2553
- target.removeAttribute("aria-controls");
2554
- target.removeAttribute("aria-expanded");
2555
- target.removeAttribute("aria-haspopup");
2556
- target.setAttribute("aria-disabled", "true");
2557
- } else {
2558
- target.setAttribute("aria-controls", id);
2559
- target.setAttribute("aria-expanded", String(visible));
2560
- target.setAttribute("aria-haspopup", "menu");
2561
- target.removeAttribute("aria-disabled");
2562
- }
2563
- onCleanup(() => {
2564
- for (const [attribute, value] of snapshot) if (value == null) target.removeAttribute(attribute);
2565
- else target.setAttribute(attribute, value);
2566
- });
2567
- }, { flush: "sync" });
2930
+ isDisabled,
2931
+ ignoredTargets: () => props.ignore ?? [],
2932
+ pointerDownOutside: emit.pointerDownOutside,
2933
+ focusOutside: emit.focusOutside,
2934
+ interactOutside: emit.interactOutside
2935
+ });
2936
+ const layeredContentStyle = computed(() => ({
2937
+ ...contentStyle.value,
2938
+ zIndex: layer.zIndex.value
2939
+ }));
2940
+ onBeforeUnmount(cleanupRootMenu);
2568
2941
  return {
2569
2942
  rootRef,
2570
2943
  menuRef,
@@ -2575,33 +2948,30 @@ function useDropdownMenu(props, emit = {}) {
2575
2948
  visibleItems,
2576
2949
  renderedItems,
2577
2950
  renderContext,
2578
- focusedIndex,
2579
- focusedPath,
2580
- activeDescendantId,
2581
- rootClass,
2582
- contentClass,
2583
- contentStyle: computed(() => ({
2584
- ...contentStyle.value,
2585
- zIndex: layer.zIndex.value
2586
- })),
2951
+ focusedIndex: dataController.focusedIndex,
2952
+ focusedPath: dataController.focusedPath,
2953
+ activeDescendantId: dataController.activeDescendantId,
2954
+ rootClass: presentation.rootClass,
2955
+ contentClass: presentation.contentClass,
2956
+ contentStyle: layeredContentStyle,
2587
2957
  arrowStyle,
2588
2958
  actualPlacement,
2589
2959
  placementSide,
2590
2960
  isTargetMode: isExplicitTarget,
2591
2961
  teleportTo,
2592
2962
  shouldTeleport,
2593
- triggerProps,
2594
- contentProps,
2595
- slotProps,
2963
+ triggerProps: presentation.triggerProps,
2964
+ contentProps: presentation.contentProps,
2965
+ slotProps: presentation.slotProps,
2596
2966
  open,
2597
2967
  close,
2598
2968
  toggle,
2599
- selectItem,
2600
- openSubmenu,
2601
- closeSubmenu,
2602
- onItemMouseenter: focusHoveredItem,
2603
- onTriggerKeydown,
2604
- onMenuKeydown,
2969
+ selectItem: dataController.selectItem,
2970
+ openSubmenu: dataController.openSubmenu,
2971
+ closeSubmenu: dataController.closeSubmenu,
2972
+ onItemMouseenter: dataController.focusHoveredItem,
2973
+ onTriggerKeydown: (event) => interaction.onTriggerKeydown(event),
2974
+ onMenuKeydown: presentation.onMenuKeydown,
2605
2975
  getItemProps,
2606
2976
  getItemSlotProps
2607
2977
  };
@@ -2687,12 +3057,17 @@ var dropdown_menu_default = /* @__PURE__ */ defineVaporComponent({
2687
3057
  focusOutside: (event) => emit("focusOutside", event),
2688
3058
  interactOutside: (event) => emit("interactOutside", event)
2689
3059
  });
3060
+ const templateRefs = {
3061
+ root: rootRef,
3062
+ menu: menuRef,
3063
+ arrow: arrowRef
3064
+ };
2690
3065
  const { getPartAttrs, getRootAttrs } = useStylesApi(props, "root");
2691
3066
  const state = computed(() => isVisible.value ? "open" : "closed");
2692
3067
  const rootAttrs = computed(() => getRootAttrs({
2693
3068
  class: rootClass.value,
2694
3069
  "data-state": state.value,
2695
- "data-disabled": presence(props.disabled)
3070
+ "data-disabled": toPresenceAttribute(props.disabled)
2696
3071
  }));
2697
3072
  const publicSlotProps = computed(() => {
2698
3073
  const partAttrs = getPartAttrs("trigger");
@@ -2703,7 +3078,7 @@ var dropdown_menu_default = /* @__PURE__ */ defineVaporComponent({
2703
3078
  ...props.classNames?.trigger !== void 0 ? { class: partAttrs.class } : {},
2704
3079
  ...props.styles?.trigger !== void 0 ? { style: partAttrs.style } : {},
2705
3080
  "data-state": state.value,
2706
- "data-disabled": presence(props.disabled)
3081
+ "data-disabled": toPresenceAttribute(props.disabled)
2707
3082
  }
2708
3083
  };
2709
3084
  });
@@ -2739,10 +3114,10 @@ var dropdown_menu_default = /* @__PURE__ */ defineVaporComponent({
2739
3114
  setInsertionState(n26, null, 0);
2740
3115
  createIf(() => __props.arrow, () => {
2741
3116
  const n7 = t0();
2742
- _setTemplateRef(n7, arrowRef, null, "arrowRef");
2743
3117
  renderEffect(() => {
2744
3118
  setAttr(n7, "data-side", unref(placementSide));
2745
3119
  setStyle(n7, unref(arrowStyle));
3120
+ _setTemplateRef(n7, templateRefs.arrow);
2746
3121
  });
2747
3122
  return n7;
2748
3123
  });
@@ -2785,12 +3160,12 @@ var dropdown_menu_default = /* @__PURE__ */ defineVaporComponent({
2785
3160
  return n23;
2786
3161
  }, null, 129)];
2787
3162
  }, { _: 8 }) });
2788
- _setTemplateRef(n26, menuRef, null, "menuRef");
3163
+ setTemplateRefBinding(n26, () => templateRefs.menu, _setTemplateRef);
2789
3164
  return n26;
2790
3165
  }, null, 129);
2791
3166
  }, { _: 8 }));
2792
3167
  });
2793
- setStaticTemplateRef(n29, rootRef, null, "rootRef");
3168
+ setTemplateRefBinding(n29, () => templateRefs.root);
2794
3169
  return n29;
2795
3170
  }
2796
3171
  });