@adobe/spectrum-wc-core 0.0.0-snapshot.20260922095923

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 (335) hide show
  1. package/README.md +39 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.124.0/helpers/decorate.js +9 -0
  3. package/dist/components/accordion/Accordion.base.d.ts +59 -0
  4. package/dist/components/accordion/Accordion.base.js +69 -0
  5. package/dist/components/accordion/Accordion.base.js.map +1 -0
  6. package/dist/components/accordion/Accordion.types.d.ts +22 -0
  7. package/dist/components/accordion/Accordion.types.js +21 -0
  8. package/dist/components/accordion/Accordion.types.js.map +1 -0
  9. package/dist/components/accordion/AccordionItem.base.d.ts +96 -0
  10. package/dist/components/accordion/AccordionItem.base.js +93 -0
  11. package/dist/components/accordion/AccordionItem.base.js.map +1 -0
  12. package/dist/components/accordion/index.d.ts +14 -0
  13. package/dist/components/accordion/index.js +4 -0
  14. package/dist/components/action-button/ActionButton.types.d.ts +15 -0
  15. package/dist/components/action-button/ActionButton.types.js +12 -0
  16. package/dist/components/action-button/ActionButton.types.js.map +1 -0
  17. package/dist/components/action-button/index.d.ts +12 -0
  18. package/dist/components/action-button/index.js +2 -0
  19. package/dist/components/action-group/ActionGroup.base.d.ts +108 -0
  20. package/dist/components/action-group/ActionGroup.base.js +53 -0
  21. package/dist/components/action-group/ActionGroup.base.js.map +1 -0
  22. package/dist/components/action-group/ActionGroup.types.d.ts +17 -0
  23. package/dist/components/action-group/ActionGroup.types.js +12 -0
  24. package/dist/components/action-group/ActionGroup.types.js.map +1 -0
  25. package/dist/components/action-group/index.d.ts +13 -0
  26. package/dist/components/action-group/index.js +3 -0
  27. package/dist/components/alert-banner/AlertBanner.base.d.ts +36 -0
  28. package/dist/components/alert-banner/AlertBanner.base.js +57 -0
  29. package/dist/components/alert-banner/AlertBanner.base.js.map +1 -0
  30. package/dist/components/alert-banner/AlertBanner.types.d.ts +13 -0
  31. package/dist/components/alert-banner/AlertBanner.types.js +10 -0
  32. package/dist/components/alert-banner/AlertBanner.types.js.map +1 -0
  33. package/dist/components/alert-banner/index.d.ts +13 -0
  34. package/dist/components/alert-banner/index.js +3 -0
  35. package/dist/components/asset/Asset.base.d.ts +101 -0
  36. package/dist/components/asset/Asset.base.js +130 -0
  37. package/dist/components/asset/Asset.base.js.map +1 -0
  38. package/dist/components/asset/Asset.types.d.ts +31 -0
  39. package/dist/components/asset/Asset.types.js +14 -0
  40. package/dist/components/asset/Asset.types.js.map +1 -0
  41. package/dist/components/asset/index.d.ts +13 -0
  42. package/dist/components/asset/index.js +3 -0
  43. package/dist/components/avatar/Avatar.base.d.ts +53 -0
  44. package/dist/components/avatar/Avatar.base.js +52 -0
  45. package/dist/components/avatar/Avatar.base.js.map +1 -0
  46. package/dist/components/avatar/Avatar.types.d.ts +19 -0
  47. package/dist/components/avatar/Avatar.types.js +24 -0
  48. package/dist/components/avatar/Avatar.types.js.map +1 -0
  49. package/dist/components/avatar/index.d.ts +13 -0
  50. package/dist/components/avatar/index.js +3 -0
  51. package/dist/components/badge/Badge.base.d.ts +114 -0
  52. package/dist/components/badge/Badge.base.js +49 -0
  53. package/dist/components/badge/Badge.base.js.map +1 -0
  54. package/dist/components/badge/Badge.types.d.ts +21 -0
  55. package/dist/components/badge/Badge.types.js +43 -0
  56. package/dist/components/badge/Badge.types.js.map +1 -0
  57. package/dist/components/badge/index.d.ts +13 -0
  58. package/dist/components/badge/index.js +3 -0
  59. package/dist/components/button/Button.base.d.ts +78 -0
  60. package/dist/components/button/Button.base.js +51 -0
  61. package/dist/components/button/Button.base.js.map +1 -0
  62. package/dist/components/button/Button.types.d.ts +19 -0
  63. package/dist/components/button/Button.types.js +16 -0
  64. package/dist/components/button/Button.types.js.map +1 -0
  65. package/dist/components/button/index.d.ts +13 -0
  66. package/dist/components/button/index.js +3 -0
  67. package/dist/components/button-group/ButtonGroup.base.d.ts +89 -0
  68. package/dist/components/button-group/ButtonGroup.base.js +60 -0
  69. package/dist/components/button-group/ButtonGroup.base.js.map +1 -0
  70. package/dist/components/button-group/ButtonGroup.types.d.ts +17 -0
  71. package/dist/components/button-group/ButtonGroup.types.js +15 -0
  72. package/dist/components/button-group/ButtonGroup.types.js.map +1 -0
  73. package/dist/components/button-group/index.d.ts +13 -0
  74. package/dist/components/button-group/index.js +3 -0
  75. package/dist/components/card/Card.base.d.ts +172 -0
  76. package/dist/components/card/Card.base.js +123 -0
  77. package/dist/components/card/Card.base.js.map +1 -0
  78. package/dist/components/card/Card.types.d.ts +18 -0
  79. package/dist/components/card/Card.types.js +21 -0
  80. package/dist/components/card/Card.types.js.map +1 -0
  81. package/dist/components/card/index.d.ts +13 -0
  82. package/dist/components/card/index.js +3 -0
  83. package/dist/components/color-handle/ColorHandle.base.d.ts +51 -0
  84. package/dist/components/color-handle/ColorHandle.base.js +39 -0
  85. package/dist/components/color-handle/ColorHandle.base.js.map +1 -0
  86. package/dist/components/color-handle/ColorHandle.types.d.ts +30 -0
  87. package/dist/components/color-handle/color-contrast.d.ts +55 -0
  88. package/dist/components/color-handle/color-contrast.js +49 -0
  89. package/dist/components/color-handle/color-contrast.js.map +1 -0
  90. package/dist/components/color-handle/index.d.ts +14 -0
  91. package/dist/components/color-handle/index.js +3 -0
  92. package/dist/components/color-loupe/ColorLoupe.base.d.ts +41 -0
  93. package/dist/components/color-loupe/ColorLoupe.base.js +21 -0
  94. package/dist/components/color-loupe/ColorLoupe.base.js.map +1 -0
  95. package/dist/components/color-loupe/index.d.ts +12 -0
  96. package/dist/components/color-loupe/index.js +2 -0
  97. package/dist/components/divider/Divider.base.d.ts +43 -0
  98. package/dist/components/divider/Divider.base.js +42 -0
  99. package/dist/components/divider/Divider.base.js.map +1 -0
  100. package/dist/components/divider/Divider.types.d.ts +15 -0
  101. package/dist/components/divider/Divider.types.js +10 -0
  102. package/dist/components/divider/Divider.types.js.map +1 -0
  103. package/dist/components/divider/index.d.ts +13 -0
  104. package/dist/components/divider/index.js +3 -0
  105. package/dist/components/dropzone/Dropzone.base.d.ts +74 -0
  106. package/dist/components/dropzone/Dropzone.base.js +82 -0
  107. package/dist/components/dropzone/Dropzone.base.js.map +1 -0
  108. package/dist/components/dropzone/Dropzone.types.d.ts +43 -0
  109. package/dist/components/dropzone/Dropzone.types.js +15 -0
  110. package/dist/components/dropzone/Dropzone.types.js.map +1 -0
  111. package/dist/components/dropzone/index.d.ts +13 -0
  112. package/dist/components/dropzone/index.js +3 -0
  113. package/dist/components/icon/Icon.base.d.ts +36 -0
  114. package/dist/components/icon/Icon.base.js +28 -0
  115. package/dist/components/icon/Icon.base.js.map +1 -0
  116. package/dist/components/icon/Icon.types.d.ts +13 -0
  117. package/dist/components/icon/Icon.types.js +12 -0
  118. package/dist/components/icon/Icon.types.js.map +1 -0
  119. package/dist/components/icon/index.d.ts +13 -0
  120. package/dist/components/icon/index.js +3 -0
  121. package/dist/components/illustrated-message/IllustratedMessage.base.d.ts +60 -0
  122. package/dist/components/illustrated-message/IllustratedMessage.base.js +59 -0
  123. package/dist/components/illustrated-message/IllustratedMessage.base.js.map +1 -0
  124. package/dist/components/illustrated-message/IllustratedMessage.types.d.ts +15 -0
  125. package/dist/components/illustrated-message/IllustratedMessage.types.js +10 -0
  126. package/dist/components/illustrated-message/IllustratedMessage.types.js.map +1 -0
  127. package/dist/components/illustrated-message/index.d.ts +13 -0
  128. package/dist/components/illustrated-message/index.js +3 -0
  129. package/dist/components/infield-button/InfieldButton.base.d.ts +28 -0
  130. package/dist/components/infield-button/InfieldButton.base.js +22 -0
  131. package/dist/components/infield-button/InfieldButton.base.js.map +1 -0
  132. package/dist/components/infield-button/InfieldButton.types.d.ts +13 -0
  133. package/dist/components/infield-button/InfieldButton.types.js +11 -0
  134. package/dist/components/infield-button/InfieldButton.types.js.map +1 -0
  135. package/dist/components/infield-button/index.d.ts +13 -0
  136. package/dist/components/infield-button/index.js +3 -0
  137. package/dist/components/meter/Meter.base.d.ts +40 -0
  138. package/dist/components/meter/Meter.base.js +32 -0
  139. package/dist/components/meter/Meter.base.js.map +1 -0
  140. package/dist/components/meter/Meter.types.d.ts +13 -0
  141. package/dist/components/meter/Meter.types.js +11 -0
  142. package/dist/components/meter/Meter.types.js.map +1 -0
  143. package/dist/components/meter/index.d.ts +13 -0
  144. package/dist/components/meter/index.js +3 -0
  145. package/dist/components/popover/Popover.base.d.ts +226 -0
  146. package/dist/components/popover/Popover.base.js +287 -0
  147. package/dist/components/popover/Popover.base.js.map +1 -0
  148. package/dist/components/popover/Popover.types.d.ts +39 -0
  149. package/dist/components/popover/Popover.types.js +24 -0
  150. package/dist/components/popover/Popover.types.js.map +1 -0
  151. package/dist/components/popover/index.d.ts +13 -0
  152. package/dist/components/popover/index.js +3 -0
  153. package/dist/components/progress-bar/ProgressBar.base.d.ts +34 -0
  154. package/dist/components/progress-bar/ProgressBar.base.js +21 -0
  155. package/dist/components/progress-bar/ProgressBar.base.js.map +1 -0
  156. package/dist/components/progress-bar/ProgressBar.types.d.ts +12 -0
  157. package/dist/components/progress-bar/index.d.ts +13 -0
  158. package/dist/components/progress-bar/index.js +2 -0
  159. package/dist/components/progress-circle/ProgressCircle.base.d.ts +83 -0
  160. package/dist/components/progress-circle/ProgressCircle.base.js +77 -0
  161. package/dist/components/progress-circle/ProgressCircle.base.js.map +1 -0
  162. package/dist/components/progress-circle/ProgressCircle.types.d.ts +15 -0
  163. package/dist/components/progress-circle/ProgressCircle.types.js +10 -0
  164. package/dist/components/progress-circle/ProgressCircle.types.js.map +1 -0
  165. package/dist/components/progress-circle/index.d.ts +13 -0
  166. package/dist/components/progress-circle/index.js +3 -0
  167. package/dist/components/status-light/StatusLight.base.d.ts +69 -0
  168. package/dist/components/status-light/StatusLight.base.js +32 -0
  169. package/dist/components/status-light/StatusLight.base.js.map +1 -0
  170. package/dist/components/status-light/StatusLight.types.d.ts +19 -0
  171. package/dist/components/status-light/StatusLight.types.js +32 -0
  172. package/dist/components/status-light/StatusLight.types.js.map +1 -0
  173. package/dist/components/status-light/index.d.ts +13 -0
  174. package/dist/components/status-light/index.js +3 -0
  175. package/dist/components/tabs/Tab.base.d.ts +49 -0
  176. package/dist/components/tabs/Tab.base.js +39 -0
  177. package/dist/components/tabs/Tab.base.js.map +1 -0
  178. package/dist/components/tabs/Tab.types.d.ts +17 -0
  179. package/dist/components/tabs/TabPanel.base.d.ts +49 -0
  180. package/dist/components/tabs/TabPanel.base.js +36 -0
  181. package/dist/components/tabs/TabPanel.base.js.map +1 -0
  182. package/dist/components/tabs/TabPanel.types.d.ts +17 -0
  183. package/dist/components/tabs/Tabs.base.d.ts +221 -0
  184. package/dist/components/tabs/Tabs.base.js +213 -0
  185. package/dist/components/tabs/Tabs.base.js.map +1 -0
  186. package/dist/components/tabs/Tabs.types.d.ts +55 -0
  187. package/dist/components/tabs/Tabs.types.js +6 -0
  188. package/dist/components/tabs/Tabs.types.js.map +1 -0
  189. package/dist/components/tabs/index.d.ts +17 -0
  190. package/dist/components/tabs/index.js +5 -0
  191. package/dist/components/tooltip/Tooltip.base.d.ts +159 -0
  192. package/dist/components/tooltip/Tooltip.base.js +198 -0
  193. package/dist/components/tooltip/Tooltip.base.js.map +1 -0
  194. package/dist/components/tooltip/Tooltip.types.d.ts +18 -0
  195. package/dist/components/tooltip/Tooltip.types.js +15 -0
  196. package/dist/components/tooltip/Tooltip.types.js.map +1 -0
  197. package/dist/components/tooltip/index.d.ts +13 -0
  198. package/dist/components/tooltip/index.js +3 -0
  199. package/dist/controllers/color-controller/index.d.ts +12 -0
  200. package/dist/controllers/color-controller/index.js +2 -0
  201. package/dist/controllers/color-controller/src/color-controller.d.ts +226 -0
  202. package/dist/controllers/color-controller/src/color-controller.js +253 -0
  203. package/dist/controllers/color-controller/src/color-controller.js.map +1 -0
  204. package/dist/controllers/drag-and-drop-controller/index.d.ts +12 -0
  205. package/dist/controllers/drag-and-drop-controller/index.js +2 -0
  206. package/dist/controllers/drag-and-drop-controller/src/drag-and-drop-controller.d.ts +68 -0
  207. package/dist/controllers/drag-and-drop-controller/src/drag-and-drop-controller.js +56 -0
  208. package/dist/controllers/drag-and-drop-controller/src/drag-and-drop-controller.js.map +1 -0
  209. package/dist/controllers/focusgroup-navigation-controller/index.d.ts +12 -0
  210. package/dist/controllers/focusgroup-navigation-controller/index.js +2 -0
  211. package/dist/controllers/focusgroup-navigation-controller/src/focusgroup-navigation-controller.d.ts +496 -0
  212. package/dist/controllers/focusgroup-navigation-controller/src/focusgroup-navigation-controller.js +345 -0
  213. package/dist/controllers/focusgroup-navigation-controller/src/focusgroup-navigation-controller.js.map +1 -0
  214. package/dist/controllers/hover-controller/index.d.ts +12 -0
  215. package/dist/controllers/hover-controller/index.js +2 -0
  216. package/dist/controllers/hover-controller/src/hover-controller.d.ts +118 -0
  217. package/dist/controllers/hover-controller/src/hover-controller.js +90 -0
  218. package/dist/controllers/hover-controller/src/hover-controller.js.map +1 -0
  219. package/dist/controllers/index.d.ts +26 -0
  220. package/dist/controllers/index.js +18 -0
  221. package/dist/controllers/language-resolution.d.ts +87 -0
  222. package/dist/controllers/language-resolution.js +56 -0
  223. package/dist/controllers/language-resolution.js.map +1 -0
  224. package/dist/controllers/live-selection-controller/index.d.ts +12 -0
  225. package/dist/controllers/live-selection-controller/index.js +2 -0
  226. package/dist/controllers/live-selection-controller/src/live-selection-controller.d.ts +144 -0
  227. package/dist/controllers/live-selection-controller/src/live-selection-controller.js +36 -0
  228. package/dist/controllers/live-selection-controller/src/live-selection-controller.js.map +1 -0
  229. package/dist/controllers/page-scroll-lock-controller/index.d.ts +12 -0
  230. package/dist/controllers/page-scroll-lock-controller/index.js +2 -0
  231. package/dist/controllers/page-scroll-lock-controller/src/page-scroll-lock.d.ts +20 -0
  232. package/dist/controllers/page-scroll-lock-controller/src/page-scroll-lock.js +30 -0
  233. package/dist/controllers/page-scroll-lock-controller/src/page-scroll-lock.js.map +1 -0
  234. package/dist/controllers/pending-controller/index.d.ts +12 -0
  235. package/dist/controllers/pending-controller/index.js +2 -0
  236. package/dist/controllers/pending-controller/src/pending-controller.d.ts +95 -0
  237. package/dist/controllers/pending-controller/src/pending-controller.js +55 -0
  238. package/dist/controllers/pending-controller/src/pending-controller.js.map +1 -0
  239. package/dist/controllers/placement-controller/index.d.ts +12 -0
  240. package/dist/controllers/placement-controller/index.js +4 -0
  241. package/dist/controllers/placement-controller/src/fallback-placements.d.ts +9 -0
  242. package/dist/controllers/placement-controller/src/fallback-placements.js +71 -0
  243. package/dist/controllers/placement-controller/src/fallback-placements.js.map +1 -0
  244. package/dist/controllers/placement-controller/src/placement-controller.d.ts +152 -0
  245. package/dist/controllers/placement-controller/src/placement-controller.js +129 -0
  246. package/dist/controllers/placement-controller/src/placement-controller.js.map +1 -0
  247. package/dist/controllers/placement-controller/src/placement-conversion.d.ts +35 -0
  248. package/dist/controllers/placement-controller/src/placement-conversion.js +54 -0
  249. package/dist/controllers/placement-controller/src/placement-conversion.js.map +1 -0
  250. package/dist/controllers/placement-controller/src/types.d.ts +128 -0
  251. package/dist/controllers/placement-controller/src/types.js +29 -0
  252. package/dist/controllers/placement-controller/src/types.js.map +1 -0
  253. package/dist/controllers/slot-attribute-propagation-controller/index.d.ts +12 -0
  254. package/dist/controllers/slot-attribute-propagation-controller/index.js +2 -0
  255. package/dist/controllers/slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.d.ts +65 -0
  256. package/dist/controllers/slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.js +33 -0
  257. package/dist/controllers/slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.js.map +1 -0
  258. package/dist/controllers/slot-presence-controller/index.d.ts +12 -0
  259. package/dist/controllers/slot-presence-controller/index.js +2 -0
  260. package/dist/controllers/slot-presence-controller/src/slot-presence-controller.d.ts +57 -0
  261. package/dist/controllers/slot-presence-controller/src/slot-presence-controller.js +44 -0
  262. package/dist/controllers/slot-presence-controller/src/slot-presence-controller.js.map +1 -0
  263. package/dist/controllers/slot-text-controller/index.d.ts +12 -0
  264. package/dist/controllers/slot-text-controller/index.js +2 -0
  265. package/dist/controllers/slot-text-controller/src/slot-text-controller.d.ts +92 -0
  266. package/dist/controllers/slot-text-controller/src/slot-text-controller.js +53 -0
  267. package/dist/controllers/slot-text-controller/src/slot-text-controller.js.map +1 -0
  268. package/dist/directives/index.d.ts +15 -0
  269. package/dist/directives/index.js +3 -0
  270. package/dist/directives/pending-spinner/index.d.ts +12 -0
  271. package/dist/directives/pending-spinner/index.js +1 -0
  272. package/dist/directives/pending-spinner/src/pending-spinner.d.ts +22 -0
  273. package/dist/directives/pending-spinner/src/pending-spinner.js +39 -0
  274. package/dist/directives/pending-spinner/src/pending-spinner.js.map +1 -0
  275. package/dist/element/define-element.d.ts +16 -0
  276. package/dist/element/define-element.js +8 -0
  277. package/dist/element/define-element.js.map +1 -0
  278. package/dist/element/index.d.ts +14 -0
  279. package/dist/element/index.js +4 -0
  280. package/dist/element/spectrum-element.d.ts +69 -0
  281. package/dist/element/spectrum-element.js +85 -0
  282. package/dist/element/spectrum-element.js.map +1 -0
  283. package/dist/element/version.d.ts +19 -0
  284. package/dist/element/version.js +6 -0
  285. package/dist/element/version.js.map +1 -0
  286. package/dist/mixins/disabled-mixin.d.ts +30 -0
  287. package/dist/mixins/disabled-mixin.js +27 -0
  288. package/dist/mixins/disabled-mixin.js.map +1 -0
  289. package/dist/mixins/index.d.ts +16 -0
  290. package/dist/mixins/index.js +5 -0
  291. package/dist/mixins/linear-progress-mixin.d.ts +41 -0
  292. package/dist/mixins/linear-progress-mixin.js +115 -0
  293. package/dist/mixins/linear-progress-mixin.js.map +1 -0
  294. package/dist/mixins/pending-mixin.d.ts +45 -0
  295. package/dist/mixins/pending-mixin.js +50 -0
  296. package/dist/mixins/pending-mixin.js.map +1 -0
  297. package/dist/mixins/sized-mixin.d.ts +49 -0
  298. package/dist/mixins/sized-mixin.js +42 -0
  299. package/dist/mixins/sized-mixin.js.map +1 -0
  300. package/dist/utils/actual-placement.d.ts +10 -0
  301. package/dist/utils/actual-placement.js +8 -0
  302. package/dist/utils/actual-placement.js.map +1 -0
  303. package/dist/utils/capitalize.d.ts +26 -0
  304. package/dist/utils/capitalize.js +8 -0
  305. package/dist/utils/capitalize.js.map +1 -0
  306. package/dist/utils/deep-contains.d.ts +26 -0
  307. package/dist/utils/deep-contains.js +14 -0
  308. package/dist/utils/deep-contains.js.map +1 -0
  309. package/dist/utils/dev-validation.d.ts +88 -0
  310. package/dist/utils/dev-validation.js +33 -0
  311. package/dist/utils/dev-validation.js.map +1 -0
  312. package/dist/utils/dismissible-stack.d.ts +26 -0
  313. package/dist/utils/dismissible-stack.js +16 -0
  314. package/dist/utils/dismissible-stack.js.map +1 -0
  315. package/dist/utils/focusable-selectors.d.ts +43 -0
  316. package/dist/utils/focusable-selectors.js +18 -0
  317. package/dist/utils/focusable-selectors.js.map +1 -0
  318. package/dist/utils/get-active-element.d.ts +31 -0
  319. package/dist/utils/get-active-element.js +11 -0
  320. package/dist/utils/get-active-element.js.map +1 -0
  321. package/dist/utils/get-label-from-slot.d.ts +12 -0
  322. package/dist/utils/get-label-from-slot.js +10 -0
  323. package/dist/utils/get-label-from-slot.js.map +1 -0
  324. package/dist/utils/index.d.ts +22 -0
  325. package/dist/utils/index.js +12 -0
  326. package/dist/utils/is-focus-visible-in-tree.d.ts +33 -0
  327. package/dist/utils/is-focus-visible-in-tree.js +11 -0
  328. package/dist/utils/is-focus-visible-in-tree.js.map +1 -0
  329. package/dist/utils/resolve-trigger.d.ts +42 -0
  330. package/dist/utils/resolve-trigger.js +23 -0
  331. package/dist/utils/resolve-trigger.js.map +1 -0
  332. package/dist/utils/transition.d.ts +53 -0
  333. package/dist/utils/transition.js +26 -0
  334. package/dist/utils/transition.js.map +1 -0
  335. package/package.json +644 -0
@@ -0,0 +1,345 @@
1
+ //#region controllers/focusgroup-navigation-controller/src/focusgroup-navigation-controller.ts
2
+ var e = {
3
+ wrap: !1,
4
+ memory: !0,
5
+ skipDisabled: !1
6
+ }, t = 6, n = "swc-focusgroup-navigation-active-change", r = class {
7
+ constructor(t, n) {
8
+ this.boundKeydown = this.handleKeydown.bind(this), this.boundFocusin = this.handleFocusin.bind(this), this.boundFocusout = this.handleFocusout.bind(this), this.lastFocused = null, this.previousActive = null, this.isNavigating = !1, this.cachedEligibleItems = null, this.cachedRows = null, this.host = t, this.options = {
9
+ ...e,
10
+ ...n
11
+ }, t.addController(this);
12
+ }
13
+ setOptions(e) {
14
+ this.options = {
15
+ ...this.options,
16
+ ...e
17
+ }, this.refresh();
18
+ }
19
+ getActiveItem() {
20
+ for (let e of this.getEligibleItems()) if (e.tabIndex === 0) return e;
21
+ return null;
22
+ }
23
+ refresh() {
24
+ var e, t;
25
+ this.cachedEligibleItems = null, this.cachedRows = null;
26
+ let n = this.getEligibleItems();
27
+ if (n.length === 0) {
28
+ for (let e of this.getRawItems()) e.tabIndex = -1;
29
+ if (this.lastFocused = null, this.previousActive !== null) {
30
+ var r, i;
31
+ this.previousActive = null, this.dispatchActiveChange(null, "refresh"), (r = (i = this.options).onActiveItemChange) == null || r.call(i, null);
32
+ }
33
+ return;
34
+ }
35
+ let a = (e = (t = this.options.memory && this.lastFocused && n.includes(this.lastFocused) ? this.lastFocused : null) == null ? this.getActiveItem() : t) == null ? n[0] : e;
36
+ this.applyRovingTabindex(a, "refresh");
37
+ }
38
+ setActiveItem(e) {
39
+ return this.getEligibleItems().includes(e) ? (this.applyRovingTabindex(e, "programmatic"), this.options.memory && (this.lastFocused = e), !0) : !1;
40
+ }
41
+ focusFirstItemByTextPrefix(e) {
42
+ let t = e.trim();
43
+ if (t === "") return !1;
44
+ let n = t.toLowerCase(), r = this.getEligibleItems().find((e) => this.getItemTypeaheadLabel(e).toLowerCase().startsWith(n));
45
+ return r ? (this.applyRovingTabindex(r, "programmatic"), !0) : !1;
46
+ }
47
+ hostConnected() {
48
+ this.previousActive = null, this.cachedEligibleItems = null, this.cachedRows = null, this.host.addEventListener("keydown", this.boundKeydown, !0), this.host.addEventListener("focusin", this.boundFocusin, !0), this.host.addEventListener("focusout", this.boundFocusout, !0), this.refresh();
49
+ }
50
+ hostDisconnected() {
51
+ this.host.removeEventListener("keydown", this.boundKeydown, !0), this.host.removeEventListener("focusin", this.boundFocusin, !0), this.host.removeEventListener("focusout", this.boundFocusout, !0);
52
+ }
53
+ isRtl() {
54
+ return getComputedStyle(this.host).direction === "rtl";
55
+ }
56
+ isNodeWithinHostScope(e) {
57
+ if (!e) return !1;
58
+ let t = this.host, n = e;
59
+ for (; n;) {
60
+ if (n === t) return !0;
61
+ let e = n.parentNode;
62
+ if (e) n = e;
63
+ else if (n instanceof ShadowRoot) n = n.host;
64
+ else return !1;
65
+ }
66
+ return !1;
67
+ }
68
+ getRawItems() {
69
+ return this.options.getItems().filter((e) => this.isNodeWithinHostScope(e));
70
+ }
71
+ getEligibleItems() {
72
+ return this.cachedEligibleItems || (this.cachedEligibleItems = this.getRawItems().filter((e) => this.isNavigableItem(e))), this.cachedEligibleItems;
73
+ }
74
+ getRows(e) {
75
+ return this.cachedRows || (this.cachedRows = this.buildRows(e)), this.cachedRows;
76
+ }
77
+ isNavigableItem(e) {
78
+ if (!e.isConnected || e.hasAttribute("inert") || e.closest("[inert]")) return !1;
79
+ let t = getComputedStyle(e);
80
+ return !(t.visibility === "hidden" || t.display === "none" || this.options.skipDisabled && this.isDisabledForSkip(e));
81
+ }
82
+ isDisabledForSkip(e) {
83
+ return "disabled" in e && e.disabled ? !0 : e.getAttribute("aria-disabled") === "true";
84
+ }
85
+ getItemTypeaheadLabel(e) {
86
+ var t, n, r, i;
87
+ let a = (t = e.getAttribute("aria-label")) == null ? void 0 : t.trim();
88
+ if (a) return a;
89
+ let o = (n = e.getAttribute("aria-labelledby")) == null ? void 0 : n.trim();
90
+ if (o) {
91
+ let t = e.getRootNode(), n = [];
92
+ for (let r of o.split(/\s+/)) {
93
+ var s, c;
94
+ if (!r) continue;
95
+ let i = t instanceof ShadowRoot ? (s = t.getElementById(r)) == null ? e.ownerDocument.getElementById(r) : s : e.ownerDocument.getElementById(r), a = i == null || (c = i.textContent) == null ? void 0 : c.trim();
96
+ a && n.push(a);
97
+ }
98
+ let r = n.join(" ").trim();
99
+ if (r) return r;
100
+ }
101
+ return (r = (i = e.textContent) == null ? void 0 : i.trim()) == null ? "" : r;
102
+ }
103
+ isNativelyDisabled(e) {
104
+ return "disabled" in e && e.disabled === !0;
105
+ }
106
+ applyRovingTabindex(e, t) {
107
+ let n = this.getEligibleItems(), r = new Set(n);
108
+ for (let e of this.getRawItems()) r.has(e) || (e.tabIndex = -1);
109
+ if (n.length === 0) return;
110
+ let i = r.has(e) ? e : n[0], a = i;
111
+ if (this.isNativelyDisabled(i)) {
112
+ var o;
113
+ i = (o = n.find((e) => !this.isNativelyDisabled(e))) == null ? i : o;
114
+ }
115
+ for (let e of n) e === i ? e.tabIndex = 0 : e.tabIndex = -1;
116
+ if (a !== this.previousActive) {
117
+ var s, c;
118
+ this.previousActive = a, this.dispatchActiveChange(a, t), (s = (c = this.options).onActiveItemChange) == null || s.call(c, i);
119
+ }
120
+ }
121
+ dispatchActiveChange(e, t) {
122
+ this.host.dispatchEvent(new CustomEvent(n, {
123
+ bubbles: !0,
124
+ composed: !0,
125
+ detail: {
126
+ activeElement: e,
127
+ source: t
128
+ }
129
+ }));
130
+ }
131
+ resolveManagedFocusTarget(e, t) {
132
+ if (t.length === 0) return null;
133
+ let n = new Set(t);
134
+ for (let t of e.composedPath()) if (t instanceof HTMLElement) {
135
+ if (n.has(t)) return t;
136
+ if (t === this.host) break;
137
+ }
138
+ let r = this.host.shadowRoot, i = r == null ? void 0 : r.activeElement;
139
+ return i instanceof HTMLElement && n.has(i) ? i : null;
140
+ }
141
+ handleFocusin(e) {
142
+ if (this.isNavigating) return;
143
+ this.cachedEligibleItems = null, this.cachedRows = null;
144
+ let t = this.getEligibleItems(), n = this.resolveManagedFocusTarget(e, t);
145
+ n && (this.applyRovingTabindex(n, "focus"), this.options.memory && (this.lastFocused = n));
146
+ }
147
+ handleFocusout(e) {
148
+ let t = e.relatedTarget;
149
+ if (t instanceof Node && this.isNodeWithinHostScope(t)) return;
150
+ let n = e.target;
151
+ if (this.options.memory && n instanceof HTMLElement && this.getRawItems().includes(n) && (this.lastFocused = n), !this.options.memory) {
152
+ this.cachedEligibleItems = null, this.cachedRows = null;
153
+ let e = this.getEligibleItems();
154
+ e.length > 0 && this.applyRovingTabindex(e[0], "focus");
155
+ }
156
+ }
157
+ resolveManagedKeydownTarget(e, t) {
158
+ if (t.length === 0) return null;
159
+ let n = new Set(t);
160
+ for (let t of e.composedPath()) if (t instanceof HTMLElement) {
161
+ if (n.has(t)) return t;
162
+ if (t === this.host) break;
163
+ }
164
+ let r = this.host.shadowRoot, i = r == null ? void 0 : r.activeElement;
165
+ return i instanceof HTMLElement && n.has(i) ? i : null;
166
+ }
167
+ handleKeydown(e) {
168
+ if (e.defaultPrevented || e.altKey) return;
169
+ this.cachedEligibleItems = null, this.cachedRows = null;
170
+ let t = this.getEligibleItems(), n = this.resolveManagedKeydownTarget(e, t);
171
+ if (!n) return;
172
+ let r = this.options.direction === "grid", i = r ? this.getRows(t) : null;
173
+ if (r && e.ctrlKey && !e.metaKey && (e.key === "Home" || e.key === "End")) {
174
+ if (i.length > 0) {
175
+ var a, o;
176
+ let t = i[0], r = i[i.length - 1], s = e.key === "Home" ? (a = t == null ? void 0 : t[0]) == null ? null : a : (o = r == null ? void 0 : r[r.length - 1]) == null ? null : o;
177
+ s && s !== n && (e.preventDefault(), this.moveKeyNavigationFocusTo(s));
178
+ }
179
+ return;
180
+ }
181
+ if (e.ctrlKey || e.metaKey) return;
182
+ let s = this.getEffectivePageMagnitude();
183
+ if (s !== null && (e.key === "PageUp" || e.key === "PageDown")) {
184
+ let r = this.navigatePage(t, n, e.key === "PageDown" ? s : -s, i);
185
+ r && r !== n && (e.preventDefault(), this.moveKeyNavigationFocusTo(r));
186
+ return;
187
+ }
188
+ let c = this.isRtl(), l = null;
189
+ switch (this.options.direction) {
190
+ case "horizontal":
191
+ l = this.navigateLinear(t, n, e.key, "horizontal", c);
192
+ break;
193
+ case "vertical":
194
+ l = this.navigateLinear(t, n, e.key, "vertical", c);
195
+ break;
196
+ case "both":
197
+ l = this.navigateBothAxes(t, n, e.key, c);
198
+ break;
199
+ case "grid":
200
+ l = this.navigateGrid(n, e.key, c, i);
201
+ break;
202
+ default: break;
203
+ }
204
+ if (l && l !== n) {
205
+ e.preventDefault(), this.moveKeyNavigationFocusTo(l);
206
+ return;
207
+ }
208
+ if (e.key === "Home" || e.key === "End") if (r) {
209
+ let t = this.findGridIndex(i, n);
210
+ if (!t) return;
211
+ let r = i[t.row];
212
+ if (!(r != null && r.length)) return;
213
+ let a = e.key === "Home" ? r[0] : r[r.length - 1];
214
+ a && a !== n && (e.preventDefault(), this.moveKeyNavigationFocusTo(a));
215
+ } else {
216
+ if (t.length === 0) return;
217
+ let r = e.key === "Home" ? t[0] : t[t.length - 1];
218
+ r && r !== n && (e.preventDefault(), this.moveKeyNavigationFocusTo(r));
219
+ }
220
+ }
221
+ moveKeyNavigationFocusTo(e) {
222
+ this.isNavigating = !0;
223
+ try {
224
+ this.getEligibleItems().includes(e) && (this.applyRovingTabindex(e, "keyboard"), this.options.memory && (this.lastFocused = e), e.focus());
225
+ } finally {
226
+ this.isNavigating = !1;
227
+ }
228
+ }
229
+ getEffectivePageMagnitude() {
230
+ let e = this.options.pageStep;
231
+ if (e === void 0) return null;
232
+ let t = Math.trunc(Number(e));
233
+ return !Number.isFinite(t) || t === 0 ? null : Math.abs(t);
234
+ }
235
+ navigatePage(e, t, n, r) {
236
+ return this.options.direction === "grid" ? this.navigatePageGridRows(t, n, r) : this.navigatePageLinearItems(e, t, n);
237
+ }
238
+ navigatePageLinearItems(e, t, n) {
239
+ var r;
240
+ let i = e.indexOf(t);
241
+ if (i < 0 || e.length === 0) return null;
242
+ let a = i + n;
243
+ if (this.options.wrap) {
244
+ let t = e.length;
245
+ a = (a % t + t) % t;
246
+ } else a = Math.max(0, Math.min(e.length - 1, a));
247
+ return (r = e[a]) == null ? null : r;
248
+ }
249
+ navigatePageGridRows(e, t, n) {
250
+ var r;
251
+ if (n.length === 0) return null;
252
+ let i = this.findGridIndex(n, e);
253
+ if (!i) return null;
254
+ let { row: a, col: o } = i, s = a + t;
255
+ if (this.options.wrap) {
256
+ let e = n.length;
257
+ s = (s % e + e) % e;
258
+ } else s = Math.max(0, Math.min(n.length - 1, s));
259
+ let c = n[s];
260
+ return c != null && c.length ? (r = c[Math.min(o, c.length - 1)]) == null ? null : r : null;
261
+ }
262
+ navigateLinear(e, t, n, r, i) {
263
+ var a;
264
+ let o = e.indexOf(t);
265
+ if (o < 0) return null;
266
+ let s = 0;
267
+ if (r === "horizontal" ? n === "ArrowLeft" ? s = i ? 1 : -1 : n === "ArrowRight" && (s = i ? -1 : 1) : n === "ArrowUp" ? s = -1 : n === "ArrowDown" && (s = 1), s === 0) return null;
268
+ let c = o + s;
269
+ if (this.options.wrap) c = (c + e.length) % e.length;
270
+ else if (c < 0 || c >= e.length) return null;
271
+ return (a = e[c]) == null ? null : a;
272
+ }
273
+ navigateBothAxes(e, t, n, r) {
274
+ var i;
275
+ let a = e.indexOf(t);
276
+ if (a < 0) return null;
277
+ let o = 0;
278
+ if (n === "ArrowLeft" ? o = r ? 1 : -1 : n === "ArrowRight" ? o = r ? -1 : 1 : n === "ArrowUp" ? o = -1 : n === "ArrowDown" && (o = 1), o === 0) return null;
279
+ let s = a + o;
280
+ if (this.options.wrap) s = (s + e.length) % e.length;
281
+ else if (s < 0 || s >= e.length) return null;
282
+ return (i = e[s]) == null ? null : i;
283
+ }
284
+ navigateGrid(e, t, n, r) {
285
+ var i, a;
286
+ let o = this.findGridIndex(r, e);
287
+ if (!o) return null;
288
+ let { row: s, col: c } = o, l = (i = r[s]) == null ? [] : i, u = s, d = c;
289
+ switch (t) {
290
+ case "ArrowLeft":
291
+ d = n ? c + 1 : c - 1;
292
+ break;
293
+ case "ArrowRight":
294
+ d = n ? c - 1 : c + 1;
295
+ break;
296
+ case "ArrowUp":
297
+ u = s - 1;
298
+ break;
299
+ case "ArrowDown":
300
+ u = s + 1;
301
+ break;
302
+ default: return null;
303
+ }
304
+ if (t === "ArrowLeft" || t === "ArrowRight") {
305
+ if (d >= 0 && d < l.length) {
306
+ var f;
307
+ return (f = l[d]) == null ? null : f;
308
+ }
309
+ if (this.options.wrap && l.length > 0) {
310
+ var p;
311
+ return (p = l[(d + l.length) % l.length]) == null ? null : p;
312
+ }
313
+ return null;
314
+ }
315
+ if (u < 0 || u >= r.length) if (this.options.wrap && r.length > 0) u = (u + r.length) % r.length;
316
+ else return null;
317
+ let m = r[u];
318
+ return m != null && m.length ? (a = m[Math.min(c, m.length - 1)]) == null ? null : a : null;
319
+ }
320
+ buildRows(e) {
321
+ let n = [];
322
+ for (let r of e) {
323
+ let e = r.getBoundingClientRect().top, i = n.find((n) => Math.abs(n.top - e) <= t);
324
+ i || (i = {
325
+ top: e,
326
+ elements: []
327
+ }, n.push(i)), i.elements.push(r);
328
+ }
329
+ return n.sort((e, t) => e.top - t.top), n.map((e) => e.elements.sort((e, t) => e.getBoundingClientRect().left - t.getBoundingClientRect().left));
330
+ }
331
+ findGridIndex(e, t) {
332
+ for (let n = 0; n < e.length; n++) {
333
+ let r = e[n].indexOf(t);
334
+ if (r !== -1) return {
335
+ row: n,
336
+ col: r
337
+ };
338
+ }
339
+ return null;
340
+ }
341
+ };
342
+ //#endregion
343
+ export { r as FocusgroupNavigationController, n as focusgroupNavigationActiveChange };
344
+
345
+ //# sourceMappingURL=focusgroup-navigation-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusgroup-navigation-controller.js","names":[],"sources":["../../../../controllers/focusgroup-navigation-controller/src/focusgroup-navigation-controller.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\n// ─────────────────────────\n// TYPES\n// ─────────────────────────\n\n/**\n * Spatial mode for arrow-key movement. Aligns with logical axes (inline/block) and a\n * 2D layout mode derived from element geometry.\n *\n * - **horizontal**: Arrow keys on the inline axis move focus (respects `dir`).\n * - **vertical**: Arrow keys on the block axis move focus.\n * - **both**: **ArrowLeft** / **ArrowRight** move along `getItems()` order like **horizontal**\n * (respects `dir`); **ArrowUp** / **ArrowDown** move backward / forward in the same order.\n * - **grid**: Arrow keys move in rows and columns using bounding-rect layout; Ctrl+Home / Ctrl+End\n * jump to the first cell of the first row or the last cell of the last row.\n */\nexport type FocusgroupDirection = 'horizontal' | 'vertical' | 'both' | 'grid';\n\n/**\n * Options for {@link FocusgroupNavigationController}.\n */\nexport type FocusgroupNavigationOptions = {\n /**\n * Returns the current set of items that participate in roving tabindex and\n * directional navigation. Callers typically close over the host (for example\n * querying slotted or shadow DOM children).\n */\n getItems: () => HTMLElement[];\n\n /**\n * Determines which arrow keys move focus and how grid navigation is computed.\n * Use **`both`** when the same linear order should respond to horizontal and vertical arrow keys.\n */\n direction: FocusgroupDirection;\n\n /**\n * When true, arrow keys wrap from the last item to the first (and reverse).\n * Defaults to false.\n */\n wrap?: boolean;\n\n /**\n * When true, restoring focus into the composite (for example with Tab) targets\n * the item that was last focused, if it is still a member of the group.\n * Similar to the default memory behavior described for `focusgroup` in Open UI.\n * Defaults to true.\n */\n memory?: boolean;\n\n /**\n * When true, both natively `disabled` and `aria-disabled=\"true\"` items are\n * skipped for arrow navigation and are not chosen as the roving tab stop.\n * When false (default), disabled items remain in sequence — useful for\n * patterns such as menus where disabled items may still be focusable per\n * APG guidance.\n *\n * **Note:** Regardless of this flag, natively `disabled` elements are never\n * chosen as the roving tab stop (`tabindex=\"0\"`) because they cannot receive\n * browser focus; see {@link applyRovingTabindex}. A future revision may\n * decouple native `disabled` and `aria-disabled` into separate options if\n * component migrations surface the need.\n *\n * Defaults to false.\n */\n skipDisabled?: boolean;\n\n /**\n * Invoked after the active item changes and `tabindex` values are synchronized.\n * The argument is the new active element, or null when the group has no eligible items.\n */\n onActiveItemChange?: (active: HTMLElement | null) => void;\n\n /**\n * When set to a **non-zero** integer, **Page Up** / **Page Down** move focus by that many\n * positions in `getItems()` order for **`horizontal`**, **`vertical`**, and **`both`** modes\n * (respects **`wrap`** the same way as single-step arrows).\n * For **`grid`**, page keys move by that many **rows** (column index is clamped to each row’s\n * length). Omitted, `0`, `NaN`, and non-finite values disable page keys. The sign of the\n * number is ignored; only the magnitude is used.\n */\n pageStep?: number;\n};\n\n// ─────────────────────────\n// CONSTANTS\n// ─────────────────────────\n\n/**\n * Default boolean flags merged with the constructor `options` object.\n *\n * @internal\n */\nconst DEFAULT_OPTIONS = {\n wrap: false,\n memory: true,\n skipDisabled: false,\n} as const;\n\n/**\n * Tolerance in CSS pixels for grouping items into the same grid row when using\n * {@link FocusgroupDirection | `grid`} mode.\n *\n * @internal\n */\nconst GRID_ROW_TOLERANCE_PX = 6;\n\n/**\n * Name of the `CustomEvent` dispatched on the host when the roving tabindex active item changes.\n *\n * The event `bubbles` and is `composed`. Handlers read\n * {@link FocusgroupNavigationActiveChangeDetail} from `event.detail`.\n */\nexport const focusgroupNavigationActiveChange =\n 'swc-focusgroup-navigation-active-change';\n\n/**\n * Describes why the active item changed in a {@link focusgroupNavigationActiveChange} event.\n *\n * - **`keyboard`** — Arrow key, Home, or End navigation moved focus.\n * - **`focus`** — A managed item received DOM focus directly (pointer click or Tab-key entry).\n * - **`refresh`** — {@link FocusgroupNavigationController.refresh} re-parked the roving tab stop.\n * - **`programmatic`** — {@link FocusgroupNavigationController.setActiveItem} or\n * {@link FocusgroupNavigationController.focusFirstItemByTextPrefix} was called directly.\n */\nexport type FocusgroupActiveChangeSource =\n | 'keyboard'\n | 'focus'\n | 'refresh'\n | 'programmatic';\n\n/**\n * `detail` object for the {@link focusgroupNavigationActiveChange} event.\n */\nexport type FocusgroupNavigationActiveChangeDetail = {\n /**\n * Element that now has `tabindex=\"0\"` among managed items, or null when the group is empty.\n */\n activeElement: HTMLElement | null;\n\n /**\n * Why the active item changed. Hosts that implement selection-follows-focus (e.g. automatic\n * activation in a tab list) should react to `'keyboard'` and `'focus'` sources and ignore\n * `'refresh'` and `'programmatic'` to avoid spurious selection changes on mount or when\n * toggling `disabled`.\n */\n source: FocusgroupActiveChangeSource;\n};\n\n/**\n * **FocusgroupNavigation** — implements the roving `tabindex` pattern from the APG\n * keyboard guide and directional navigation similar to the proposed `focusgroup`\n * attribute (Open UI). The exported class name is `FocusgroupNavigationController`.\n *\n * The controller:\n * - Keeps exactly one item in the tab order (`tabindex=\"0\"`) per composite; sets\n * `tabindex=\"-1\"` on other items it manages.\n * - Handles Arrow keys, Home, and End for focus movement (and optionally wrap). **`both`**\n * direction accepts horizontal and vertical arrows on the same `getItems()` sequence.\n * In **`grid`** mode only, **Ctrl+Home** / **Ctrl+End** move to the first cell of the first\n * row or the last cell of the last row (by layout-derived rows).\n * - Optional **`pageStep`**: **Page Up** / **Page Down** move by that many items (linear modes)\n * or rows (**`grid`**).\n * - Optional **`skipDisabled`**: omit **`disabled`** and **`aria-disabled=\"true\"`** items from\n * roving tabindex and arrow navigation.\n * - Supports optional last-focused memory when re-entering via Tab.\n * - Exposes {@link FocusgroupNavigationController.setActiveItem} to choose the roving tab stop\n * without calling `focus()`, and {@link FocusgroupNavigationController.focusFirstItemByTextPrefix}\n * for typeahead-style roving `tabindex` (call {@link FocusgroupNavigationController.getActiveItem}\n * and `focus()` yourself when you want keyboard focus to move). Arrow-key handling calls\n * `setActiveItem` and `focus()` together.\n *\n * Dispatches a bubbling, composed `CustomEvent` named\n * {@link focusgroupNavigationActiveChange} when the active item changes.\n *\n * This is not a browser `focusgroup` implementation; it is a Lit reactive controller\n * for custom elements until native `focusgroup` is available.\n *\n * @example\n * ```typescript\n * class MyToolbar extends LitElement {\n * private readonly navigation = new FocusgroupNavigationController(this, {\n * direction: 'horizontal',\n * wrap: true,\n * getItems: () =>\n * Array.from(this.renderRoot.querySelectorAll<HTMLElement>('button')),\n * });\n *\n * protected override firstUpdated(): void {\n * super.firstUpdated();\n * this.navigation.refresh();\n * }\n * }\n * ```\n *\n * @see https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#keyboardnavigationinsidecomponents\n * @see https://open-ui.org/components/scoped-focusgroup.explainer/\n *\n * **Native `focusgroup` (future):** The comment block immediately below this class lists which\n * parts of this file are the most likely candidates for deprecation or deletion once browsers\n * ship built-in focus-group behavior that covers the same cases (especially roving tabindex and\n * arrow-key focus moves). Some options (for example rect-based **grid**, **pageStep**, or\n * **skipDisabled**) may remain useful longer if the platform surface stays narrower.\n */\n// ─────────────────────────────────────────────────────────────────────────────\n// Native `focusgroup` (future) — likely deprecation candidates\n//\n// If/when browsers implement `focusgroup` (or equivalent) with behavior comparable to this\n// controller for your targets, consider removing or shrinking the following areas first:\n//\n// 1. Roving tabindex — `applyRovingTabindex()`, the tabindex portions of `refresh()` and\n// `setActiveItem()`, and assigning `tabIndex` to ineligible raw items.\n//\n// 2. Host keyboard interception — `handleKeydown()`, `hostConnected` / `hostDisconnected`\n// `keydown` listeners, `resolveManagedKeydownTarget()` (shadow retargeting workaround), and\n// navigation helpers: `navigateLinear`, `navigateBothAxes`, `navigateGrid`, `navigatePage`,\n// `navigatePageLinearItems`, `navigatePageGridRows`, `getEffectivePageMagnitude`, plus\n// Home/End and Ctrl+Home/Ctrl+End branches inside `handleKeydown`.\n//\n// 3. JS “memory” for Tab re-entry — `lastFocused`, `handleFocusin` / `handleFocusout` memory\n// paths, and `refresh()`’s preference for `lastFocused` when native group memory replaces\n// this pattern.\n//\n// Often slower to retire (verify against shipped HTML/Open UI behavior): `buildRows` and\n// geometry-based **grid** navigation; **pageStep** (Page Up/Down magnitude); **skipDisabled** and\n// `isDisabledForSkip`; `isNodeWithinHostScope` / `getRawItems` if declarative scoping differs in\n// shadow DOM; `dispatchActiveChange`, `onActiveItemChange`, and the exported event name if\n// products still want a single composed integration hook; `focusFirstItemByTextPrefix` for\n// typeahead roving tabindex (callers focus `getActiveItem()` unless the platform adds an equivalent).\n// `isRtl()` may\n// duplicate or diverge from native axis mapping — revisit when testing RTL with native focusgroup.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class FocusgroupNavigationController implements ReactiveController {\n /**\n * Lit reactive host this controller is attached to.\n */\n private host: ReactiveElement;\n\n /**\n * Effective options (defaults merged with the latest `setOptions` / constructor values).\n */\n private options: FocusgroupNavigationOptions;\n\n /**\n * Capture-phase `keydown` listener reference for removal on disconnect.\n */\n private readonly boundKeydown = this.handleKeydown.bind(this);\n\n /**\n * Capture-phase `focusin` listener reference for removal on disconnect.\n */\n private readonly boundFocusin = this.handleFocusin.bind(this);\n\n /**\n * Capture-phase `focusout` listener reference for removal on disconnect.\n */\n private readonly boundFocusout = this.handleFocusout.bind(this);\n\n /**\n * Cached item for {@link FocusgroupNavigationOptions.memory} when the user moves focus\n * inside or out of the composite. Cleared when that node is no longer returned by\n * `getItems` or when the group becomes empty.\n */\n private lastFocused: HTMLElement | null = null;\n\n /**\n * Tracks the previously dispatched active item so that\n * {@link applyRovingTabindex} only fires the active-change event and\n * {@link FocusgroupNavigationOptions.onActiveItemChange} callback when the\n * active item actually changes.\n */\n private previousActive: HTMLElement | null = null;\n\n /**\n * Guard flag set during keyboard navigation so that the `focusin` triggered\n * by `item.focus()` does not redundantly call {@link applyRovingTabindex}.\n */\n private isNavigating = false;\n\n /**\n * Cached result of {@link getEligibleItems}, populated on first access within\n * a refresh cycle and cleared at the start of each entry point\n * ({@link refresh}, {@link handleFocusin}, {@link handleKeydown}).\n */\n private cachedEligibleItems: HTMLElement[] | null = null;\n\n /**\n * Cached result of {@link buildRows}, populated on first access within a\n * keydown cycle and cleared alongside {@link cachedEligibleItems}.\n */\n private cachedRows: HTMLElement[][] | null = null;\n\n // ─────────────────────────\n // PUBLIC API\n // ─────────────────────────\n\n /**\n * Registers this instance on `host` via `addController` and merges `options` with defaults.\n *\n * @param host - Reactive element that owns the composite (arrow keys and tab order apply within its subtree).\n * @param options - `getItems`, `direction`, and optional behavior flags.\n */\n constructor(host: ReactiveElement, options: FocusgroupNavigationOptions) {\n this.host = host;\n this.options = { ...DEFAULT_OPTIONS, ...options };\n host.addController(this);\n }\n\n /**\n * Merges `partial` into the current options and reapplies roving `tabindex` to the item set.\n *\n * @param partial - Fields to override; omitted keys keep their previous values.\n */\n public setOptions(partial: Partial<FocusgroupNavigationOptions>): void {\n this.options = { ...this.options, ...partial };\n this.refresh();\n }\n\n /**\n * Returns the eligible managed item that currently participates in the sequential focus order\n * (`tabindex=\"0\"`), or null if no eligible item has tab index zero.\n *\n * @returns The active roving item, or null.\n */\n public getActiveItem(): HTMLElement | null {\n for (const el of this.getEligibleItems()) {\n if (el.tabIndex === 0) {\n return el;\n }\n }\n return null;\n }\n\n /**\n * Re-queries `getItems()`, recomputes eligibility, and syncs roving `tabindex`.\n *\n * Call after the item list or item eligibility changes (for example after Lit\n * `updated()` or slot changes). When {@link FocusgroupNavigationOptions.memory} is true,\n * prefers the stored last-focused item if it is still eligible; otherwise keeps the\n * current active item or falls back to the first eligible item.\n */\n public refresh(): void {\n this.cachedEligibleItems = null;\n this.cachedRows = null;\n const items = this.getEligibleItems();\n if (items.length === 0) {\n for (const el of this.getRawItems()) {\n el.tabIndex = -1;\n }\n this.lastFocused = null;\n if (this.previousActive !== null) {\n this.previousActive = null;\n this.dispatchActiveChange(null, 'refresh');\n this.options.onActiveItemChange?.(null);\n }\n return;\n }\n\n const preferred =\n (this.options.memory &&\n this.lastFocused &&\n items.includes(this.lastFocused)\n ? this.lastFocused\n : null) ??\n this.getActiveItem() ??\n items[0];\n\n this.applyRovingTabindex(preferred, 'refresh');\n }\n\n /**\n * Sets roving `tabindex` so `item` is the active tab stop (`tabindex=\"0\"`) and others in the\n * group are `-1`. Does **not** call `focus()`. When {@link FocusgroupNavigationOptions.memory}\n * is true, updates the stored last-focused item so Tab re-entry can target this item.\n *\n * @param item - Item to mark active; must be returned by `getItems` and pass eligibility checks.\n * @returns False if `item` is not in the current eligible item list.\n */\n public setActiveItem(item: HTMLElement): boolean {\n const items = this.getEligibleItems();\n if (!items.includes(item)) {\n return false;\n }\n this.applyRovingTabindex(item, 'programmatic');\n if (this.options.memory) {\n this.lastFocused = item;\n }\n return true;\n }\n\n /**\n * Updates roving `tabindex` so the first **eligible** item (same set as arrow navigation)\n * whose typeahead label starts with `prefix` becomes the active tab stop (`tabindex=\"0\"`).\n * Matching is **case-insensitive**. The label is the first non-empty of: trimmed\n * **`aria-label`**, trimmed text from **`aria-labelledby`** references (in order, space-joined),\n * or trimmed **`textContent`**. Search order matches arrow-key traversal.\n *\n * Does **not** call `focus()`. After this returns `true`, call `focus()` on\n * {@link FocusgroupNavigationController.getActiveItem} (for example `getActiveItem()?.focus()`),\n * often from a **microtask** when the caller runs from a pointer handler so focus is not\n * overwritten by the clicked control.\n *\n * Typical use: menu typeahead; wire `keydown` or `input` at the host and debounce as needed.\n *\n * @param prefix - String to match as a leading substring after `trim`; whitespace-only yields\n * no match and returns `false`.\n * @returns True if a matching item was found and roving tabindex was applied.\n */\n public focusFirstItemByTextPrefix(prefix: string): boolean {\n const trimmed = prefix.trim();\n if (trimmed === '') {\n return false;\n }\n const needle = trimmed.toLowerCase();\n const items = this.getEligibleItems();\n const match = items.find((el) => {\n const label = this.getItemTypeaheadLabel(el).toLowerCase();\n return label.startsWith(needle);\n });\n if (!match) {\n return false;\n }\n this.applyRovingTabindex(match, 'programmatic');\n return true;\n }\n\n /**\n * Lit `ReactiveController` hook: registers capture-phase listeners on `host` and runs\n * an initial {@link refresh}.\n */\n public hostConnected(): void {\n this.previousActive = null;\n this.cachedEligibleItems = null;\n this.cachedRows = null;\n this.host.addEventListener('keydown', this.boundKeydown, true);\n this.host.addEventListener('focusin', this.boundFocusin, true);\n this.host.addEventListener('focusout', this.boundFocusout, true);\n this.refresh();\n }\n\n /**\n * Lit `ReactiveController` hook: removes listeners registered in {@link hostConnected}.\n */\n public hostDisconnected(): void {\n this.host.removeEventListener('keydown', this.boundKeydown, true);\n this.host.removeEventListener('focusin', this.boundFocusin, true);\n this.host.removeEventListener('focusout', this.boundFocusout, true);\n }\n\n // ─────────────────────────\n // IMPLEMENTATION\n // ─────────────────────────\n //\n // Which parts may become redundant under native `focusgroup` is summarized in the\n // “Native `focusgroup` (future)” comment block directly above the class declaration.\n\n /**\n * Resolves writing direction from the computed style of the host element.\n *\n * Uses `getComputedStyle` rather than walking `dir` attributes so that\n * CSS-inherited direction (the gen2 default) is correctly detected.\n *\n * @returns True when horizontal arrow directions should follow RTL semantics.\n */\n private isRtl(): boolean {\n return getComputedStyle(this.host).direction === 'rtl';\n }\n\n /**\n * Whether `node` is the host or reachable from it by walking `parentNode` and\n * `ShadowRoot.host` (so shadow descendants count, including nested shadow roots).\n *\n * `Element.contains()` is not used because it returns false for nodes inside the\n * host's shadow tree, which would drop every item for typical Lit components.\n *\n * @param node - Node to test (may be null).\n * @returns True if `node` is in the host's shadow-inclusive subtree.\n */\n private isNodeWithinHostScope(node: Node | null): boolean {\n if (!node) {\n return false;\n }\n const host = this.host;\n let current: Node | null = node;\n while (current) {\n if (current === host) {\n return true;\n }\n const parent: Node | null = current.parentNode;\n if (parent) {\n current = parent;\n } else if (current instanceof ShadowRoot) {\n current = current.host;\n } else {\n return false;\n }\n }\n return false;\n }\n\n /**\n * Items returned by `getItems` that lie within `host` (shadow-inclusive tree).\n *\n * @returns Candidates before eligibility filtering.\n */\n private getRawItems(): HTMLElement[] {\n return this.options\n .getItems()\n .filter((el) => this.isNodeWithinHostScope(el));\n }\n\n /**\n * {@link getRawItems} filtered by {@link isNavigableItem}.\n *\n * @returns Items that participate in roving tabindex and arrow navigation.\n */\n private getEligibleItems(): HTMLElement[] {\n if (this.cachedEligibleItems) {\n return this.cachedEligibleItems;\n }\n this.cachedEligibleItems = this.getRawItems().filter((el) =>\n this.isNavigableItem(el)\n );\n return this.cachedEligibleItems;\n }\n\n /**\n * {@link buildRows} with per-cycle caching, cleared alongside\n * {@link cachedEligibleItems}.\n *\n * @param items - Eligible items to lay out as a grid.\n * @returns Cached row-major array of rows.\n */\n private getRows(items: HTMLElement[]): HTMLElement[][] {\n if (this.cachedRows) {\n return this.cachedRows;\n }\n this.cachedRows = this.buildRows(items);\n return this.cachedRows;\n }\n\n /**\n * Whether `el` may participate in the focus group (connected, visible, not inert,\n * and not skipped when {@link FocusgroupNavigationOptions.skipDisabled} is true).\n *\n * @param el - Candidate from `getItems`.\n * @returns True if the element counts as navigable for this controller.\n */\n private isNavigableItem(el: HTMLElement): boolean {\n if (!el.isConnected) {\n return false;\n }\n if (el.hasAttribute('inert') || el.closest('[inert]')) {\n return false;\n }\n const style = getComputedStyle(el);\n if (style.visibility === 'hidden' || style.display === 'none') {\n return false;\n }\n if (this.options.skipDisabled && this.isDisabledForSkip(el)) {\n return false;\n }\n return true;\n }\n\n /**\n * Whether `el` should be treated as disabled for {@link FocusgroupNavigationOptions.skipDisabled}.\n *\n * @param el - Element to test.\n * @returns True if the native `disabled` property is true or `aria-disabled` is `\"true\"`.\n */\n private isDisabledForSkip(el: HTMLElement): boolean {\n if ('disabled' in el && (el as HTMLButtonElement).disabled) {\n return true;\n }\n return el.getAttribute('aria-disabled') === 'true';\n }\n\n /**\n * String used for {@link focusFirstItemByTextPrefix}: prefers **`aria-label`**, then text from\n * **`aria-labelledby`** (IDs resolved in the shadow root or document), else **`textContent`**.\n * All branches are trimmed; empty strings fall through to the next source.\n */\n private getItemTypeaheadLabel(el: HTMLElement): string {\n const fromAria = el.getAttribute('aria-label')?.trim();\n if (fromAria) {\n return fromAria;\n }\n const labelledBy = el.getAttribute('aria-labelledby')?.trim();\n if (labelledBy) {\n const root = el.getRootNode();\n const chunks: string[] = [];\n for (const id of labelledBy.split(/\\s+/)) {\n if (!id) {\n continue;\n }\n const ref =\n root instanceof ShadowRoot\n ? (root.getElementById(id) ?? el.ownerDocument.getElementById(id))\n : el.ownerDocument.getElementById(id);\n const t = ref?.textContent?.trim();\n if (t) {\n chunks.push(t);\n }\n }\n const joined = chunks.join(' ').trim();\n if (joined) {\n return joined;\n }\n }\n return el.textContent?.trim() ?? '';\n }\n\n /**\n * Whether `el` is natively disabled and therefore unable to receive focus\n * regardless of its `tabindex` value.\n */\n private isNativelyDisabled(el: HTMLElement): boolean {\n return 'disabled' in el && (el as HTMLButtonElement).disabled === true;\n }\n\n /**\n * Sets `tabindex=\"-1\"` on ineligible raw items, then assigns `tabindex=\"0\"` to\n * `active` (or the first eligible item if `active` is not eligible) and `-1` to the rest.\n *\n * When `skipDisabled` is false, natively disabled items remain in the eligible list\n * for arrow navigation but are never chosen as the roving tab stop because they\n * cannot receive focus. The tab stop falls through to the nearest non-disabled item.\n *\n * Dispatches the active-change event and {@link FocusgroupNavigationOptions.onActiveItemChange}.\n *\n * @param active - Preferred item to mark as the single tab stop when eligible.\n * @param source - Why the active item is changing; included in the dispatched event detail.\n */\n private applyRovingTabindex(\n active: HTMLElement,\n source: FocusgroupActiveChangeSource\n ): void {\n const items = this.getEligibleItems();\n const eligibleSet = new Set(items);\n for (const el of this.getRawItems()) {\n if (!eligibleSet.has(el)) {\n el.tabIndex = -1;\n }\n }\n if (items.length === 0) {\n return;\n }\n\n let safeActive = eligibleSet.has(active) ? active : items[0];\n\n // Natively disabled elements cannot receive focus even with tabindex=\"0\".\n // Fall through to the first non-disabled eligible item so the group\n // remains reachable via Tab.\n //\n // The active-change event is dispatched with the originally requested item\n // (before the fallback), not the fallback tab-stop. This lets consumers\n // such as a tab list in automatic-activation mode inspect the item and skip\n // the selection change when it is disabled — without being misled by the\n // roving tab stop landing on a different element.\n const reportedActive = safeActive;\n if (this.isNativelyDisabled(safeActive)) {\n safeActive =\n items.find((el) => !this.isNativelyDisabled(el)) ?? safeActive;\n }\n\n for (const el of items) {\n if (el === safeActive) {\n el.tabIndex = 0;\n } else {\n el.tabIndex = -1;\n }\n }\n if (reportedActive !== this.previousActive) {\n this.previousActive = reportedActive;\n this.dispatchActiveChange(reportedActive, source);\n this.options.onActiveItemChange?.(safeActive);\n }\n }\n\n /**\n * Dispatches {@link focusgroupNavigationActiveChange} on the reactive host with the given detail.\n *\n * @param activeElement - New active item, or null when clearing selection.\n * @param source - Why the active item changed.\n */\n private dispatchActiveChange(\n activeElement: HTMLElement | null,\n source: FocusgroupActiveChangeSource\n ): void {\n this.host.dispatchEvent(\n new CustomEvent<FocusgroupNavigationActiveChangeDetail>(\n focusgroupNavigationActiveChange,\n {\n bubbles: true,\n composed: true,\n detail: { activeElement, source },\n }\n )\n );\n }\n\n /**\n * Resolves the managed item that actually received focus inside the shadow tree.\n *\n * Same retargeting problem as {@link resolveManagedKeydownTarget}: listeners on\n * the shadow host see `event.target` retargeted to the host when focus lands on a\n * descendant inside the shadow root. Walk `composedPath()` and fall back to\n * `shadowRoot.activeElement` to find the real focused managed item.\n *\n * @param event - Focus event dispatched while focus moves into the composite.\n * @param items - Current eligible items from {@link getEligibleItems}.\n * @returns The managed element that received focus, or null.\n */\n private resolveManagedFocusTarget(\n event: FocusEvent,\n items: HTMLElement[]\n ): HTMLElement | null {\n if (items.length === 0) {\n return null;\n }\n const set = new Set(items);\n for (const node of event.composedPath()) {\n if (!(node instanceof HTMLElement)) {\n continue;\n }\n if (set.has(node)) {\n return node;\n }\n if (node === this.host) {\n break;\n }\n }\n const root = this.host.shadowRoot;\n const active = root?.activeElement;\n if (active instanceof HTMLElement && set.has(active)) {\n return active;\n }\n return null;\n }\n\n /**\n * Capture-phase `focusin` handler: syncs roving `tabindex` when focus moves to a managed item\n * (for example via pointer), and updates memory when enabled.\n *\n * @param event - Focus event whose target may be a group item.\n */\n private handleFocusin(event: FocusEvent): void {\n if (this.isNavigating) {\n return;\n }\n this.cachedEligibleItems = null;\n this.cachedRows = null;\n const items = this.getEligibleItems();\n const target = this.resolveManagedFocusTarget(event, items);\n if (!target) {\n return;\n }\n this.applyRovingTabindex(target, 'focus');\n if (this.options.memory) {\n this.lastFocused = target;\n }\n }\n\n /**\n * Capture-phase `focusout` handler: when focus leaves the host subtree, stores the\n * previous target for {@link FocusgroupNavigationOptions.memory}.\n *\n * @param event - Focus event; `relatedTarget` stays inside the host when moving between items.\n */\n private handleFocusout(event: FocusEvent): void {\n const next = event.relatedTarget;\n if (next instanceof Node && this.isNodeWithinHostScope(next)) {\n return;\n }\n const target = event.target;\n if (\n this.options.memory &&\n target instanceof HTMLElement &&\n this.getRawItems().includes(target)\n ) {\n this.lastFocused = target;\n }\n // When memory is off, reset the roving tab stop to the first eligible\n // item so Tab re-entry always starts from the beginning.\n if (!this.options.memory) {\n this.cachedEligibleItems = null;\n this.cachedRows = null;\n const items = this.getEligibleItems();\n if (items.length > 0) {\n this.applyRovingTabindex(items[0], 'focus');\n }\n }\n }\n\n /**\n * Resolves which managed item should receive arrow, Home, End, or grid Ctrl+Home / Ctrl+End\n * handling for this key event.\n *\n * Listeners on the shadow **host** often see a **retargeted** {@link KeyboardEvent.target}\n * (the host) while focus is on a descendant inside the shadow tree, so matching\n * `event.target` against `getItems()` fails. {@link Event.composedPath} still includes the\n * focused node; we also fall back to {@link ShadowRoot.activeElement} when needed.\n *\n * @param event - Keyboard event dispatched while focus is in this composite.\n * @param items - Current eligible items from {@link getEligibleItems}.\n * @returns The managed element to treat as keydown target, or null.\n */\n private resolveManagedKeydownTarget(\n event: KeyboardEvent,\n items: HTMLElement[]\n ): HTMLElement | null {\n if (items.length === 0) {\n return null;\n }\n const set = new Set(items);\n for (const node of event.composedPath()) {\n if (!(node instanceof HTMLElement)) {\n continue;\n }\n if (set.has(node)) {\n return node;\n }\n if (node === this.host) {\n break;\n }\n }\n const root = this.host.shadowRoot;\n const active = root?.activeElement;\n if (active instanceof HTMLElement && set.has(active)) {\n return active;\n }\n return null;\n }\n\n /**\n * Capture-phase `keydown` handler: arrow keys and Home/End move focus among eligible items\n * when the event target is managed; calls `preventDefault` when handling navigation.\n *\n * When {@link FocusgroupDirection | `direction`} is **`both`**, **ArrowLeft** / **ArrowRight**\n * and **ArrowUp** / **ArrowDown** all participate (see {@link navigateBothAxes}).\n *\n * When {@link FocusgroupDirection | `direction`} is **`grid`**, **Ctrl+Home** focuses the\n * first cell in the first row and **Ctrl+End** focuses the last cell in the last row (from\n * {@link buildRows}); other modifier combinations are ignored except plain Home/End.\n *\n * When {@link FocusgroupNavigationOptions.pageStep} is a non-zero finite number, **Page Up**\n * and **Page Down** are handled before arrow keys (see {@link navigatePage}).\n *\n * @param event - Keyboard event from the focused element inside the host.\n */\n private handleKeydown(event: KeyboardEvent): void {\n if (event.defaultPrevented || event.altKey) {\n return;\n }\n\n this.cachedEligibleItems = null;\n this.cachedRows = null;\n const items = this.getEligibleItems();\n const target = this.resolveManagedKeydownTarget(event, items);\n if (!target) {\n return;\n }\n\n const isGrid = this.options.direction === 'grid';\n const rows = isGrid ? this.getRows(items) : null;\n\n if (\n isGrid &&\n event.ctrlKey &&\n !event.metaKey &&\n (event.key === 'Home' || event.key === 'End')\n ) {\n if (rows!.length > 0) {\n const firstRow = rows![0];\n const lastRow = rows![rows!.length - 1];\n const boundary =\n event.key === 'Home'\n ? (firstRow?.[0] ?? null)\n : (lastRow?.[lastRow.length - 1] ?? null);\n if (boundary && boundary !== target) {\n event.preventDefault();\n this.moveKeyNavigationFocusTo(boundary);\n }\n }\n return;\n }\n\n if (event.ctrlKey || event.metaKey) {\n return;\n }\n\n const pageMagnitude = this.getEffectivePageMagnitude();\n if (\n pageMagnitude !== null &&\n (event.key === 'PageUp' || event.key === 'PageDown')\n ) {\n const pageNext = this.navigatePage(\n items,\n target,\n event.key === 'PageDown' ? pageMagnitude : -pageMagnitude,\n rows\n );\n if (pageNext && pageNext !== target) {\n event.preventDefault();\n this.moveKeyNavigationFocusTo(pageNext);\n }\n return;\n }\n\n const rtl = this.isRtl();\n let next: HTMLElement | null = null;\n\n switch (this.options.direction) {\n case 'horizontal':\n next = this.navigateLinear(items, target, event.key, 'horizontal', rtl);\n break;\n case 'vertical':\n next = this.navigateLinear(items, target, event.key, 'vertical', rtl);\n break;\n case 'both':\n next = this.navigateBothAxes(items, target, event.key, rtl);\n break;\n case 'grid':\n next = this.navigateGrid(target, event.key, rtl, rows!);\n break;\n default:\n break;\n }\n\n if (next && next !== target) {\n event.preventDefault();\n this.moveKeyNavigationFocusTo(next);\n return;\n }\n\n if (event.key === 'Home' || event.key === 'End') {\n if (isGrid) {\n // APG grid pattern: Home/End scope to the current row.\n // Ctrl+Home/End (entire grid) is handled above.\n const pos = this.findGridIndex(rows!, target);\n if (!pos) {\n return;\n }\n const currentRow = rows![pos.row];\n if (!currentRow?.length) {\n return;\n }\n const boundary =\n event.key === 'Home'\n ? currentRow[0]\n : currentRow[currentRow.length - 1];\n if (boundary && boundary !== target) {\n event.preventDefault();\n this.moveKeyNavigationFocusTo(boundary);\n }\n } else {\n if (items.length === 0) {\n return;\n }\n const boundary =\n event.key === 'Home' ? items[0] : items[items.length - 1];\n if (boundary && boundary !== target) {\n event.preventDefault();\n this.moveKeyNavigationFocusTo(boundary);\n }\n }\n }\n }\n\n /**\n * Applies roving tabindex to `item` and moves DOM focus; used for keyboard navigation only.\n */\n private moveKeyNavigationFocusTo(item: HTMLElement): void {\n this.isNavigating = true;\n try {\n const items = this.getEligibleItems();\n if (items.includes(item)) {\n this.applyRovingTabindex(item, 'keyboard');\n if (this.options.memory) {\n this.lastFocused = item;\n }\n item.focus();\n }\n } finally {\n this.isNavigating = false;\n }\n }\n\n /**\n * Positive step count for {@link FocusgroupNavigationOptions.pageStep}, or null when page keys\n * are disabled.\n */\n private getEffectivePageMagnitude(): number | null {\n const raw = this.options.pageStep;\n if (raw === undefined) {\n return null;\n }\n const n = Math.trunc(Number(raw));\n if (!Number.isFinite(n) || n === 0) {\n return null;\n }\n return Math.abs(n);\n }\n\n /**\n * Target for **Page Up** / **Page Down** when {@link getEffectivePageMagnitude} is set.\n *\n * @param items - Eligible items.\n * @param current - Focused item.\n * @param signedDelta - `+magnitude` for Page Down or `-magnitude` for Page Up (items for\n * linear modes, rows for `grid`).\n */\n private navigatePage(\n items: HTMLElement[],\n current: HTMLElement,\n signedDelta: number,\n rows: HTMLElement[][] | null\n ): HTMLElement | null {\n if (this.options.direction === 'grid') {\n return this.navigatePageGridRows(current, signedDelta, rows!);\n }\n return this.navigatePageLinearItems(items, current, signedDelta);\n }\n\n /**\n * Page Up/Down along `getItems()` order (used for `horizontal`, `vertical`, and `both`).\n */\n private navigatePageLinearItems(\n items: HTMLElement[],\n current: HTMLElement,\n deltaIdx: number\n ): HTMLElement | null {\n const idx = items.indexOf(current);\n if (idx < 0 || items.length === 0) {\n return null;\n }\n let nextIdx = idx + deltaIdx;\n if (this.options.wrap) {\n const len = items.length;\n nextIdx = ((nextIdx % len) + len) % len;\n } else {\n nextIdx = Math.max(0, Math.min(items.length - 1, nextIdx));\n }\n return items[nextIdx] ?? null;\n }\n\n /**\n * Page Up/Down by whole rows in `grid` mode (column clamped per {@link navigateGrid}).\n */\n private navigatePageGridRows(\n current: HTMLElement,\n rowDelta: number,\n grid: HTMLElement[][]\n ): HTMLElement | null {\n if (grid.length === 0) {\n return null;\n }\n const pos = this.findGridIndex(grid, current);\n if (!pos) {\n return null;\n }\n const { row, col } = pos;\n let nextRow = row + rowDelta;\n if (this.options.wrap) {\n const n = grid.length;\n nextRow = ((nextRow % n) + n) % n;\n } else {\n nextRow = Math.max(0, Math.min(grid.length - 1, nextRow));\n }\n const targetRow = grid[nextRow];\n if (!targetRow?.length) {\n return null;\n }\n const clampedCol = Math.min(col, targetRow.length - 1);\n return targetRow[clampedCol] ?? null;\n }\n\n /**\n * Computes the next focus target for linear {@link FocusgroupDirection} modes.\n *\n * @param items - Eligible items in traversal order.\n * @param current - Currently focused item.\n * @param key - `KeyboardEvent.key` value.\n * @param mode - `horizontal` (inline axis) or `vertical` (block axis).\n * @param rtl - When true, horizontal Left/Right swap forward/backward.\n * @returns Next item, or null if the key is not a navigation key or movement is blocked.\n */\n private navigateLinear(\n items: HTMLElement[],\n current: HTMLElement,\n key: string,\n mode: 'horizontal' | 'vertical',\n rtl: boolean\n ): HTMLElement | null {\n const idx = items.indexOf(current);\n if (idx < 0) {\n return null;\n }\n\n let delta = 0;\n if (mode === 'horizontal') {\n if (key === 'ArrowLeft') {\n delta = rtl ? 1 : -1;\n } else if (key === 'ArrowRight') {\n delta = rtl ? -1 : 1;\n }\n } else {\n if (key === 'ArrowUp') {\n delta = -1;\n } else if (key === 'ArrowDown') {\n delta = 1;\n }\n }\n\n if (delta === 0) {\n return null;\n }\n\n let nextIdx = idx + delta;\n if (this.options.wrap) {\n nextIdx = (nextIdx + items.length) % items.length;\n } else if (nextIdx < 0 || nextIdx >= items.length) {\n return null;\n }\n return items[nextIdx] ?? null;\n }\n\n /**\n * Computes the next focus target when {@link FocusgroupDirection | `direction`} is **`both`**:\n * inline arrows use the same deltas as {@link navigateLinear} `horizontal` mode; **ArrowUp** /\n * **ArrowDown** step backward / forward in `getItems()` order (not flipped by `dir`).\n *\n * @param items - Eligible items in traversal order.\n * @param current - Currently focused item.\n * @param key - `KeyboardEvent.key` value.\n * @param rtl - When true, horizontal Left/Right swap forward/backward.\n * @returns Next item, or null if the key is not handled or movement is blocked.\n */\n private navigateBothAxes(\n items: HTMLElement[],\n current: HTMLElement,\n key: string,\n rtl: boolean\n ): HTMLElement | null {\n const idx = items.indexOf(current);\n if (idx < 0) {\n return null;\n }\n\n let delta = 0;\n if (key === 'ArrowLeft') {\n delta = rtl ? 1 : -1;\n } else if (key === 'ArrowRight') {\n delta = rtl ? -1 : 1;\n } else if (key === 'ArrowUp') {\n delta = -1;\n } else if (key === 'ArrowDown') {\n delta = 1;\n }\n\n if (delta === 0) {\n return null;\n }\n\n let nextIdx = idx + delta;\n if (this.options.wrap) {\n nextIdx = (nextIdx + items.length) % items.length;\n } else if (nextIdx < 0 || nextIdx >= items.length) {\n return null;\n }\n return items[nextIdx] ?? null;\n }\n\n /**\n * Computes the next focus target for `grid` {@link FocusgroupDirection} mode using\n * row clustering and column indices.\n *\n * @param current - Currently focused item.\n * @param key - `KeyboardEvent.key` value.\n * @param rtl - When true, horizontal Left/Right swap column direction within a row.\n * @param grid - Pre-built row grid from {@link buildRows}.\n * @returns Next cell item, or null if the key is not handled or movement is blocked.\n */\n private navigateGrid(\n current: HTMLElement,\n key: string,\n rtl: boolean,\n grid: HTMLElement[][]\n ): HTMLElement | null {\n const pos = this.findGridIndex(grid, current);\n if (!pos) {\n return null;\n }\n const { row, col } = pos;\n const rowItems = grid[row] ?? [];\n let nextRow = row;\n let nextCol = col;\n\n switch (key) {\n case 'ArrowLeft':\n nextCol = rtl ? col + 1 : col - 1;\n break;\n case 'ArrowRight':\n nextCol = rtl ? col - 1 : col + 1;\n break;\n case 'ArrowUp':\n nextRow = row - 1;\n break;\n case 'ArrowDown':\n nextRow = row + 1;\n break;\n default:\n return null;\n }\n\n if (key === 'ArrowLeft' || key === 'ArrowRight') {\n if (nextCol >= 0 && nextCol < rowItems.length) {\n return rowItems[nextCol] ?? null;\n }\n if (this.options.wrap && rowItems.length > 0) {\n const wrappedCol = (nextCol + rowItems.length) % rowItems.length;\n return rowItems[wrappedCol] ?? null;\n }\n return null;\n }\n\n if (nextRow < 0 || nextRow >= grid.length) {\n if (this.options.wrap && grid.length > 0) {\n nextRow = (nextRow + grid.length) % grid.length;\n } else {\n return null;\n }\n }\n\n const targetRow = grid[nextRow];\n if (!targetRow?.length) {\n return null;\n }\n const clampedCol = Math.min(col, targetRow.length - 1);\n return targetRow[clampedCol] ?? null;\n }\n\n /**\n * Groups `items` into rows by similar `getBoundingClientRect().top`, then sorts each row by `left`.\n *\n * @param items - Eligible elements to lay out as a grid.\n * @returns Row-major array of rows; each row is left-to-right.\n */\n private buildRows(items: HTMLElement[]): HTMLElement[][] {\n type RowAcc = { top: number; elements: HTMLElement[] };\n const rows: RowAcc[] = [];\n\n for (const el of items) {\n const top = el.getBoundingClientRect().top;\n let row = rows.find(\n (r) => Math.abs(r.top - top) <= GRID_ROW_TOLERANCE_PX\n );\n if (!row) {\n row = { top, elements: [] };\n rows.push(row);\n }\n row.elements.push(el);\n }\n\n rows.sort((a, b) => a.top - b.top);\n return rows.map((r) =>\n r.elements.sort(\n (a, b) =>\n a.getBoundingClientRect().left - b.getBoundingClientRect().left\n )\n );\n }\n\n /**\n * Locates `el` in a row-major grid built by {@link buildRows}.\n *\n * @param grid - Rows of elements.\n * @param el - Element to find.\n * @returns Row and column indices, or null if absent.\n */\n private findGridIndex(\n grid: HTMLElement[][],\n el: HTMLElement\n ): { row: number; col: number } | null {\n for (let r = 0; r < grid.length; r++) {\n const c = grid[r].indexOf(el);\n if (c !== -1) {\n return { row: r, col: c };\n }\n }\n return null;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright 2026 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ export { HoverController, type HoverControllerHost, type HoverControllerOptions, } from './src/hover-controller.js';
@@ -0,0 +1,2 @@
1
+ import { HoverController as e } from "./src/hover-controller.js";
2
+ export { e as HoverController };
@@ -0,0 +1,118 @@
1
+ import { ReactiveController, ReactiveElement } from 'lit';
2
+ /** Minimum interface required from any element that hosts a {@link HoverController}. */
3
+ export interface HoverControllerHost extends ReactiveElement {
4
+ /** Warm-up duration in milliseconds before the popover opens on hover. `0` opens immediately. */
5
+ readonly delay: number;
6
+ /**
7
+ * Cooldown duration in milliseconds after the pointer leaves the trigger or
8
+ * popover before the popover closes. Independent of `delay` so that the
9
+ * WCAG 1.4.13 pointer bridge always has enough time to cancel the close,
10
+ * regardless of how quickly the popover opened.
11
+ *
12
+ * Defaults to `300` when omitted.
13
+ */
14
+ readonly closeDelay?: number;
15
+ /** When `true`, the controller skips all event wiring. */
16
+ readonly manual: boolean;
17
+ /** When `true`, the controller skips all event wiring. */
18
+ readonly disabled: boolean;
19
+ /**
20
+ * Asks the host to open. The host owns its visibility state and is the single
21
+ * source of truth; the controller never drives the Popover API directly. Must
22
+ * be idempotent — calling it while already open is a no-op.
23
+ */
24
+ requestOpen(): void;
25
+ /**
26
+ * Asks the host to close. Must be idempotent — calling it while already closed
27
+ * is a no-op.
28
+ */
29
+ requestClose(): void;
30
+ }
31
+ /** Configuration options for {@link HoverController}. */
32
+ export interface HoverControllerOptions {
33
+ /**
34
+ * Per-component-type key used to namespace shared warm state on `document`.
35
+ * Use the element tag name (e.g. `'swc-tooltip'`). Must be static; must not
36
+ * vary per instance.
37
+ */
38
+ warmStateKey: string;
39
+ }
40
+ /**
41
+ * A Lit {@link ReactiveController} that manages hover and keyboard-focus event
42
+ * wiring for components that use the native Popover API.
43
+ *
44
+ * See the Storybook stories for full usage documentation and interactive demos.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * class SwcTooltip extends LitElement implements HoverControllerHost {
49
+ * @property({ type: Number }) delay = 1500;
50
+ * @property({ type: Boolean }) manual = false;
51
+ * @property({ type: Boolean }) disabled = false;
52
+ *
53
+ * private hoverController = new HoverController(this, { warmStateKey: 'swc-tooltip' });
54
+ *
55
+ * protected override updated(changes: PropertyValues): void {
56
+ * super.updated(changes);
57
+ * if (changes.has('triggerElement')) {
58
+ * this.hoverController.setTarget(this.triggerElement ?? null);
59
+ * }
60
+ * }
61
+ * }
62
+ * ```
63
+ */
64
+ export declare class HoverController implements ReactiveController {
65
+ private readonly host;
66
+ private readonly warmStateKey;
67
+ private target;
68
+ private warmupTimer;
69
+ private isBridgeWired;
70
+ /**
71
+ * Tracks whether the `disabled`/`manual` guard is currently active. Used by
72
+ * `hostUpdated()` to skip the rewire on every Lit update when the guard state
73
+ * hasn't changed.
74
+ */
75
+ private isGuardActive;
76
+ /** True while the trigger has keyboard focus; pointer-driven timers are suppressed. */
77
+ private hasFocusOpen;
78
+ /**
79
+ * Set by `pointerdown` on the trigger and cleared asynchronously after `focusin`
80
+ * fires. The async reset ensures the flag is still `true` when `focusin` arrives
81
+ * synchronously later in the same click event sequence.
82
+ */
83
+ private hadPointerdown;
84
+ private readonly boundPointerDownTrigger;
85
+ private readonly boundPointerEnterTrigger;
86
+ private readonly boundPointerLeaveTrigger;
87
+ private readonly boundFocusin;
88
+ private readonly boundFocusout;
89
+ private readonly boundPointerEnterHost;
90
+ private readonly boundPointerLeaveHost;
91
+ constructor(host: HoverControllerHost, options: HoverControllerOptions);
92
+ /**
93
+ * Sets the element that receives pointer and focus listeners. Call this whenever
94
+ * the resolved trigger changes (e.g. in `updated()` after a `for` attribute change).
95
+ * Passing `null` detaches all listeners from the previous target.
96
+ */
97
+ setTarget(trigger: HTMLElement | null): void;
98
+ hostConnected(): void;
99
+ hostDisconnected(): void;
100
+ /** Re-evaluates `disabled` and `manual` guards whenever the host updates. */
101
+ hostUpdated(): void;
102
+ private wireTarget;
103
+ private unwireTarget;
104
+ private wireBridge;
105
+ private unwireBridge;
106
+ private showWithBridge;
107
+ private callHidePopover;
108
+ private clearWarmupTimer;
109
+ private clearCooldownTimer;
110
+ private startCooldown;
111
+ private handlePointerDownTrigger;
112
+ private handlePointerEnterTrigger;
113
+ private handlePointerLeaveTrigger;
114
+ private handleFocusin;
115
+ private handleFocusout;
116
+ private handlePointerEnterHost;
117
+ private handlePointerLeaveHost;
118
+ }