@fabric-msft/fabric-web 5.0.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/CHANGELOG.json +180 -0
  2. package/CHANGELOG.md +56 -1
  3. package/dist/dts/components/accordion-menu/accordion-menu.d.ts +1 -1
  4. package/dist/dts/components/accordion-menu/accordion-menu.d.ts.map +1 -1
  5. package/dist/dts/components/accordion-menu-panel/accordion-menu-panel.d.ts +2 -2
  6. package/dist/dts/components/accordion-menu-panel/accordion-menu-panel.d.ts.map +1 -1
  7. package/dist/dts/components/component-register.d.ts +67 -0
  8. package/dist/dts/components/component-register.d.ts.map +1 -0
  9. package/dist/dts/components/field/field.d.ts +32 -1
  10. package/dist/dts/components/field/field.d.ts.map +1 -1
  11. package/dist/dts/components/filter-pill/filter-pill.d.ts +1 -1
  12. package/dist/dts/components/filter-pill/filter-pill.d.ts.map +1 -1
  13. package/dist/dts/components/listbox/listbox.d.ts +1 -1
  14. package/dist/dts/components/listbox/listbox.d.ts.map +1 -1
  15. package/dist/dts/components/menu/menu.d.ts +71 -83
  16. package/dist/dts/components/menu/menu.d.ts.map +1 -1
  17. package/dist/dts/components/menu/menu.positioning.d.ts +26 -67
  18. package/dist/dts/components/menu/menu.positioning.d.ts.map +1 -1
  19. package/dist/dts/components/menu/menu.styles.d.ts.map +1 -1
  20. package/dist/dts/components/menu-item/menu-item.d.ts +4 -4
  21. package/dist/dts/components/menu-item/menu-item.d.ts.map +1 -1
  22. package/dist/dts/components/menu-list/menu-list.d.ts +1 -1
  23. package/dist/dts/components/menu-list/menu-list.d.ts.map +1 -1
  24. package/dist/dts/components/option-group/option-group.d.ts +1 -1
  25. package/dist/dts/components/option-group/option-group.d.ts.map +1 -1
  26. package/dist/dts/components/popover/popover.d.ts +48 -69
  27. package/dist/dts/components/popover/popover.d.ts.map +1 -1
  28. package/dist/dts/components/popover/popover.definition.d.ts +1 -1
  29. package/dist/dts/components/popover/popover.definition.d.ts.map +1 -1
  30. package/dist/dts/components/popover/popover.options.d.ts +2 -2
  31. package/dist/dts/components/popover/popover.options.d.ts.map +1 -1
  32. package/dist/dts/components/popover/popover.positioning.d.ts +92 -0
  33. package/dist/dts/components/popover/popover.positioning.d.ts.map +1 -0
  34. package/dist/dts/components/popover/popover.styles.d.ts.map +1 -1
  35. package/dist/dts/components/search-box/search-box.d.ts +1 -1
  36. package/dist/dts/components/search-box/search-box.d.ts.map +1 -1
  37. package/dist/dts/components/search-box/search-box.options.d.ts.map +1 -1
  38. package/dist/dts/components/table/table.d.ts +1 -1
  39. package/dist/dts/components/table/table.d.ts.map +1 -1
  40. package/dist/dts/components/text-input/text-input.base.d.ts +3 -3
  41. package/dist/dts/components/text-input/text-input.base.d.ts.map +1 -1
  42. package/dist/dts/components/text-input/text-input.d.ts.map +1 -1
  43. package/dist/dts/components/tooltip/tooltip.d.ts +5 -0
  44. package/dist/dts/components/tooltip/tooltip.d.ts.map +1 -1
  45. package/dist/dts/components/tooltip/tooltip.template.d.ts +13 -0
  46. package/dist/dts/components/tooltip/tooltip.template.d.ts.map +1 -0
  47. package/dist/dts/components/tree/tree.d.ts +16 -0
  48. package/dist/dts/components/tree/tree.d.ts.map +1 -1
  49. package/dist/dts/components/tree-item/index.d.ts +2 -1
  50. package/dist/dts/components/tree-item/index.d.ts.map +1 -1
  51. package/dist/dts/components/tree-item/tree-item.definition.d.ts.map +1 -1
  52. package/dist/dts/components/tree-item/tree-item.styles.d.ts +2 -0
  53. package/dist/dts/components/tree-item/tree-item.styles.d.ts.map +1 -0
  54. package/dist/dts/components/tree-item/tree-item.template.d.ts +4 -0
  55. package/dist/dts/components/tree-item/tree-item.template.d.ts.map +1 -0
  56. package/dist/dts/components/wizard/wizard.d.ts +1 -1
  57. package/dist/dts/components/wizard/wizard.d.ts.map +1 -1
  58. package/dist/dts/components/wizard-step/wizard-step.d.ts +1 -1
  59. package/dist/dts/components/wizard-step/wizard-step.d.ts.map +1 -1
  60. package/dist/dts/index.d.ts +3 -2
  61. package/dist/dts/index.d.ts.map +1 -1
  62. package/dist/dts/util/accessibility-utilities.d.ts +23 -0
  63. package/dist/dts/util/accessibility-utilities.d.ts.map +1 -0
  64. package/dist/dts/util/attribute-utilities.d.ts +9 -0
  65. package/dist/dts/util/attribute-utilities.d.ts.map +1 -0
  66. package/dist/dts/util/debounce.d.ts.map +1 -0
  67. package/dist/dts/util/debug.d.ts +32 -0
  68. package/dist/dts/util/debug.d.ts.map +1 -0
  69. package/dist/dts/util/direction.d.ts +193 -0
  70. package/dist/dts/util/direction.d.ts.map +1 -1
  71. package/dist/dts/util/dom.d.ts +2 -0
  72. package/dist/dts/util/dom.d.ts.map +1 -0
  73. package/dist/dts/util/element-internal-mocks.d.ts +67 -0
  74. package/dist/dts/util/element-internal-mocks.d.ts.map +1 -0
  75. package/dist/dts/util/focus-management.d.ts +132 -0
  76. package/dist/dts/util/focus-management.d.ts.map +1 -0
  77. package/dist/dts/util/hash-utilities.d.ts +8 -0
  78. package/dist/dts/util/hash-utilities.d.ts.map +1 -0
  79. package/dist/dts/util/index.d.ts +13 -6
  80. package/dist/dts/util/index.d.ts.map +1 -1
  81. package/dist/dts/util/positioning/flexible-position-strategy.d.ts +87 -0
  82. package/dist/dts/util/positioning/flexible-position-strategy.d.ts.map +1 -0
  83. package/dist/dts/util/positioning/index.d.ts +9 -0
  84. package/dist/dts/util/positioning/index.d.ts.map +1 -0
  85. package/dist/dts/util/positioning/position-calculator.d.ts +46 -0
  86. package/dist/dts/util/positioning/position-calculator.d.ts.map +1 -0
  87. package/dist/dts/util/positioning/types.d.ts +181 -0
  88. package/dist/dts/util/positioning/types.d.ts.map +1 -0
  89. package/dist/esm/components/accordion-menu/accordion-menu.js +1 -1
  90. package/dist/esm/components/accordion-menu/accordion-menu.js.map +1 -1
  91. package/dist/esm/components/accordion-menu/accordion-menu.styles.js +1 -1
  92. package/dist/esm/components/accordion-menu-panel/accordion-menu-panel.js +2 -2
  93. package/dist/esm/components/accordion-menu-panel/accordion-menu-panel.js.map +1 -1
  94. package/dist/esm/components/accordion-menu-panel/accordion-menu-panel.styles.js +1 -1
  95. package/dist/esm/components/card/card.styles.js +1 -1
  96. package/dist/esm/components/card-footer/card-footer.styles.js +1 -1
  97. package/dist/esm/components/card-header/card-header.styles.js +1 -1
  98. package/dist/esm/components/card-preview/card-preview.styles.js +1 -1
  99. package/dist/esm/components/carousel/carousel.js +1 -1
  100. package/dist/esm/components/carousel/carousel.js.map +1 -1
  101. package/dist/esm/components/carousel/carousel.styles.js +1 -1
  102. package/dist/esm/components/component-register.js +67 -0
  103. package/dist/esm/components/component-register.js.map +1 -0
  104. package/dist/esm/components/compound-button/compound-button.options.js +1 -1
  105. package/dist/esm/components/field/field.js +68 -1
  106. package/dist/esm/components/field/field.js.map +1 -1
  107. package/dist/esm/components/filter-pill/filter-pill.js +1 -1
  108. package/dist/esm/components/filter-pill/filter-pill.js.map +1 -1
  109. package/dist/esm/components/filter-pill/filter-pill.options.js +1 -1
  110. package/dist/esm/components/filter-pill/filter-pill.styles.js +3 -2
  111. package/dist/esm/components/listbox/listbox.js +1 -1
  112. package/dist/esm/components/listbox/listbox.js.map +1 -1
  113. package/dist/esm/components/loading-button/loading-button.js.map +1 -1
  114. package/dist/esm/components/menu/menu.js +158 -230
  115. package/dist/esm/components/menu/menu.js.map +1 -1
  116. package/dist/esm/components/menu/menu.options.js +1 -1
  117. package/dist/esm/components/menu/menu.options.js.map +1 -1
  118. package/dist/esm/components/menu/menu.positioning.js +98 -201
  119. package/dist/esm/components/menu/menu.positioning.js.map +1 -1
  120. package/dist/esm/components/menu/menu.styles.js +2 -2
  121. package/dist/esm/components/menu/menu.styles.js.map +1 -1
  122. package/dist/esm/components/menu-item/menu-item.js +4 -4
  123. package/dist/esm/components/menu-item/menu-item.js.map +1 -1
  124. package/dist/esm/components/menu-item/menu-item.styles.js +1 -1
  125. package/dist/esm/components/menu-list/menu-list.js +1 -1
  126. package/dist/esm/components/menu-list/menu-list.js.map +1 -1
  127. package/dist/esm/components/menu-list/menu-list.styles.js +1 -1
  128. package/dist/esm/components/multi-view/multi-view.styles.js +1 -1
  129. package/dist/esm/components/multi-view-controller/multi-view-controller.styles.js +1 -1
  130. package/dist/esm/components/multi-view-group/multi-view-group.styles.js +1 -1
  131. package/dist/esm/components/option-group/option-group.js +1 -1
  132. package/dist/esm/components/option-group/option-group.js.map +1 -1
  133. package/dist/esm/components/popover/define.js +2 -2
  134. package/dist/esm/components/popover/define.js.map +1 -1
  135. package/dist/esm/components/popover/popover.definition.js.map +1 -1
  136. package/dist/esm/components/popover/popover.js +82 -122
  137. package/dist/esm/components/popover/popover.js.map +1 -1
  138. package/dist/esm/components/popover/popover.options.js +2 -2
  139. package/dist/esm/components/popover/popover.options.js.map +1 -1
  140. package/dist/esm/components/popover/popover.positioning.js +314 -0
  141. package/dist/esm/components/popover/popover.positioning.js.map +1 -0
  142. package/dist/esm/components/popover/popover.styles.js +14 -18
  143. package/dist/esm/components/popover/popover.styles.js.map +1 -1
  144. package/dist/esm/components/search-box/search-box.js +1 -1
  145. package/dist/esm/components/search-box/search-box.js.map +1 -1
  146. package/dist/esm/components/search-box/search-box.options.js +1 -1
  147. package/dist/esm/components/search-box/search-box.options.js.map +1 -1
  148. package/dist/esm/components/simple-table/simple-table.styles.js +2 -1
  149. package/dist/esm/components/table/table.js +1 -1
  150. package/dist/esm/components/table/table.js.map +1 -1
  151. package/dist/esm/components/table/table.styles.js +1 -1
  152. package/dist/esm/components/table-cell/table-cell.styles.js +1 -1
  153. package/dist/esm/components/tag/tag.styles.js +1 -1
  154. package/dist/esm/components/teaching-bubble/teaching-bubble.styles.js +1 -1
  155. package/dist/esm/components/text-input/text-input.base.js +3 -3
  156. package/dist/esm/components/text-input/text-input.base.js.map +1 -1
  157. package/dist/esm/components/text-input/text-input.js +1 -0
  158. package/dist/esm/components/text-input/text-input.js.map +1 -1
  159. package/dist/esm/components/text-input/text-input.styles.js +1 -1
  160. package/dist/esm/components/tooltip/tooltip.js +23 -0
  161. package/dist/esm/components/tooltip/tooltip.js.map +1 -1
  162. package/dist/esm/components/tooltip/tooltip.template.js +16 -0
  163. package/dist/esm/components/tooltip/tooltip.template.js.map +1 -0
  164. package/dist/esm/components/tree/tree.js +38 -1
  165. package/dist/esm/components/tree/tree.js.map +1 -1
  166. package/dist/esm/components/tree-item/index.js +2 -1
  167. package/dist/esm/components/tree-item/index.js.map +1 -1
  168. package/dist/esm/components/tree-item/tree-item.definition.js +4 -3
  169. package/dist/esm/components/tree-item/tree-item.definition.js.map +1 -1
  170. package/dist/esm/components/tree-item/tree-item.styles.js +9 -0
  171. package/dist/esm/components/tree-item/tree-item.styles.js.map +1 -0
  172. package/dist/esm/components/tree-item/tree-item.template.js +6 -0
  173. package/dist/esm/components/tree-item/tree-item.template.js.map +1 -0
  174. package/dist/esm/components/wizard/wizard.js +3 -3
  175. package/dist/esm/components/wizard/wizard.js.map +1 -1
  176. package/dist/esm/components/wizard/wizard.styles.js +1 -1
  177. package/dist/esm/components/wizard-panel/wizard-panel.styles.js +1 -1
  178. package/dist/esm/components/wizard-step/wizard-step.js +1 -1
  179. package/dist/esm/components/wizard-step/wizard-step.js.map +1 -1
  180. package/dist/esm/components/wizard-step/wizard-step.styles.js +1 -1
  181. package/dist/esm/index.js +17 -7
  182. package/dist/esm/index.js.map +1 -1
  183. package/dist/esm/util/accessibility-utilities.js +40 -0
  184. package/dist/esm/util/accessibility-utilities.js.map +1 -0
  185. package/dist/esm/util/attribute-utilities.js +12 -0
  186. package/dist/esm/util/attribute-utilities.js.map +1 -0
  187. package/dist/esm/util/debounce.js.map +1 -0
  188. package/dist/esm/util/debug.js +74 -0
  189. package/dist/esm/util/debug.js.map +1 -0
  190. package/dist/esm/util/direction.js +240 -8
  191. package/dist/esm/util/direction.js.map +1 -1
  192. package/dist/esm/util/dom.js +3 -0
  193. package/dist/esm/util/dom.js.map +1 -0
  194. package/dist/esm/util/element-internal-mocks.js +114 -0
  195. package/dist/esm/util/element-internal-mocks.js.map +1 -0
  196. package/dist/esm/util/focus-management.js +247 -0
  197. package/dist/esm/util/focus-management.js.map +1 -0
  198. package/dist/esm/util/hash-utilities.js +12 -0
  199. package/dist/esm/util/hash-utilities.js.map +1 -0
  200. package/dist/esm/util/index.js +13 -6
  201. package/dist/esm/util/index.js.map +1 -1
  202. package/dist/esm/util/positioning/flexible-position-strategy.js +232 -0
  203. package/dist/esm/util/positioning/flexible-position-strategy.js.map +1 -0
  204. package/dist/esm/util/positioning/index.js +10 -0
  205. package/dist/esm/util/positioning/index.js.map +1 -0
  206. package/dist/esm/util/positioning/position-calculator.js +196 -0
  207. package/dist/esm/util/positioning/position-calculator.js.map +1 -0
  208. package/dist/esm/util/positioning/types.js +30 -0
  209. package/dist/esm/util/positioning/types.js.map +1 -0
  210. package/dist/index.d.ts +697 -223
  211. package/dist/index.d.ts.map +1 -1
  212. package/dist/index.js +2365 -1215
  213. package/dist/index.js.map +1 -1
  214. package/dist/index.min.js +109 -110
  215. package/dist/index.min.js.map +1 -1
  216. package/package.json +6 -8
  217. package/dist/component-utilities.js +0 -43
  218. package/dist/dts/components/popover/positioning.d.ts +0 -51
  219. package/dist/dts/components/popover/positioning.d.ts.map +0 -1
  220. package/dist/dts/util/positioning.d.ts +0 -129
  221. package/dist/dts/util/positioning.d.ts.map +0 -1
  222. package/dist/dts/utils/debounce.d.ts.map +0 -1
  223. package/dist/esm/component-utilities.js +0 -13
  224. package/dist/esm/components/popover/positioning.js +0 -189
  225. package/dist/esm/components/popover/positioning.js.map +0 -1
  226. package/dist/esm/util/positioning.js +0 -130
  227. package/dist/esm/util/positioning.js.map +0 -1
  228. package/dist/esm/utils/debounce.js.map +0 -1
  229. /package/dist/dts/{utils → util}/debounce.d.ts +0 -0
  230. /package/dist/esm/{utils → util}/debounce.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { attr, observable, FASTElement, Updates } from '@microsoft/fast-element';
2
2
  import { keyEnter, keySpace, keyEscape } from '@microsoft/fast-web-utilities';
3
- import { debounce } from '../../utils/debounce.js';
3
+ import { debounce } from '../../util/debounce.js';
4
4
  import '../menu-list/menu-list.js';
5
5
  import { MenuPositions, MenuRepositionModes } from './menu.options.js';
6
6
  import { MenuPositioning } from './menu.positioning.js';
@@ -16,6 +16,12 @@ var __decorate = globalThis && globalThis.__decorate || function (decorators, ta
16
16
  * Menu
17
17
  * @summary A Menu component that provides an interactive menu interface with support for various trigger and open behaviors.
18
18
  *
19
+ * When `reposition-mode="auto"` is set the component creates a {@link MenuPositioning} manager backed by
20
+ * `FlexiblePositionStrategy`. On every open/resize/scroll cycle the strategy measures available space, picks
21
+ * the best-fitting position from the preferred position plus its fallbacks, and resizes the menu if there is
22
+ * not enough available space. Supply `overflowBoundary` to scope collision detection to a specific scrolling
23
+ * container instead of the viewport.
24
+ *
19
25
  * @example
20
26
  * ```html
21
27
  * <fabric-menu open-on-hover="true" open-on-context="true" close-on-scroll="true" persist-on-item-click="true">
@@ -33,8 +39,10 @@ var __decorate = globalThis && globalThis.__decorate || function (decorators, ta
33
39
  * @attr {boolean | undefined} close-on-scroll - Determines if the menu should close on scroll.
34
40
  * @attr {boolean | undefined} persist-on-item-click - Determines if the menu open state should persist on click of a menu item.
35
41
  * @attr {boolean | undefined} split - Determines if the menu is in split state (for split button pattern).
36
- * @attr {MenuPosition | undefined} menu-position - Determines whether the menu list is above or below the trigger.
37
- * @attr {MenuRepositionMode | undefined} reposition-mode - The mode for repositioning the menu. Default is "none".
42
+ * @attr {MenuPosition | undefined} menu-position - Determines whether the menu list is above or below the trigger. Defaults to `"below"`.
43
+ * @attr {MenuRepositionMode | undefined} reposition-mode - Controls JS-based repositioning. `"auto"` enables
44
+ * `FlexiblePositionStrategy` which detects collisions and flips the menu to the best-fitting position on
45
+ * every resize/scroll cycle. `"none"` (default) disables JS repositioning and relies on CSS anchored positioning.
38
46
  *
39
47
  * @prop {boolean | undefined} openOnHover - Determines if the menu should open on hover.
40
48
  * @prop {boolean | undefined} openOnContext - Determines if the menu should open on right click.
@@ -43,9 +51,12 @@ var __decorate = globalThis && globalThis.__decorate || function (decorators, ta
43
51
  * @prop {boolean | undefined} split - Determines if the menu is in split state.
44
52
  * @prop {MenuList[]} slottedMenuList - Holds the slotted menu list.
45
53
  * @prop {HTMLElement[]} slottedTriggers - Holds the slotted triggers.
46
- * @prop {MenuPosition | undefined} menuPosition - Determines whether the menu list is above or below the trigger.
47
- * @prop {MenuRepositionMode | undefined} repositionMode - The mode for repositioning the menu.
48
- * @prop {HTMLElement | undefined} overflowBoundary - The overflow boundary element reference.
54
+ * @prop {MenuPosition | undefined} menuPosition - Reflects the active resolved position (`"above"` or `"below"`). May
55
+ * differ from the authored `menu-position` attribute when `reposition-mode="auto"` has flipped the menu.
56
+ * @prop {MenuRepositionMode | undefined} repositionMode - The active repositioning mode (`"auto"` | `"none"`).
57
+ * @prop {HTMLElement | undefined} overflowBoundary - An optional scrolling container used by the
58
+ * `FlexiblePositionStrategy` as the collision boundary instead of the viewport. Only relevant when
59
+ * `reposition-mode="auto"`.
49
60
  *
50
61
  * @slot primary-action - Slot for the primary action element (used when split is true).
51
62
  * @slot trigger - Slot for the trigger element.
@@ -73,10 +84,10 @@ class Menu extends FASTElement {
73
84
  constructor() {
74
85
  super(...arguments);
75
86
  /**
76
- * The array of open positions based on collision detection.
87
+ * The positioning instance for the menu.
77
88
  * @private
78
89
  */
79
- this.openPositions = [];
90
+ this.positioning = null;
80
91
  /**
81
92
  * Determines if the menu should open on hover.
82
93
  * @public
@@ -109,8 +120,15 @@ class Menu extends FASTElement {
109
120
  this.menuPosition = MenuPositions.below;
110
121
  /**
111
122
  * The mode for repositioning the menu when it overflows the boundary.
123
+ *
124
+ * - `"none"` (default) — no JS-based repositioning; positioning is handled entirely by CSS anchored positioning.
125
+ * - `"auto"` — enables `FlexiblePositionStrategy`. On every open, resize, and scroll event the strategy
126
+ * measures available space, picks the best-fitting position from the preferred position plus its fallbacks,
127
+ * and re-applies layout. When the chosen position is `"above"`, bottom-anchored positioning keeps the
128
+ * menu's bottom edge pinned to the anchor's top edge so that height constraints shrink it upward.
129
+ *
112
130
  * @public
113
- * @default "auto"
131
+ * @default "none"
114
132
  */
115
133
  this.repositionMode = MenuRepositionModes.none;
116
134
  /**
@@ -129,111 +147,75 @@ class Menu extends FASTElement {
129
147
  */
130
148
  this._open = false;
131
149
  /**
132
- * Creates the overflow handler using IntersectionObserver.
133
- * Note: When a custom overflowBoundary is set, we use direct rect calculations
134
- * instead because popovers render in the top layer and IntersectionObserver
135
- * with a custom root won't work correctly.
136
- * @private
137
- */
138
- this.createOverflowHandler = () => {
139
- // Only use IntersectionObserver when no custom boundary is set
140
- // (uses document/viewport as root)
141
- if (!this.overflowBoundary) {
142
- const options = {
143
- root: document
144
- };
145
- this.intersectionObserver = new IntersectionObserver(this.handleOverflow, options);
146
- }
147
- };
148
- /**
149
- * Checks overflow against custom boundary using direct rect calculations.
150
- * Delegates to MenuPositioning for the actual calculation.
150
+ * Internal open handler used by multiple entry points.
151
151
  * @private
152
152
  */
153
- this.checkOverflowWithBoundary = () => {
154
- if (!this.positioning || !this._menuList || !this.overflowBoundary) {
153
+ this.openMenuInternal = e => {
154
+ if (this._open) {
155
155
  return;
156
156
  }
157
- const {
158
- hasOverflow,
159
- suggestedPosition
160
- } = this.positioning.checkOverflowWithBoundary();
161
- if (hasOverflow) {
162
- if (suggestedPosition) {
163
- this.openPositions = [suggestedPosition];
164
- this.repositionMenu(this.openPositions);
165
- }
166
- // Apply height constraint if still needed
167
- this.applyHeightConstraint();
168
- } else {
169
- // Menu fits, release any previous constraints
170
- if (this.openPositions.length > 0) {
171
- this.releasePositions();
172
- this.clearHeightConstraint();
173
- }
157
+ if (this.repositionMode == MenuRepositionModes.auto) {
158
+ this.addRepositioningHandlers();
159
+ this.createPositioningManager();
160
+ this.scheduleReposition();
174
161
  }
175
- };
176
- /**
177
- * Handles overflow collisions with the menu.
178
- * @private
179
- */
180
- this.handleOverflow = entries => {
181
- if (this.positioning) {
182
- this.positioning.position = this.originalMenuPosition || this.menuPosition || MenuPositions.below;
162
+ this.setMenuOpenState(true);
163
+ this.focusMenuList();
164
+ if (e && this.openOnContext) {
165
+ e.preventDefault();
166
+ }
167
+ document.addEventListener("click", this.documentClickHandler);
168
+ if (this.closeOnScroll) {
169
+ document.addEventListener("scroll", this.closeMenu);
183
170
  }
184
- entries.forEach(entry => {
185
- if (entry.intersectionRatio < 1 && this.positioning) {
186
- // Try to reposition (flip) first
187
- this.openPositions = this.positioning.findOpenPositionsFromCollision(entry);
188
- if (this.openPositions.length > 0) {
189
- this.repositionMenu(this.openPositions);
190
- }
191
- // After repositioning, constrain height if still needed
192
- this.applyHeightConstraint();
193
- } else if (this.openPositions.length > 0) {
194
- this.releasePositions();
195
- this.clearHeightConstraint();
196
- }
197
- });
198
171
  };
199
172
  /**
200
- * Repositions the menu based on collisions.
173
+ * Internal close handler used by multiple entry points.
201
174
  * @private
202
175
  */
203
- this.repositionMenu = openPositions => {
204
- this.menuPosition = openPositions[0];
205
- if (this.positioning) {
206
- this.positioning.position = this.menuPosition;
176
+ this.closeMenuInternal = () => {
177
+ if (!this._open) {
178
+ return;
179
+ }
180
+ this.setMenuOpenState(false);
181
+ this.removeRepositioningListeners();
182
+ document.removeEventListener("click", this.documentClickHandler);
183
+ if (this.closeOnScroll) {
184
+ document.removeEventListener("scroll", this.closeMenu);
207
185
  }
208
186
  };
209
187
  /**
210
- * Updates the menu position on window changes.
188
+ * Delegates resize + flip logic to the positioning strategy.
211
189
  * @private
212
190
  */
213
- this.updateMenuPosition = () => {
214
- if (this._open) {
215
- this.observeMenuOverflow();
191
+ this.repositionMenu = () => {
192
+ var _a;
193
+ if (!this.positioning || !this._menuList) {
194
+ return;
216
195
  }
196
+ this.positioning.position = (_a = this.menuPosition) !== null && _a !== void 0 ? _a : MenuPositions.below;
197
+ this.positioning.reposition();
217
198
  };
218
199
  /**
219
- * Debounced handler for window changes.
200
+ * Debounced handler for window resize/scroll changes.
220
201
  * @private
221
202
  */
222
203
  this.handleWindowChanges = debounce(() => {
223
- this.updateMenuPosition();
204
+ if (this._open) {
205
+ this.scheduleReposition();
206
+ }
224
207
  }, 50);
225
208
  /**
226
209
  * Toggles the open state of the menu.
227
210
  * @public
228
211
  */
229
212
  this.toggleMenu = () => {
230
- var _a;
231
213
  this.setComponent();
232
- (_a = this._menuList) === null || _a === void 0 ? void 0 : _a.togglePopover(!this._open);
233
214
  if (this._open) {
234
- this.focusMenuList();
235
- } else {
215
+ this.closeMenuInternal();
236
216
  this.focusTrigger();
217
+ } else {
218
+ this.openMenuInternal();
237
219
  }
238
220
  };
239
221
  /**
@@ -241,57 +223,14 @@ class Menu extends FASTElement {
241
223
  * @public
242
224
  */
243
225
  this.closeMenu = () => {
244
- var _a;
245
- if (this._open) {
246
- (_a = this._menuList) === null || _a === void 0 ? void 0 : _a.togglePopover(false);
247
- }
248
- if (this.closeOnScroll) {
249
- document.removeEventListener("scroll", this.closeMenu);
250
- }
226
+ this.closeMenuInternal();
251
227
  };
252
228
  /**
253
229
  * Opens the menu.
254
230
  * @public
255
231
  */
256
232
  this.openMenu = e => {
257
- var _a;
258
- if (!this._open) {
259
- (_a = this._menuList) === null || _a === void 0 ? void 0 : _a.togglePopover(true);
260
- }
261
- if (e && this.openOnContext) {
262
- e.preventDefault();
263
- }
264
- if (this.closeOnScroll) {
265
- document.addEventListener("scroll", this.closeMenu);
266
- }
267
- };
268
- /**
269
- * Handles the 'toggle' event on the popover.
270
- * @public
271
- * @param e - the event
272
- * @returns void
273
- */
274
- this.toggleHandler = e => {
275
- var _a;
276
- if ("newState" in e) {
277
- const event = e;
278
- const newState = event.newState === "open" ? true : false;
279
- (_a = this._trigger) === null || _a === void 0 ? void 0 : _a.setAttribute("aria-expanded", `${newState}`);
280
- this._open = newState;
281
- if (newState) {
282
- // Menu is opening - add repositioning handlers
283
- if (!this.originalMenuPosition) {
284
- this.originalMenuPosition = this.menuPosition;
285
- }
286
- this.addRepositioningHandlers();
287
- this.createPositioningManager();
288
- this.observeMenuOverflow();
289
- this.focusMenuList();
290
- } else {
291
- // Menu is closing - remove repositioning handlers
292
- this.removeRepositioningListeners();
293
- }
294
- }
233
+ this.openMenuInternal(e);
295
234
  };
296
235
  /**
297
236
  * Handles keyboard interaction for the trigger. Toggles the menu when the Space or Enter key is pressed. If the menu
@@ -315,84 +254,75 @@ class Menu extends FASTElement {
315
254
  }
316
255
  };
317
256
  /**
318
- * Handles document click events to close a menu opened with contextmenu in popover="manual" mode.
257
+ * Handles document click events to close the menu when clicking outside (light dismiss).
319
258
  * @internal
320
259
  * @param e - The event triggered on document click.
321
260
  */
322
261
  this.documentClickHandler = e => {
262
+ if (!this._open) {
263
+ return;
264
+ }
323
265
  if (!e.composedPath().some(el => el === this._trigger || el === this._menuList)) {
324
266
  this.closeMenu();
325
267
  }
326
268
  };
327
269
  }
328
270
  /**
329
- * Applies height constraint to make menu scrollable when space is limited.
330
- * @private
271
+ * Handles changes to the menuPosition attribute.
272
+ * Updates the positioning strategy and triggers a reposition if
273
+ * the menu is currently open, without mutating the public attribute.
274
+ * @public
331
275
  */
332
- applyHeightConstraint() {
333
- if (!this.positioning || !this._menuList) {
334
- return;
276
+ menuPositionChanged() {
277
+ var _a;
278
+ if (this.positioning) {
279
+ this.positioning.position = (_a = this.menuPosition) !== null && _a !== void 0 ? _a : MenuPositions.below;
335
280
  }
336
- const {
337
- needsConstraint,
338
- maxHeight
339
- } = this.positioning.calculateConstrainedHeight();
340
- if (needsConstraint && maxHeight !== null) {
341
- this.style.setProperty("--menu-max-height", `${maxHeight}px`);
342
- this._menuList.style.overflow = "auto";
343
- } else {
344
- this.clearHeightConstraint();
281
+ if (this._open) {
282
+ this.scheduleReposition();
345
283
  }
346
284
  }
347
285
  /**
348
- * Clears height constraint from the menu.
349
- * @private
286
+ * Handles menu list slot changes.
287
+ * @internal
350
288
  */
351
- clearHeightConstraint() {
352
- this.style.removeProperty("--menu-max-height");
353
- if (this._menuList) {
354
- this._menuList.style.overflow = "";
289
+ slottedMenuListChanged() {
290
+ this.setComponent();
291
+ if (this._open) {
292
+ this.scheduleReposition();
355
293
  }
356
294
  }
357
295
  /**
358
- * Releases positions and restores original if valid.
359
- * @private
296
+ * Handles trigger slot changes.
297
+ * @internal
360
298
  */
361
- releasePositions() {
362
- this.openPositions = [];
363
- if (this.positioning) {
364
- for (const key in MenuPositions) {
365
- const position = MenuPositions[key];
366
- const positionOpen = this.positioning.checkPosition(position);
367
- if (positionOpen) {
368
- this.openPositions.push(position);
369
- if (position === this.originalMenuPosition) {
370
- this.menuPosition = this.originalMenuPosition;
371
- }
372
- }
373
- }
299
+ slottedTriggersChanged() {
300
+ this.setComponent();
301
+ if (this._open) {
302
+ this.scheduleReposition();
374
303
  }
375
304
  }
376
305
  /**
377
- * Observes the menu for overflow.
306
+ * Updates menu list open state and ARIA expanded.
378
307
  * @private
379
308
  */
380
- observeMenuOverflow() {
381
- var _a, _b;
382
- if (this.overflowBoundary) {
383
- // Use direct rect calculations for custom boundary
384
- // Need a small delay to ensure menu is rendered and positioned
385
- requestAnimationFrame(() => {
386
- this.checkOverflowWithBoundary();
387
- });
388
- } else {
389
- // Use IntersectionObserver for viewport
390
- (_a = this.intersectionObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
391
- if (this._menuList) {
392
- (_b = this.intersectionObserver) === null || _b === void 0 ? void 0 : _b.observe(this._menuList);
393
- }
309
+ setMenuOpenState(isOpen) {
310
+ var _a;
311
+ this._open = isOpen;
312
+ (_a = this._trigger) === null || _a === void 0 ? void 0 : _a.setAttribute("aria-expanded", `${isOpen}`);
313
+ if (this._menuList) {
314
+ this._menuList.classList.toggle("menu-open", isOpen);
394
315
  }
395
316
  }
317
+ /**
318
+ * Schedules a reposition on the next microtask.
319
+ * @private
320
+ */
321
+ scheduleReposition() {
322
+ Updates.enqueue(() => {
323
+ this.repositionMenu();
324
+ });
325
+ }
396
326
  /**
397
327
  * Adds window event listeners.
398
328
  * @private
@@ -424,7 +354,6 @@ class Menu extends FASTElement {
424
354
  */
425
355
  addRepositioningHandlers() {
426
356
  if (this.repositionMode !== MenuRepositionModes.none) {
427
- this.createOverflowHandler();
428
357
  this.addWindowEventListeners();
429
358
  this.addOverflowBoundaryEventListeners();
430
359
  }
@@ -435,11 +364,11 @@ class Menu extends FASTElement {
435
364
  */
436
365
  createPositioningManager() {
437
366
  if (this._menuList && this._trigger && this.repositionMode !== MenuRepositionModes.none) {
367
+ this.positioning = null;
438
368
  this.positioning = new MenuPositioning({
439
369
  menuReference: this._menuList,
440
370
  anchorReference: this._trigger,
441
- overflowBoundaryReference: this.overflowBoundary,
442
- repositionMode: this.repositionMode
371
+ overflowBoundaryReference: this.overflowBoundary
443
372
  });
444
373
  }
445
374
  }
@@ -448,11 +377,8 @@ class Menu extends FASTElement {
448
377
  * @private
449
378
  */
450
379
  removeRepositioningListeners() {
451
- var _a;
452
380
  if (this._menuList) {
453
381
  this.removeWindowEventListeners();
454
- (_a = this.intersectionObserver) === null || _a === void 0 ? void 0 : _a.unobserve(this._menuList);
455
- this.clearHeightConstraint();
456
382
  }
457
383
  }
458
384
  /**
@@ -463,7 +389,6 @@ class Menu extends FASTElement {
463
389
  super.connectedCallback();
464
390
  Updates.enqueue(() => {
465
391
  this.setComponent();
466
- this.setMenuPosition();
467
392
  });
468
393
  }
469
394
  /**
@@ -474,6 +399,8 @@ class Menu extends FASTElement {
474
399
  super.disconnectedCallback();
475
400
  this.removeListeners();
476
401
  this.removeRepositioningListeners();
402
+ document.removeEventListener("click", this.documentClickHandler);
403
+ document.removeEventListener("scroll", this.closeMenu);
477
404
  }
478
405
  /**
479
406
  * Sets the component.
@@ -485,19 +412,10 @@ class Menu extends FASTElement {
485
412
  this._menuList = this.slottedMenuList[0];
486
413
  this._trigger.setAttribute("aria-haspopup", "true");
487
414
  this._trigger.setAttribute("aria-expanded", `${this._open}`);
488
- this._menuList.setAttribute("popover", this.openOnContext ? "manual" : "");
415
+ this._menuList.setAttribute("data-menu-list", "");
489
416
  this.addListeners();
490
417
  }
491
418
  }
492
- /**
493
- * Sets the menu position.
494
- * @public
495
- */
496
- setMenuPosition() {
497
- if (this.$fastController.isConnected) {
498
- this.style.setProperty("--menu-position-area", this.menuPosition === MenuPositions.above ? "block-start span-inline-end" : "block-end span-inline-end");
499
- }
500
- }
501
419
  /**
502
420
  * Focuses on the menu list.
503
421
  * @public
@@ -524,7 +442,7 @@ class Menu extends FASTElement {
524
442
  * @param newValue - The new value of 'openOnHover'.
525
443
  * @public
526
444
  */
527
- openOnHoverChanged(oldValue, newValue) {
445
+ openOnHoverChanged(_oldValue, newValue) {
528
446
  var _a, _b;
529
447
  if (newValue) {
530
448
  (_a = this._trigger) === null || _a === void 0 ? void 0 : _a.addEventListener("mouseover", this.openMenu);
@@ -539,7 +457,7 @@ class Menu extends FASTElement {
539
457
  * @param oldValue - The previous value of 'persistOnItemClick'.
540
458
  * @param newValue - The new value of 'persistOnItemClick'.
541
459
  */
542
- persistOnItemClickChanged(oldValue, newValue) {
460
+ persistOnItemClickChanged(_oldValue, newValue) {
543
461
  var _a, _b;
544
462
  if (!newValue) {
545
463
  (_a = this._menuList) === null || _a === void 0 ? void 0 : _a.addEventListener("change", this.closeMenu);
@@ -548,25 +466,40 @@ class Menu extends FASTElement {
548
466
  }
549
467
  }
550
468
  /**
551
- * Called whenever the 'menuPosition' property changes.
469
+ * Called whenever the 'repositionMode' property changes.
552
470
  *
553
- * @param oldValue - The previous value of 'menuPosition'.
554
- * @param newValue - The new value of 'menuPosition'.
555
471
  * @public
472
+ * @param oldValue - The previous value of 'repositionMode'.
473
+ * @param newValue - The new value of 'repositionMode'.
556
474
  */
557
- menuPositionChanged() {
558
- this.setMenuPosition();
475
+ repositionModeChanged(_oldValue, newValue) {
476
+ if (newValue === MenuRepositionModes.none) {
477
+ this.positioning = null;
478
+ } else if (!this.positioning && this._open) {
479
+ this.createPositioningManager();
480
+ }
481
+ if (this._open) {
482
+ this.scheduleReposition();
483
+ }
559
484
  }
560
485
  /**
561
- * Called whenever the 'repositionMode' property changes.
486
+ * Called whenever the 'overflowBoundary' property changes.
562
487
  *
563
488
  * @public
564
- * @param oldValue - The previous value of 'repositionMode'.
565
- * @param newValue - The new value of 'repositionMode'.
566
489
  */
567
- repositionModeChanged(oldValue, newValue) {
568
- if (this.positioning) {
569
- this.positioning.repositionMode = newValue;
490
+ overflowBoundaryChanged(oldValue, newValue) {
491
+ var _a;
492
+ if (oldValue) {
493
+ oldValue.removeEventListener("scroll", this.handleWindowChanges);
494
+ }
495
+ if (this._open && this.repositionMode !== MenuRepositionModes.none) {
496
+ if (newValue) {
497
+ newValue.addEventListener("scroll", this.handleWindowChanges);
498
+ }
499
+ (_a = this.positioning) === null || _a === void 0 ? void 0 : _a.setOverflowBoundaryReference(newValue !== null && newValue !== void 0 ? newValue : null);
500
+ this.scheduleReposition();
501
+ } else if (this.positioning) {
502
+ this.positioning.setOverflowBoundaryReference(newValue !== null && newValue !== void 0 ? newValue : null);
570
503
  }
571
504
  }
572
505
  /**
@@ -576,7 +509,7 @@ class Menu extends FASTElement {
576
509
  * @param oldValue - The previous value of 'openOnContext'.
577
510
  * @param newValue - The new value of 'openOnContext'.
578
511
  */
579
- openOnContextChanged(oldValue, newValue) {
512
+ openOnContextChanged(_oldValue, newValue) {
580
513
  var _a, _b;
581
514
  if (newValue) {
582
515
  (_a = this._trigger) === null || _a === void 0 ? void 0 : _a.addEventListener("contextmenu", this.openMenu);
@@ -591,7 +524,7 @@ class Menu extends FASTElement {
591
524
  * @param oldValue - The previous value of 'closeOnScroll'.
592
525
  * @param newValue - The new value of 'closeOnScroll'.
593
526
  */
594
- closeOnScrollChanged(oldValue, newValue) {
527
+ closeOnScrollChanged(_oldValue, newValue) {
595
528
  if (newValue) {
596
529
  document.addEventListener("scroll", this.closeMenu);
597
530
  } else {
@@ -604,19 +537,17 @@ class Menu extends FASTElement {
604
537
  * @internal
605
538
  */
606
539
  addListeners() {
607
- var _a, _b, _c, _d, _e, _f;
608
- (_a = this._menuList) === null || _a === void 0 ? void 0 : _a.addEventListener("toggle", this.toggleHandler);
609
- (_b = this._trigger) === null || _b === void 0 ? void 0 : _b.addEventListener("keydown", this.triggerKeydownHandler);
540
+ var _a, _b, _c, _d, _e;
541
+ (_a = this._trigger) === null || _a === void 0 ? void 0 : _a.addEventListener("keydown", this.triggerKeydownHandler);
610
542
  if (!this.persistOnItemClick) {
611
- (_c = this._menuList) === null || _c === void 0 ? void 0 : _c.addEventListener("change", this.closeMenu);
543
+ (_b = this._menuList) === null || _b === void 0 ? void 0 : _b.addEventListener("change", this.closeMenu);
612
544
  }
613
545
  if (this.openOnHover) {
614
- (_d = this._trigger) === null || _d === void 0 ? void 0 : _d.addEventListener("mouseover", this.openMenu);
546
+ (_c = this._trigger) === null || _c === void 0 ? void 0 : _c.addEventListener("mouseover", this.openMenu);
615
547
  } else if (this.openOnContext) {
616
- (_e = this._trigger) === null || _e === void 0 ? void 0 : _e.addEventListener("contextmenu", this.openMenu);
617
- document.addEventListener("click", this.documentClickHandler);
548
+ (_d = this._trigger) === null || _d === void 0 ? void 0 : _d.addEventListener("contextmenu", this.openMenu);
618
549
  } else {
619
- (_f = this._trigger) === null || _f === void 0 ? void 0 : _f.addEventListener("click", this.toggleMenu);
550
+ (_e = this._trigger) === null || _e === void 0 ? void 0 : _e.addEventListener("click", this.toggleMenu);
620
551
  }
621
552
  }
622
553
  /**
@@ -625,20 +556,18 @@ class Menu extends FASTElement {
625
556
  * @internal
626
557
  */
627
558
  removeListeners() {
628
- var _a, _b, _c, _d, _e, _f;
629
- (_a = this._menuList) === null || _a === void 0 ? void 0 : _a.removeEventListener("toggle", this.toggleHandler);
630
- (_b = this._trigger) === null || _b === void 0 ? void 0 : _b.removeEventListener("keydown", this.triggerKeydownHandler);
559
+ var _a, _b, _c, _d, _e;
560
+ (_a = this._trigger) === null || _a === void 0 ? void 0 : _a.removeEventListener("keydown", this.triggerKeydownHandler);
631
561
  if (!this.persistOnItemClick) {
632
- (_c = this._menuList) === null || _c === void 0 ? void 0 : _c.removeEventListener("change", this.closeMenu);
562
+ (_b = this._menuList) === null || _b === void 0 ? void 0 : _b.removeEventListener("change", this.closeMenu);
633
563
  }
634
564
  if (this.openOnHover) {
635
- (_d = this._trigger) === null || _d === void 0 ? void 0 : _d.removeEventListener("mouseover", this.openMenu);
565
+ (_c = this._trigger) === null || _c === void 0 ? void 0 : _c.removeEventListener("mouseover", this.openMenu);
636
566
  }
637
567
  if (this.openOnContext) {
638
- (_e = this._trigger) === null || _e === void 0 ? void 0 : _e.removeEventListener("contextmenu", this.openMenu);
639
- document.removeEventListener("click", this.documentClickHandler);
568
+ (_d = this._trigger) === null || _d === void 0 ? void 0 : _d.removeEventListener("contextmenu", this.openMenu);
640
569
  } else {
641
- (_f = this._trigger) === null || _f === void 0 ? void 0 : _f.removeEventListener("click", this.toggleMenu);
570
+ (_e = this._trigger) === null || _e === void 0 ? void 0 : _e.removeEventListener("click", this.toggleMenu);
642
571
  }
643
572
  }
644
573
  /**
@@ -648,7 +577,6 @@ class Menu extends FASTElement {
648
577
  * @public
649
578
  */
650
579
  menuKeydownHandler(e) {
651
- var _a;
652
580
  if (e.defaultPrevented) {
653
581
  return;
654
582
  }
@@ -657,7 +585,7 @@ class Menu extends FASTElement {
657
585
  case keyEscape:
658
586
  e.preventDefault();
659
587
  if (this._open) {
660
- (_a = this._menuList) === null || _a === void 0 ? void 0 : _a.togglePopover(false);
588
+ this.closeMenuInternal();
661
589
  this.focusTrigger();
662
590
  }
663
591
  break;