@ds-mo/ui 14.34.0 → 14.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/dist/.package-ready.json +1 -1
  2. package/dist/agent-patterns.json +1 -1
  3. package/dist/agent.json +1423 -218
  4. package/dist/angular/ds-button-inverted.d.ts +17 -0
  5. package/dist/angular/ds-button-inverted.js +46 -0
  6. package/dist/angular/ds-calendar.d.ts +15 -0
  7. package/dist/angular/ds-calendar.js +43 -0
  8. package/dist/angular/ds-field.d.ts +1 -1
  9. package/dist/angular/ds-field.js +1 -1
  10. package/dist/angular/ds-filter-menu.d.ts +1 -1
  11. package/dist/angular/ds-filter-menu.js +3 -3
  12. package/dist/angular/ds-input-date.d.ts +15 -0
  13. package/dist/angular/ds-input-date.js +43 -0
  14. package/dist/angular/ds-input-time.d.ts +15 -0
  15. package/dist/angular/ds-input-time.js +43 -0
  16. package/dist/angular/ds-input.d.ts +1 -1
  17. package/dist/angular/ds-input.js +3 -3
  18. package/dist/angular/ds-menu.d.ts +1 -1
  19. package/dist/angular/ds-menu.js +3 -3
  20. package/dist/angular/ds-select.d.ts +1 -1
  21. package/dist/angular/ds-select.js +3 -3
  22. package/dist/angular/ds-tab-group.d.ts +1 -1
  23. package/dist/angular/ds-tab-group.js +3 -3
  24. package/dist/angular/ds-table-filter.d.ts +1 -1
  25. package/dist/angular/ds-table-filter.js +3 -3
  26. package/dist/angular/ds-table-group.d.ts +1 -1
  27. package/dist/angular/ds-table-group.js +3 -3
  28. package/dist/angular/ds-table-preferences.d.ts +39 -0
  29. package/dist/angular/ds-table-preferences.js +69 -0
  30. package/dist/angular/ds-table-saved-views.d.ts +1 -1
  31. package/dist/angular/ds-table-saved-views.js +3 -3
  32. package/dist/angular/ds-table-search.d.ts +1 -1
  33. package/dist/angular/ds-table-search.js +3 -3
  34. package/dist/angular/ds-table-sort.d.ts +1 -1
  35. package/dist/angular/ds-table-sort.js +3 -3
  36. package/dist/angular/ds-table-toolbar.d.ts +1 -1
  37. package/dist/angular/ds-table-toolbar.js +3 -3
  38. package/dist/angular/ds-table.d.ts +3 -1
  39. package/dist/angular/ds-table.js +8 -5
  40. package/dist/angular/ds-time-picker.d.ts +15 -0
  41. package/dist/angular/ds-time-picker.js +43 -0
  42. package/dist/angular/index.d.ts +1 -1
  43. package/dist/angular/index.js +6 -0
  44. package/dist/angular/proxies.d.ts +6 -0
  45. package/dist/angular/proxies.js +6 -0
  46. package/dist/angular/text-value-accessor.d.ts +1 -1
  47. package/dist/angular/text-value-accessor.js +2 -2
  48. package/dist/components/ds-agent-activity.js +1 -1
  49. package/dist/components/ds-agent-questionnaire.js +1 -1
  50. package/dist/components/ds-agent-response.js +1 -1
  51. package/dist/components/ds-agent-source-list.js +1 -1
  52. package/dist/components/ds-agent-tool-call.js +1 -1
  53. package/dist/components/ds-attachment-list.js +1 -1
  54. package/dist/components/ds-badge.js +1 -1
  55. package/dist/components/ds-banner.js +1 -1
  56. package/dist/components/ds-bar-action.js +1 -1
  57. package/dist/components/ds-bar-nav.js +1 -1
  58. package/dist/components/ds-bar-page-title.js +1 -1
  59. package/dist/components/ds-bar-title.js +1 -1
  60. package/dist/components/ds-bar-workflow.js +1 -1
  61. package/dist/components/ds-breadcrumb.js +1 -1
  62. package/dist/components/ds-button-filled.js +1 -1
  63. package/dist/components/ds-button-inverted.d.ts +11 -0
  64. package/dist/components/ds-button-inverted.js +2 -0
  65. package/dist/components/ds-button-inverted.js.map +1 -0
  66. package/dist/components/ds-button-unfilled.js +1 -1
  67. package/dist/components/ds-calendar.d.ts +11 -0
  68. package/dist/components/ds-calendar.js +2 -0
  69. package/dist/components/ds-calendar.js.map +1 -0
  70. package/dist/components/ds-card-action-center.js +1 -1
  71. package/dist/components/ds-card-chart.js +1 -1
  72. package/dist/components/ds-card-navigation.js +1 -1
  73. package/dist/components/ds-card-navigation.js.map +1 -1
  74. package/dist/components/ds-card-overview.js +1 -1
  75. package/dist/components/ds-card-setting.js +1 -1
  76. package/dist/components/ds-card-settings-scope.js +1 -1
  77. package/dist/components/ds-card-settings-scope.js.map +1 -1
  78. package/dist/components/ds-chart-legend.js +1 -1
  79. package/dist/components/ds-chart.js +1 -1
  80. package/dist/components/ds-checkbox.js +1 -1
  81. package/dist/components/ds-chip.js +1 -1
  82. package/dist/components/ds-code-block.js +1 -1
  83. package/dist/components/ds-conversation-list-item.js +1 -1
  84. package/dist/components/ds-conversation-list-item.js.map +1 -1
  85. package/dist/components/ds-conversation-list-section.js +1 -1
  86. package/dist/components/ds-conversation-list.js +1 -1
  87. package/dist/components/ds-divider.js +1 -1
  88. package/dist/components/ds-empty-state.js +1 -1
  89. package/dist/components/ds-field.js +1 -1
  90. package/dist/components/ds-filter-menu.js +1 -1
  91. package/dist/components/ds-input-date.d.ts +11 -0
  92. package/dist/components/ds-input-date.js +2 -0
  93. package/dist/components/ds-input-date.js.map +1 -0
  94. package/dist/components/ds-input-time.d.ts +11 -0
  95. package/dist/components/ds-input-time.js +2 -0
  96. package/dist/components/ds-input-time.js.map +1 -0
  97. package/dist/components/ds-input.js +1 -1
  98. package/dist/components/ds-markdown.js +1 -1
  99. package/dist/components/ds-menu.js +1 -1
  100. package/dist/components/ds-message-actions.js +1 -1
  101. package/dist/components/ds-message-bubble.js +1 -1
  102. package/dist/components/ds-message-composer.js +1 -1
  103. package/dist/components/ds-message-scroller.js +1 -1
  104. package/dist/components/ds-message.js +1 -1
  105. package/dist/components/ds-mobile-bar-nav.js +1 -1
  106. package/dist/components/ds-mobile-header.js +1 -1
  107. package/dist/components/ds-mobile-section-switcher.js +1 -1
  108. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  109. package/dist/components/ds-modal.js +1 -1
  110. package/dist/components/ds-pagination.js +1 -1
  111. package/dist/components/ds-panel-nav.js +1 -1
  112. package/dist/components/ds-panel-sub-nav.js +1 -1
  113. package/dist/components/ds-panel-tool-header.js +1 -1
  114. package/dist/components/ds-panel-tool-search.js +1 -1
  115. package/dist/components/ds-panel-tools.js +1 -1
  116. package/dist/components/ds-paper-texture.js +1 -1
  117. package/dist/components/ds-radio.js +1 -1
  118. package/dist/components/ds-score.js +1 -1
  119. package/dist/components/ds-scroll-overlay.js +1 -1
  120. package/dist/components/ds-select.js +1 -1
  121. package/dist/components/ds-shell-app.js +1 -1
  122. package/dist/components/ds-shell-app.js.map +1 -1
  123. package/dist/components/ds-shell-page.js +1 -1
  124. package/dist/components/ds-shell-tools.js +1 -1
  125. package/dist/components/ds-skeleton.js +1 -1
  126. package/dist/components/ds-slider.js +1 -1
  127. package/dist/components/ds-slider.js.map +1 -1
  128. package/dist/components/ds-swatch-picker.js +1 -1
  129. package/dist/components/ds-switch.js +1 -1
  130. package/dist/components/ds-tab-group.js +1 -1
  131. package/dist/components/ds-table-filter.js +1 -1
  132. package/dist/components/ds-table-filter.js.map +1 -1
  133. package/dist/components/ds-table-group.js +1 -1
  134. package/dist/components/ds-table-group.js.map +1 -1
  135. package/dist/components/ds-table-preferences.d.ts +11 -0
  136. package/dist/components/ds-table-preferences.js +2 -0
  137. package/dist/components/ds-table-preferences.js.map +1 -0
  138. package/dist/components/ds-table-saved-views.js +1 -1
  139. package/dist/components/ds-table-saved-views.js.map +1 -1
  140. package/dist/components/ds-table-search.js +1 -1
  141. package/dist/components/ds-table-search.js.map +1 -1
  142. package/dist/components/ds-table-sort.js +1 -1
  143. package/dist/components/ds-table-sort.js.map +1 -1
  144. package/dist/components/ds-table-toolbar.js +1 -1
  145. package/dist/components/ds-table-toolbar.js.map +1 -1
  146. package/dist/components/ds-table.js +1 -1
  147. package/dist/components/ds-table.js.map +1 -1
  148. package/dist/components/ds-tag.js +1 -1
  149. package/dist/components/ds-text.js +1 -1
  150. package/dist/components/ds-textarea.js +1 -1
  151. package/dist/components/ds-textarea.js.map +1 -1
  152. package/dist/components/ds-time-picker.d.ts +11 -0
  153. package/dist/components/ds-time-picker.js +2 -0
  154. package/dist/components/ds-time-picker.js.map +1 -0
  155. package/dist/components/ds-toast.js +1 -1
  156. package/dist/components/ds-tooltip-chart.js +1 -1
  157. package/dist/components/ds-tooltip.js +1 -1
  158. package/dist/components/ds-typing-indicator.js +1 -1
  159. package/dist/components/{p-B5QVra1K.js → p-2GVGhrh0.js} +2 -2
  160. package/dist/components/p-7O-2D29W.js +2 -0
  161. package/dist/components/p-7O-2D29W.js.map +1 -0
  162. package/dist/components/{p-BqGdNrVl.js → p-8oRkhvjy.js} +2 -2
  163. package/dist/components/p-B27_chI7.js +2 -0
  164. package/dist/components/p-B27_chI7.js.map +1 -0
  165. package/dist/components/{p-ya8A_SMw.js → p-BOhpMUwO.js} +2 -2
  166. package/dist/components/{p-ya8A_SMw.js.map → p-BOhpMUwO.js.map} +1 -1
  167. package/dist/components/{p-CqeMumqy.js → p-BV5T1hzN.js} +2 -2
  168. package/dist/components/{p-CIoeHcCk.js → p-BWfzaSc3.js} +2 -2
  169. package/dist/components/{p-AkSwrAKR.js → p-BgLI9JoT.js} +2 -2
  170. package/dist/components/{p-BwsS_Hyr.js → p-C-dHtQMi.js} +2 -2
  171. package/dist/components/p-C0CE2v5D.js +2 -0
  172. package/dist/components/{p-CHBuzAYH.js.map → p-C0CE2v5D.js.map} +1 -1
  173. package/dist/components/{p-CoE7yza3.js → p-C2JnoaWI.js} +2 -2
  174. package/dist/components/{p-lv4YQEsa.js → p-C4m8y9ki.js} +2 -2
  175. package/dist/components/{p-BEttT0UB.js → p-CDJ2Ntcq.js} +2 -2
  176. package/dist/components/p-CKarsuR2.js +2 -0
  177. package/dist/components/p-CKarsuR2.js.map +1 -0
  178. package/dist/components/{p-Dj296RmE.js → p-CMQUuGvl.js} +2 -2
  179. package/dist/components/{p-DyyfWSLB.js → p-CQ5RwY2C.js} +2 -2
  180. package/dist/components/p-CQIh2yRP.js +2 -0
  181. package/dist/components/p-CQIh2yRP.js.map +1 -0
  182. package/dist/components/{p-CQqZgTYk.js → p-CWPT3aOB.js} +2 -2
  183. package/dist/components/p-CWog26I_.js +2 -0
  184. package/dist/components/p-CWog26I_.js.map +1 -0
  185. package/dist/components/{p-LZ8F08Fj.js → p-CaNCGKEJ.js} +2 -2
  186. package/dist/components/p-CaNCGKEJ.js.map +1 -0
  187. package/dist/components/p-CbSUyv5B.js +2 -0
  188. package/dist/components/{p-CnK36Be0.js.map → p-CbSUyv5B.js.map} +1 -1
  189. package/dist/components/{p-Dhw_W8nk.js → p-Cc3ZUQlJ.js} +2 -2
  190. package/dist/components/p-Cc3ZUQlJ.js.map +1 -0
  191. package/dist/components/{p-OxdAzO4v.js → p-Ce4zy8q1.js} +2 -2
  192. package/dist/components/p-CeXf9-X3.js +2 -0
  193. package/dist/components/p-CeXf9-X3.js.map +1 -0
  194. package/dist/components/{p-CNDTHTf6.js → p-Cga6Ex0E.js} +2 -2
  195. package/dist/components/{p-CBQzTgvI.js → p-Cl9phSf0.js} +2 -2
  196. package/dist/components/{p-DzbEJN1A.js → p-ClJhuG5Y.js} +2 -2
  197. package/dist/components/{p-D_QtZZR8.js → p-CqW2eyiF.js} +2 -2
  198. package/dist/components/{p-CHUdAW1Q.js → p-Cus1Sigh.js} +2 -2
  199. package/dist/components/{p-CzbsTZt7.js → p-Cxr0IQjC.js} +2 -2
  200. package/dist/components/p-D7yBIPB6.js +2 -0
  201. package/dist/components/{p-DEyvEIYL.js.map → p-D7yBIPB6.js.map} +1 -1
  202. package/dist/components/p-D8rpKod8.js +2 -0
  203. package/dist/components/p-D8rpKod8.js.map +1 -0
  204. package/dist/components/{p-Da9VpZX2.js → p-DBwa1OXP.js} +2 -2
  205. package/dist/components/{p-Br0dx2uP.js → p-DK9Ex3aY.js} +2 -2
  206. package/dist/components/{p-CweS0FfJ.js → p-DLXXGgmf.js} +2 -2
  207. package/dist/components/{p-CvplapTD.js → p-DVd1TIxi.js} +2 -2
  208. package/dist/components/p-DZ58Sg5a.js.map +1 -1
  209. package/dist/components/{p-Dx7WcWW1.js → p-DgG9W9N4.js} +2 -2
  210. package/dist/components/{p-4DNyCYKM.js → p-DkfM55x8.js} +2 -2
  211. package/dist/components/p-DkikNSSO.js +2 -0
  212. package/dist/components/p-DkikNSSO.js.map +1 -0
  213. package/dist/components/{p-DjCnGxSd.js → p-Dryt3COt.js} +2 -2
  214. package/dist/components/{p-eo_pmPqp.js → p-Ds51RgCi.js} +2 -2
  215. package/dist/components/p-DtkLtKY0.js +2 -0
  216. package/dist/components/p-DtkLtKY0.js.map +1 -0
  217. package/dist/components/{p-DBZwI7HY.js → p-DvzI2A_M.js} +2 -2
  218. package/dist/components/p-J5x-QtlD.js +2 -0
  219. package/dist/components/{p-CIYJhyKt.js.map → p-J5x-QtlD.js.map} +1 -1
  220. package/dist/components/{p-C51ytXM6.js → p-Jaqdfw4E.js} +2 -2
  221. package/dist/components/{p-BMERUCe1.js → p-KBrC9IG4.js} +2 -2
  222. package/dist/components/p-MQ36mEgn.js +2 -0
  223. package/dist/components/p-MQ36mEgn.js.map +1 -0
  224. package/dist/components/{p-CXAa9TUM.js → p-PmcUwTQ_.js} +2 -2
  225. package/dist/components/p-TjXGRPik.js +2 -0
  226. package/dist/components/{p-ByoywoBH.js.map → p-TjXGRPik.js.map} +1 -1
  227. package/dist/components/{p-zZuAAiiI.js → p-bM3LH3kc.js} +2 -2
  228. package/dist/components/{p-CK2S28gU.js → p-ji09W9el.js} +2 -2
  229. package/dist/components/{p-DrjY8Mpy.js → p-neWTRxmf.js} +2 -2
  230. package/dist/components/{p-CMq-jKEI.js → p-rYePOPbJ.js} +2 -2
  231. package/dist/components/{p-C1QHxVvP.js → p-su6y4hLK.js} +2 -2
  232. package/dist/components/p-su6y4hLK.js.map +1 -0
  233. package/dist/components/{p-CKo2nySr.js → p-tNQ0YulU.js} +2 -2
  234. package/dist/components/{p-C8XJgV1n.js → p-x1wf38q3.js} +2 -2
  235. package/dist/docs/components.json +17368 -12812
  236. package/dist/lib/components/ShellApp/shell-app-types.d.ts +3 -1
  237. package/dist/lib/utils/button-render.d.ts +1 -1
  238. package/dist/lib/utils/clock-time.d.ts +22 -0
  239. package/dist/lib/utils/date-filter-value.d.ts +4 -0
  240. package/dist/lib/utils/index.d.ts +3 -1
  241. package/dist/lib/utils/index.js +133 -0
  242. package/dist/lib/utils/index.js.map +4 -4
  243. package/dist/mcp-data/registry/bar-title.json +2 -2
  244. package/dist/mcp-data/registry/button-inverted.json +580 -0
  245. package/dist/mcp-data/registry/calendar.json +273 -0
  246. package/dist/mcp-data/registry/card-navigation.json +4 -2
  247. package/dist/mcp-data/registry/field.json +19 -14
  248. package/dist/mcp-data/registry/filter-menu.json +106 -23
  249. package/dist/mcp-data/registry/input-date.json +516 -0
  250. package/dist/mcp-data/registry/input-time.json +535 -0
  251. package/dist/mcp-data/registry/input.json +56 -8
  252. package/dist/mcp-data/registry/menu.json +22 -3
  253. package/dist/mcp-data/registry/registry.json +1434 -211
  254. package/dist/mcp-data/registry/select.json +105 -14
  255. package/dist/mcp-data/registry/shell-app.json +4 -3
  256. package/dist/mcp-data/registry/tab-group.json +20 -3
  257. package/dist/mcp-data/registry/table-filter.json +26 -6
  258. package/dist/mcp-data/registry/table-group.json +59 -7
  259. package/dist/mcp-data/registry/table-preferences.json +511 -0
  260. package/dist/mcp-data/registry/table-saved-views.json +50 -10
  261. package/dist/mcp-data/registry/table-search.json +24 -6
  262. package/dist/mcp-data/registry/table-sort.json +22 -4
  263. package/dist/mcp-data/registry/table-toolbar.json +26 -6
  264. package/dist/mcp-data/registry/table.json +70 -4
  265. package/dist/mcp-data/registry/textarea.json +2 -2
  266. package/dist/mcp-data/registry/time-picker.json +259 -0
  267. package/dist/react/components.d.ts +6 -0
  268. package/dist/react/components.js +6 -0
  269. package/dist/react/ds-button-inverted.d.ts +13 -0
  270. package/dist/react/ds-button-inverted.js +15 -0
  271. package/dist/react/ds-calendar.d.ts +12 -0
  272. package/dist/react/ds-calendar.js +12 -0
  273. package/dist/react/ds-field.d.ts +1 -1
  274. package/dist/react/ds-input-date.d.ts +12 -0
  275. package/dist/react/ds-input-date.js +12 -0
  276. package/dist/react/ds-input-time.d.ts +12 -0
  277. package/dist/react/ds-input-time.js +12 -0
  278. package/dist/react/ds-table-preferences.d.ts +21 -0
  279. package/dist/react/ds-table-preferences.js +23 -0
  280. package/dist/react/ds-table.d.ts +1 -0
  281. package/dist/react/ds-table.js +1 -0
  282. package/dist/react/ds-time-picker.d.ts +12 -0
  283. package/dist/react/ds-time-picker.js +12 -0
  284. package/dist/types/components/BarTitle/BarTitle.d.ts +1 -0
  285. package/dist/types/components/BarTitle/bar-title-action-chrome.d.ts +2 -0
  286. package/dist/types/components/ButtonInverted/ButtonInverted.d.ts +83 -0
  287. package/dist/types/components/ButtonInverted/index.d.ts +2 -0
  288. package/dist/types/components/Calendar/Calendar.d.ts +32 -0
  289. package/dist/types/components/Calendar/calendar-grid.d.ts +19 -0
  290. package/dist/types/components/Field/Field.d.ts +7 -2
  291. package/dist/types/components/FilterMenu/FilterMenu.d.ts +16 -10
  292. package/dist/types/components/Input/Input.d.ts +10 -0
  293. package/dist/types/components/InputDate/InputDate.d.ts +78 -0
  294. package/dist/types/components/InputTime/InputTime.d.ts +80 -0
  295. package/dist/types/components/Menu/Menu.d.ts +2 -0
  296. package/dist/types/components/Select/Select.d.ts +13 -2
  297. package/dist/types/components/ShellApp/shell-app-types.d.ts +3 -1
  298. package/dist/types/components/TabGroup/TabGroup.d.ts +1 -0
  299. package/dist/types/components/Table/Table.d.ts +8 -0
  300. package/dist/types/components/TableFilter/TableFilter.d.ts +2 -0
  301. package/dist/types/components/TableGroup/TableGroup.d.ts +5 -0
  302. package/dist/types/components/TablePreferences/TablePreferences.d.ts +56 -0
  303. package/dist/types/components/TableSavedViews/TableSavedViews.d.ts +4 -0
  304. package/dist/types/components/TableSearch/TableSearch.d.ts +2 -0
  305. package/dist/types/components/TableSort/TableSort.d.ts +2 -0
  306. package/dist/types/components/TableToolbar/TableToolbar.d.ts +2 -0
  307. package/dist/types/components/TimePicker/TimePicker.d.ts +33 -0
  308. package/dist/types/components.d.ts +1299 -66
  309. package/dist/types/utils/button-render.d.ts +1 -1
  310. package/dist/types/utils/clock-time.d.ts +23 -0
  311. package/dist/types/utils/date-filter-value.d.ts +4 -0
  312. package/dist/types/utils/index.d.ts +3 -1
  313. package/dist/vue/components.d.ts +6 -0
  314. package/dist/vue/components.js +6 -0
  315. package/dist/vue/ds-button-inverted.d.ts +3 -0
  316. package/dist/vue/ds-button-inverted.js +33 -0
  317. package/dist/vue/ds-calendar.d.ts +3 -0
  318. package/dist/vue/ds-calendar.js +16 -0
  319. package/dist/vue/ds-filter-menu.js +5 -0
  320. package/dist/vue/ds-input-date.d.ts +3 -0
  321. package/dist/vue/ds-input-date.js +31 -0
  322. package/dist/vue/ds-input-time.d.ts +3 -0
  323. package/dist/vue/ds-input-time.js +32 -0
  324. package/dist/vue/ds-input.js +2 -0
  325. package/dist/vue/ds-menu.js +1 -0
  326. package/dist/vue/ds-select.js +5 -0
  327. package/dist/vue/ds-tab-group.js +1 -0
  328. package/dist/vue/ds-table-filter.js +1 -0
  329. package/dist/vue/ds-table-group.js +3 -0
  330. package/dist/vue/ds-table-preferences.d.ts +3 -0
  331. package/dist/vue/ds-table-preferences.js +45 -0
  332. package/dist/vue/ds-table-saved-views.js +2 -0
  333. package/dist/vue/ds-table-search.js +1 -0
  334. package/dist/vue/ds-table-sort.js +1 -0
  335. package/dist/vue/ds-table-toolbar.js +2 -1
  336. package/dist/vue/ds-table.js +5 -0
  337. package/dist/vue/ds-time-picker.d.ts +3 -0
  338. package/dist/vue/ds-time-picker.js +16 -0
  339. package/package.json +1 -1
  340. package/dist/components/p-B_nPct4G.js +0 -2
  341. package/dist/components/p-B_nPct4G.js.map +0 -1
  342. package/dist/components/p-BlWNMytv.js +0 -2
  343. package/dist/components/p-BlWNMytv.js.map +0 -1
  344. package/dist/components/p-ByoywoBH.js +0 -2
  345. package/dist/components/p-C1QHxVvP.js.map +0 -1
  346. package/dist/components/p-CHBuzAYH.js +0 -2
  347. package/dist/components/p-CIYJhyKt.js +0 -2
  348. package/dist/components/p-CUL5_8JM.js +0 -2
  349. package/dist/components/p-CUL5_8JM.js.map +0 -1
  350. package/dist/components/p-CnK36Be0.js +0 -2
  351. package/dist/components/p-Cw_npFwN.js +0 -2
  352. package/dist/components/p-Cw_npFwN.js.map +0 -1
  353. package/dist/components/p-DEyvEIYL.js +0 -2
  354. package/dist/components/p-DVWOUQfT.js +0 -2
  355. package/dist/components/p-DVWOUQfT.js.map +0 -1
  356. package/dist/components/p-Dhw_W8nk.js.map +0 -1
  357. package/dist/components/p-LZ8F08Fj.js.map +0 -1
  358. package/dist/components/p-xxuHxP3u.js +0 -2
  359. package/dist/components/p-xxuHxP3u.js.map +0 -1
  360. package/dist/types/components/FilterMenu/filter-menu-calendar.d.ts +0 -13
  361. /package/dist/components/{p-B5QVra1K.js.map → p-2GVGhrh0.js.map} +0 -0
  362. /package/dist/components/{p-BqGdNrVl.js.map → p-8oRkhvjy.js.map} +0 -0
  363. /package/dist/components/{p-CqeMumqy.js.map → p-BV5T1hzN.js.map} +0 -0
  364. /package/dist/components/{p-CIoeHcCk.js.map → p-BWfzaSc3.js.map} +0 -0
  365. /package/dist/components/{p-AkSwrAKR.js.map → p-BgLI9JoT.js.map} +0 -0
  366. /package/dist/components/{p-BwsS_Hyr.js.map → p-C-dHtQMi.js.map} +0 -0
  367. /package/dist/components/{p-CoE7yza3.js.map → p-C2JnoaWI.js.map} +0 -0
  368. /package/dist/components/{p-lv4YQEsa.js.map → p-C4m8y9ki.js.map} +0 -0
  369. /package/dist/components/{p-BEttT0UB.js.map → p-CDJ2Ntcq.js.map} +0 -0
  370. /package/dist/components/{p-Dj296RmE.js.map → p-CMQUuGvl.js.map} +0 -0
  371. /package/dist/components/{p-DyyfWSLB.js.map → p-CQ5RwY2C.js.map} +0 -0
  372. /package/dist/components/{p-CQqZgTYk.js.map → p-CWPT3aOB.js.map} +0 -0
  373. /package/dist/components/{p-OxdAzO4v.js.map → p-Ce4zy8q1.js.map} +0 -0
  374. /package/dist/components/{p-CNDTHTf6.js.map → p-Cga6Ex0E.js.map} +0 -0
  375. /package/dist/components/{p-CBQzTgvI.js.map → p-Cl9phSf0.js.map} +0 -0
  376. /package/dist/components/{p-DzbEJN1A.js.map → p-ClJhuG5Y.js.map} +0 -0
  377. /package/dist/components/{p-D_QtZZR8.js.map → p-CqW2eyiF.js.map} +0 -0
  378. /package/dist/components/{p-CHUdAW1Q.js.map → p-Cus1Sigh.js.map} +0 -0
  379. /package/dist/components/{p-CzbsTZt7.js.map → p-Cxr0IQjC.js.map} +0 -0
  380. /package/dist/components/{p-Da9VpZX2.js.map → p-DBwa1OXP.js.map} +0 -0
  381. /package/dist/components/{p-Br0dx2uP.js.map → p-DK9Ex3aY.js.map} +0 -0
  382. /package/dist/components/{p-CweS0FfJ.js.map → p-DLXXGgmf.js.map} +0 -0
  383. /package/dist/components/{p-CvplapTD.js.map → p-DVd1TIxi.js.map} +0 -0
  384. /package/dist/components/{p-Dx7WcWW1.js.map → p-DgG9W9N4.js.map} +0 -0
  385. /package/dist/components/{p-4DNyCYKM.js.map → p-DkfM55x8.js.map} +0 -0
  386. /package/dist/components/{p-DjCnGxSd.js.map → p-Dryt3COt.js.map} +0 -0
  387. /package/dist/components/{p-eo_pmPqp.js.map → p-Ds51RgCi.js.map} +0 -0
  388. /package/dist/components/{p-DBZwI7HY.js.map → p-DvzI2A_M.js.map} +0 -0
  389. /package/dist/components/{p-C51ytXM6.js.map → p-Jaqdfw4E.js.map} +0 -0
  390. /package/dist/components/{p-BMERUCe1.js.map → p-KBrC9IG4.js.map} +0 -0
  391. /package/dist/components/{p-CXAa9TUM.js.map → p-PmcUwTQ_.js.map} +0 -0
  392. /package/dist/components/{p-zZuAAiiI.js.map → p-bM3LH3kc.js.map} +0 -0
  393. /package/dist/components/{p-CK2S28gU.js.map → p-ji09W9el.js.map} +0 -0
  394. /package/dist/components/{p-DrjY8Mpy.js.map → p-neWTRxmf.js.map} +0 -0
  395. /package/dist/components/{p-CMq-jKEI.js.map → p-rYePOPbJ.js.map} +0 -0
  396. /package/dist/components/{p-CKo2nySr.js.map → p-tNQ0YulU.js.map} +0 -0
  397. /package/dist/components/{p-C8XJgV1n.js.map → p-x1wf38q3.js.map} +0 -0
@@ -22,7 +22,8 @@
22
22
  "avoidWhen": [
23
23
  "The user chooses from a finite set of known values.",
24
24
  "The user must enter multiple paragraphs or formatted content.",
25
- "The value is adjusted primarily by dragging within a numeric range."
25
+ "The value is adjusted primarily by dragging within a numeric range.",
26
+ "The value is a calendar date or a clock time."
26
27
  ],
27
28
  "alternatives": [
28
29
  {
@@ -30,6 +31,16 @@
30
31
  "component": "component:ds-select",
31
32
  "reason": "Select exposes the available values and owns listbox selection behavior."
32
33
  },
34
+ {
35
+ "when": "The value is a calendar date.",
36
+ "component": "component:ds-input-date",
37
+ "reason": "InputDate owns calendar date form values, shared Input density, and the Calendar picker."
38
+ },
39
+ {
40
+ "when": "The value is a clock time.",
41
+ "component": "component:ds-input-time",
42
+ "reason": "InputTime owns clock time form values, shared Input density, and the TimePicker."
43
+ },
33
44
  {
34
45
  "when": "The value is an immediate on/off setting.",
35
46
  "component": "component:ds-switch",
@@ -44,25 +55,29 @@
44
55
  "commonlyComposedWith": [
45
56
  "component:ds-field",
46
57
  "component:ds-icon",
47
- "component:ds-text"
58
+ "component:ds-text",
59
+ "component:ds-select"
48
60
  ],
49
61
  "accessibility": [
50
62
  "Provide a persistent visible label through the owning field or an external native label; placeholder text is guidance and must not be the field's only label.",
51
63
  "Choose the native input type that matches the value so browsers and assistive technology can provide appropriate semantics and keyboards.",
52
64
  "Error text is associated with the native input and announced as an alert when the invalid state is rendered.",
53
65
  "Read-only fields remain focusable and submittable while preventing edits; inactive or disabled fields leave the tab order and are omitted from form submission.",
66
+ "Password fields keep a named show-or-hide action beside the value. Revealing the value changes only the native input type; the submitted string does not change. The action restores focus to the field after activation.",
54
67
  "Number stepper actions are pointer affordances hidden from the accessibility tree; the native spinbutton remains the single keyboard and assistive-technology control.",
55
- "Suffix content must not carry information that is otherwise absent from the field's visible label, value, or description."
68
+ "Prefix and suffix content must not carry information that is otherwise absent from the field's visible label, value, or description.",
69
+ "A nested prefix or suffix Select is a separate control from the editable value and must have its own accessible name."
56
70
  ],
57
71
  "states": [
58
72
  "Every size applies one complete control-density recipe across frame height, inline padding, icon zones, text-container inset, gap, and radius; supporting error content follows the shared 4px field flow.",
59
- "The normal field boundary uses the shared secondary inset border. Focus promotes it to the thicker bold-brand inset stroke, while error uses the thicker bold-negative inset stroke without changing geometry. Set hasBorder to false only in chrome that supplies enough surrounding structure; set hasInteractionFill to false when that containing chrome also owns hover and pressed feedback.",
73
+ "The normal field boundary uses the shared secondary inset border. Focus promotes it to the thicker bold-brand inset stroke, while error uses the thicker bold-negative inset stroke without changing geometry. Set hasBorder to false only in chrome that supplies enough surrounding structure; supporting error copy then insets to the field's text origin rather than the resting border box. Set hasInteractionFill to false when that containing chrome also owns hover and pressed feedback.",
60
74
  "Placeholder text is secondary at rest and quaternary while the editable field is focused because it becomes decorative once typing begins. Single-line placeholder and entered value text truncate with an ellipsis when the field is narrower than its content.",
61
- "Entered values use the parent surface's primary foreground; prefix icons, suffix content, and resting placeholder text use its secondary foreground.",
75
+ "Entered values use the parent surface's primary foreground; prefix icons, prefix text, suffix content, and resting placeholder text use its secondary foreground.",
62
76
  "Search fields show a standard borderless CrossCircle icon action only while they contain a value. Clearing emits the empty value and restores focus to the native field.",
77
+ "Password fields show a standard inset Eye action at the field's density. EyeDisabled replaces it while the value is visible. The action stays available on read-only fields and is inactive with the field.",
63
78
  "Search fields disable browser autocomplete history, automatic capitalization, autocorrection, and spellchecking by default so browser-owned suggestions do not compete with product-owned results. Consumers can explicitly provide autoComplete when a different native history behavior is required.",
64
79
  "Search fields used with owned result sets forward aria-controls and aria-activedescendant to the native field so keyboard focus can remain in the query while assistive technology tracks the active result.",
65
- "The suffix slot is for compact units or decorations. The optional leading icon is decorative and does not replace the field label.",
80
+ "The prefix and suffix slots host compact leading or trailing units such as a currency code, or a nested same-size inset Select. Text adornments use the control's balanced text inset. Nested Selects match the field density, set isInset, hasBorder false, width hug, and indicator up-down, keep a selected value, set allowClear false so the menu has no clear footer, set neutralTrigger so the selected unit stays on the secondary adornment foreground, and do not carry the field's error message. Configurable prefix and suffix controls are separated from the editable value by a vertical divider at the density icon height. The nested inset keeps its 2px remainder toward the line; the editable value keeps the same inline padding from the line as a non-prefixed field keeps from its border. The optional leading icon is decorative and does not replace the field label. Password, search-clear, and date or time actions stay type-owned trailing buttons rather than suffix-slot content.",
66
81
  "Use autoComplete, inputMode, and enterKeyHint to provide browser and virtual-keyboard hints without changing the value's semantic type.",
67
82
  "Number fields preserve native spinbutton semantics, keyboard stepping, and min/max constraint behavior while dsChange continues to report the native string value. Two borderless inset chevron actions split the available inner height, disable independently at their numeric bound, and restore focus to the spinbutton after pointer activation.",
68
83
  "textAlign positions the editable value while the numeric stepper stays on the opposite inline edge, keeping the actions clear of the number: start-aligned values use an inline-end stepper and end-aligned values use an inline-start stepper.",
@@ -221,6 +236,14 @@
221
236
  "mutable": false,
222
237
  "description": "Show the standard hover and pressed fill when the field is not embedded in interactive chrome."
223
238
  },
239
+ "hidePasswordLabel": {
240
+ "type": "string",
241
+ "resolvedType": "string",
242
+ "attribute": "hide-password-label",
243
+ "default": "'Hide password'",
244
+ "required": false,
245
+ "mutable": false
246
+ },
224
247
  "icon": {
225
248
  "type": "string | undefined",
226
249
  "resolvedType": "string | undefined",
@@ -309,6 +332,14 @@
309
332
  "required": false,
310
333
  "mutable": false
311
334
  },
335
+ "showPasswordLabel": {
336
+ "type": "string",
337
+ "resolvedType": "string",
338
+ "attribute": "show-password-label",
339
+ "default": "'Show password'",
340
+ "required": false,
341
+ "mutable": false
342
+ },
312
343
  "size": {
313
344
  "type": "InputSize",
314
345
  "resolvedType": "\"lg\" | \"md\" | \"sm\" | \"xs\"",
@@ -505,6 +536,14 @@
505
536
  "mutable": false,
506
537
  "description": "Show the standard hover and pressed fill when the field is not embedded in interactive chrome."
507
538
  },
539
+ "hidePasswordLabel": {
540
+ "type": "string",
541
+ "resolvedType": "string",
542
+ "attribute": "hide-password-label",
543
+ "default": "'Hide password'",
544
+ "required": false,
545
+ "mutable": false
546
+ },
508
547
  "icon": {
509
548
  "type": "string | undefined",
510
549
  "resolvedType": "string | undefined",
@@ -593,6 +632,14 @@
593
632
  "required": false,
594
633
  "mutable": false
595
634
  },
635
+ "showPasswordLabel": {
636
+ "type": "string",
637
+ "resolvedType": "string",
638
+ "attribute": "show-password-label",
639
+ "default": "'Show password'",
640
+ "required": false,
641
+ "mutable": false
642
+ },
596
643
  "size": {
597
644
  "type": "InputSize",
598
645
  "resolvedType": "\"lg\" | \"md\" | \"sm\" | \"xs\"",
@@ -709,18 +756,19 @@
709
756
  ],
710
757
  "registryDependencies": [
711
758
  "button-unfilled",
759
+ "divider",
712
760
  "icon",
713
761
  "text"
714
762
  ],
715
763
  "files": [
716
764
  {
717
765
  "path": "src/wc/components/Input/Input.css",
718
- "content": "@import '../../utils/interaction-fill.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/control-inactive.css';\n@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/control-parts.css';\n@import '../../utils/control-width.css';\n@import '../../utils/field-stack.css';\n@import '../../utils/typography.css';\n@import '../../utils/forced-colors.css';\n\n:host(.input-host) {\n display: inline-flex;\n --ds-input-fg: var(--color-foreground-primary);\n --ds-input-placeholder: var(--color-foreground-secondary);\n --ds-input-placeholder-active: var(--color-foreground-quaternary);\n --ds-input-adornment-fg: var(--color-foreground-secondary);\n --ds-input-border: var(--color-border-secondary);\n --ds-input-stepper-fg: var(--color-foreground-secondary);\n}\n\n:host(.ds-control-width--fill),\n:host(.ds-control-width--fill) .input-control {\n width: 100%;\n}\n\n.input-control {\n overflow: hidden;\n background: transparent;\n border: none;\n border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n color: var(--ds-input-fg);\n}\n\n.input-control--bordered {\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--ds-input-border);\n}\n\n.input-control--error {\n --ds-interaction-border-width: var(--dimension-stroke-width-018);\n --ds-interaction-border-color: var(--color-border-bold-negative);\n}\n\n.input-control--bordered:focus-within:not(.input-control--error) {\n --ds-interaction-border-width: var(--dimension-stroke-width-018);\n --ds-interaction-border-color: var(--color-border-bold-brand);\n}\n\n:host(.ds-control-inactive) .input-control::after {\n background: transparent;\n}\n\n.native-input {\n position: relative;\n z-index: 2;\n flex: 1 1 auto;\n min-width: 0;\n height: 100%;\n background: transparent;\n border: none;\n border-radius: inherit;\n outline: none;\n color: var(--ds-input-fg);\n font-family: inherit;\n text-overflow: ellipsis;\n -webkit-appearance: none;\n appearance: none;\n}\n\n.native-input--align-start {\n text-align: start;\n}\n\n.native-input--align-end {\n text-align: end;\n}\n\n/* Replace engine-specific number arrows with one density-aware inset stepper. */\n.native-input[type='number'] {\n -moz-appearance: textfield;\n}\n\n.native-input[type='number']::-webkit-inner-spin-button,\n.native-input[type='number']::-webkit-outer-spin-button {\n margin: 0;\n -webkit-appearance: none;\n}\n\n.input-control--number.input-control--align-start {\n padding-inline-end: var(--dimension-space-025);\n}\n\n.input-control--number.input-control--align-end {\n padding-inline-start: var(--dimension-space-025);\n}\n\n.input-control__number-stepper {\n position: relative;\n z-index: 2;\n display: grid;\n grid-template-rows: repeat(2, minmax(0, 1fr));\n flex: 0 0 var(--ds-control-icon);\n width: var(--ds-control-icon);\n height: calc(var(--ds-control-height) - var(--dimension-space-050));\n overflow: hidden;\n border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n color: var(--ds-input-stepper-fg);\n}\n\n.input-control__number-step {\n all: unset;\n position: relative;\n z-index: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n color: inherit;\n cursor: default;\n}\n\n.input-control__number-step--increment {\n border-radius: var(--ds-control-radius, var(--dimension-radius-025))\n var(--ds-control-radius, var(--dimension-radius-025)) 0 0;\n}\n\n.input-control__number-step--decrement {\n border-radius: 0 0 var(--ds-control-radius, var(--dimension-radius-025))\n var(--ds-control-radius, var(--dimension-radius-025));\n}\n\n.input-control__number-step:disabled {\n color: var(--color-foreground-quaternary);\n}\n\n.input-control__number-step-icon {\n flex-shrink: 0;\n}\n\n.native-input:disabled {\n cursor: not-allowed;\n}\n\n.native-input:read-only:not(:disabled) {\n cursor: default;\n}\n\n.native-input::placeholder {\n color: var(--ds-input-placeholder);\n}\n\n.native-input:focus::placeholder {\n color: var(--ds-input-placeholder-active);\n}\n\n.native-input[type='search']::-webkit-search-cancel-button,\n.native-input[type='search']::-webkit-search-decoration {\n display: none;\n}\n\n.input-control__prefix,\n.input-control__suffix {\n position: relative;\n z-index: 2;\n color: var(--ds-input-adornment-fg);\n}\n\n.input-control__suffix--empty {\n display: none;\n}\n\n/* The input frame owns the nested clear action's cross-axis alignment. */\n.input-control > .input-control__clear {\n position: relative;\n z-index: 2;\n flex-shrink: 0;\n align-self: center;\n}\n\n.input-control.ds-control--lg:has(.input-control__clear),\n.input-control.ds-control--md:has(.input-control__clear),\n.input-control.ds-control--sm:has(.input-control__clear) {\n padding-right: var(--dimension-space-050);\n}\n\n.input-control.ds-control--xs:has(.input-control__clear) {\n padding-right: 0;\n}\n\n.error-text {\n width: 100%;\n}\n\n@media (forced-colors: active) {\n .input-control__number-step:disabled {\n color: var(--ds-forced-color-disabled);\n }\n\n .input-control--bordered:focus-within:not(.input-control--error)::after {\n outline-color: var(--ds-forced-color-selected);\n }\n\n .input-control--error::after {\n outline-style: dashed;\n }\n}\n",
766
+ "content": "@import '../../utils/interaction-fill.css';\n@import '../../utils/focus-ring.css';\n@import '../../utils/control-inactive.css';\n@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/control-parts.css';\n@import '../../utils/control-width.css';\n@import '../../utils/field-stack.css';\n@import '../../utils/typography.css';\n@import '../../utils/forced-colors.css';\n\n:host(.input-host) {\n display: inline-flex;\n --ds-input-fg: var(--color-foreground-primary);\n --ds-input-placeholder: var(--color-foreground-secondary);\n --ds-input-placeholder-active: var(--color-foreground-quaternary);\n --ds-input-adornment-fg: var(--color-foreground-secondary);\n --ds-input-border: var(--color-border-secondary);\n --ds-input-stepper-fg: var(--color-foreground-secondary);\n}\n\n:host(.ds-control-width--fill),\n:host(.ds-control-width--fill) .input-control {\n width: 100%;\n}\n\n.input-control {\n overflow: hidden;\n background: transparent;\n border: none;\n border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n color: var(--ds-input-fg);\n}\n\n.input-control--bordered {\n --ds-interaction-border-width: var(--dimension-stroke-width-012);\n --ds-interaction-border-color: var(--ds-input-border);\n}\n\n.input-control--error {\n --ds-interaction-border-width: var(--dimension-stroke-width-018);\n --ds-interaction-border-color: var(--color-border-bold-negative);\n}\n\n.input-control--bordered:focus-within:not(.input-control--error) {\n --ds-interaction-border-width: var(--dimension-stroke-width-018);\n --ds-interaction-border-color: var(--color-border-bold-brand);\n}\n\n:host(.ds-control-inactive) .input-control::after {\n background: transparent;\n}\n\n.native-input {\n position: relative;\n z-index: 2;\n flex: 1 1 auto;\n min-width: 0;\n height: 100%;\n background: transparent;\n border: none;\n border-radius: inherit;\n outline: none;\n color: var(--ds-input-fg);\n font-family: inherit;\n text-overflow: ellipsis;\n -webkit-appearance: none;\n appearance: none;\n}\n\n.native-input--align-start {\n text-align: start;\n}\n\n.native-input--align-end {\n text-align: end;\n}\n\n/* Replace engine-specific number arrows with one density-aware inset stepper. */\n.native-input[type='number'] {\n -moz-appearance: textfield;\n}\n\n.native-input[type='number']::-webkit-inner-spin-button,\n.native-input[type='number']::-webkit-outer-spin-button {\n margin: 0;\n -webkit-appearance: none;\n}\n\n.input-control--number.input-control--align-start {\n padding-inline-end: var(--dimension-space-025);\n}\n\n.input-control--number.input-control--align-end {\n padding-inline-start: var(--dimension-space-025);\n}\n\n.input-control__number-stepper {\n position: relative;\n z-index: 2;\n display: grid;\n grid-template-rows: repeat(2, minmax(0, 1fr));\n flex: 0 0 var(--ds-control-icon);\n width: var(--ds-control-icon);\n height: calc(var(--ds-control-height) - var(--dimension-space-050));\n overflow: hidden;\n border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n color: var(--ds-input-stepper-fg);\n}\n\n.input-control__number-step {\n all: unset;\n position: relative;\n z-index: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n color: inherit;\n cursor: default;\n}\n\n.input-control__number-step--increment {\n border-radius: var(--ds-control-radius, var(--dimension-radius-025))\n var(--ds-control-radius, var(--dimension-radius-025)) 0 0;\n}\n\n.input-control__number-step--decrement {\n border-radius: 0 0 var(--ds-control-radius, var(--dimension-radius-025))\n var(--ds-control-radius, var(--dimension-radius-025));\n}\n\n.input-control__number-step:disabled {\n color: var(--color-foreground-quaternary);\n}\n\n.input-control__number-step-icon {\n flex-shrink: 0;\n}\n\n.native-input:disabled {\n cursor: not-allowed;\n}\n\n.native-input:read-only:not(:disabled) {\n cursor: default;\n}\n\n.native-input::placeholder {\n color: var(--ds-input-placeholder);\n}\n\n.native-input:focus::placeholder {\n color: var(--ds-input-placeholder-active);\n}\n\n.native-input[type='search']::-webkit-search-cancel-button,\n.native-input[type='search']::-webkit-search-decoration {\n display: none;\n}\n\n.input-control__prefix,\n.input-control__prefix-text,\n.input-control__suffix {\n position: relative;\n z-index: 2;\n color: var(--ds-input-adornment-fg);\n}\n\n.input-control__prefix-text,\n.input-control__suffix {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n white-space: nowrap;\n}\n\n.input-control__prefix-text--control,\n.input-control__suffix--control {\n align-self: stretch;\n min-width: 0;\n white-space: normal;\n gap: var(--dimension-space-025);\n}\n\n.input-control__prefix-text--empty,\n.input-control__suffix--empty {\n display: none;\n}\n\n/* Nested inset controls keep 2px on the outer edge, matching trailing actions. */\n.input-control--prefix-control {\n padding-inline-start: var(--dimension-space-025);\n}\n\n.input-control--suffix-control {\n padding-inline-end: var(--dimension-space-025);\n}\n\n.input-control__adornment-divider {\n position: relative;\n z-index: 2;\n pointer-events: none;\n}\n\n/*\n * Select side stays on the 2px inset remainder. Value side matches a\n * non-prefixed field's inline padding from its border.\n */\n.input-control__prefix-text--control .input-control__adornment-divider {\n margin-inline-end: calc(var(--ds-control-padding-inline) - var(--ds-control-gap));\n}\n\n.input-control__suffix--control .input-control__adornment-divider {\n margin-inline-start: calc(var(--ds-control-padding-inline) - var(--ds-control-gap));\n}\n\n/* The input frame owns the nested clear action's cross-axis alignment. */\n.input-control > .input-control__clear {\n position: relative;\n z-index: 2;\n flex-shrink: 0;\n align-self: center;\n}\n\n.input-control.ds-control--lg:has(.input-control__clear),\n.input-control.ds-control--md:has(.input-control__clear),\n.input-control.ds-control--sm:has(.input-control__clear) {\n padding-right: var(--dimension-space-050);\n}\n\n.input-control.ds-control--xs:has(.input-control__clear) {\n padding-right: 0;\n}\n\n/* Same-size inset trailing action as date/time: 2px remaining on the end edges. */\n.input-control > .input-control__trailing-action {\n position: relative;\n z-index: 2;\n flex-shrink: 0;\n align-self: center;\n}\n\n:host(.ds-control-inactive) .input-control > .input-control__trailing-action {\n --effect-opacity-medium: 1;\n}\n\n.input-control:has(.input-control__trailing-action) {\n padding-inline-end: var(--dimension-space-025);\n}\n\n.error-text {\n width: 100%;\n}\n\n@media (forced-colors: active) {\n .input-control__number-step:disabled {\n color: var(--ds-forced-color-disabled);\n }\n\n .input-control--bordered:focus-within:not(.input-control--error)::after {\n outline-color: var(--ds-forced-color-selected);\n }\n\n .input-control--error::after {\n outline-style: dashed;\n }\n}\n",
719
767
  "type": "registry:ui"
720
768
  },
721
769
  {
722
770
  "path": "src/wc/components/Input/Input.tsx",
723
- "content": "import {\n AttachInternals,\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Element,\n Method,\n Watch,\n h,\n Host,\n} from '@stencil/core';\nimport {\n controlWidthClass,\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n restoreStringFormState,\n setFormControlValue,\n setRequiredValidity,\n type ControlWidth,\n} from '../../utils';\n\nexport type InputType = 'text' | 'email' | 'tel' | 'url' | 'search' | 'password' | 'number';\nexport type InputSize = 'lg' | 'md' | 'sm' | 'xs';\nexport type InputWidth = ControlWidth;\nexport type InputTextAlign = 'start' | 'end';\n\nconst ICON_SIZE: Record<InputSize, 'lg' | 'md' | 'sm' | 'xs'> = {\n lg: 'lg',\n md: 'md',\n sm: 'sm',\n xs: 'xs',\n};\n\nconst CLEAR_BUTTON_SIZE: Record<InputSize, 'md' | 'sm' | 'xs'> = {\n lg: 'md',\n md: 'sm',\n sm: 'xs',\n xs: 'xs',\n};\n\nlet idCounter = 0;\n\n@Component({\n tag: 'ds-input',\n styleUrl: 'Input.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Input {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n private generatedId = `ds-input-${++idCounter}`;\n private errorId = `${this.generatedId}-error`;\n\n @Prop({ mutable: true }) value: string = '';\n @Prop({ reflect: true }) name: string | undefined;\n @Prop({ reflect: true }) form: string | undefined;\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Keeps the value focusable and submittable while preventing edits. */\n @Prop({ reflect: true }) readOnly: boolean = false;\n @Prop({ reflect: true }) required: boolean = false;\n @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n @Prop() clearLabel: string = 'Clear';\n @Prop() placeholder: string | undefined;\n @Prop() type: InputType = 'text';\n /** Minimum accepted value when type is number. */\n @Prop() min: number | undefined;\n /** Maximum accepted value when type is number. */\n @Prop() max: number | undefined;\n /** Numeric increment used by native stepping and constraint validation. */\n @Prop() step: number | undefined;\n /** Align the editable value; number steppers follow the same inline edge. */\n @Prop() textAlign: InputTextAlign = 'start';\n /** Native browser autofill hint. */\n @Prop({ attribute: 'autocomplete' }) autoComplete: string | undefined;\n /** Preferred virtual keyboard without changing the value semantics. */\n @Prop({ attribute: 'inputmode' }) inputMode: string = '';\n /** Preferred virtual-keyboard action label. */\n @Prop({ attribute: 'enterkeyhint' }) enterKeyHint: string = '';\n /** Control density. */\n @Prop() size: InputSize = 'md';\n /** Width fit — fill the parent (default) or hug the available content. */\n @Prop() width: InputWidth = 'fill';\n /** Show the standard inset border, including focused and invalid strokes. */\n @Prop() hasBorder: boolean = true;\n /** Show the standard hover and pressed fill when the field is not embedded in interactive chrome. */\n @Prop() hasInteractionFill: boolean = true;\n /** Optional leading icon name. */\n @Prop() icon: string | undefined;\n @Prop() isInactive: boolean = false;\n @Prop() autoFocus: boolean = false;\n @Prop() error: boolean = false;\n @Prop() errorMessage: string | undefined;\n /** Associates the internal input with an external <label>. */\n @Prop() inputId: string | undefined;\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n /** Identifies the results or popup controlled by the editable field. */\n @Prop({ attribute: 'aria-controls' }) ariaControls: string | undefined;\n /** Identifies the active descendant while focus remains in the editable field. */\n @Prop({ attribute: 'aria-activedescendant' }) ariaActiveDescendant: string | undefined;\n\n @Event() dsChange!: EventEmitter<string>;\n @Event() dsClear!: EventEmitter<void>;\n\n private initialValue = '';\n private inputEl?: HTMLInputElement;\n @State() private formDisabled = false;\n @State() private hasSuffix = false;\n @State() private focused = false;\n @State() private touched = false;\n\n componentWillLoad() {\n this.initialValue = this.value;\n this.hasSuffix = Boolean(this.el.querySelector('[slot=\"suffix\"]'));\n this.syncFormValue();\n }\n\n componentDidRender() {\n const hasSuffix = Boolean(this.el.querySelector('[slot=\"suffix\"]'));\n if (hasSuffix !== this.hasSuffix) this.hasSuffix = hasSuffix;\n }\n\n @Watch('value')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n syncFormValue() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n setFormControlValue(this.internals, this.value, { inactive });\n const missing = this.required && !inactive && this.value.length === 0;\n setRequiredValidity(this.internals, missing, this.requiredMessage);\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.value = this.initialValue;\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n this.value = restoreStringFormState(state);\n }\n\n @Method()\n async setFocus() {\n this.inputEl?.focus();\n }\n\n private handleInput = (e: Event) => {\n this.value = (e.target as HTMLInputElement).value;\n this.dsChange.emit(this.value);\n };\n\n private handleFocus = () => {\n this.focused = true;\n };\n\n private handleBlur = () => {\n this.focused = false;\n this.touched = true;\n };\n\n private handleClear = () => {\n this.value = '';\n this.dsChange.emit('');\n this.dsClear.emit();\n this.inputEl?.focus();\n };\n\n private numericStepDisabled(direction: 1 | -1): boolean {\n if (this.value.trim() === '') return false;\n const value = Number(this.value);\n if (!Number.isFinite(value)) return false;\n if (direction > 0 && this.max !== undefined) return value >= this.max;\n if (direction < 0 && this.min !== undefined) return value <= this.min;\n return false;\n }\n\n private handleNumericStep = (direction: 1 | -1) => {\n const input = this.inputEl;\n if (!input || input.disabled || input.readOnly || this.numericStepDisabled(direction)) return;\n\n const previous = input.value;\n if (direction > 0) input.stepUp();\n else input.stepDown();\n input.focus({ preventScroll: true });\n if (input.value === previous) return;\n\n this.value = input.value;\n this.dsChange.emit(this.value);\n };\n\n private renderNumericStepper(inactive: boolean) {\n const incrementDisabled = inactive || this.readOnly || this.numericStepDisabled(1);\n const decrementDisabled = inactive || this.readOnly || this.numericStepDisabled(-1);\n\n return (\n <span class=\"input-control__number-stepper\">\n <button\n type=\"button\"\n class=\"input-control__number-step input-control__number-step--increment ds-focus-ring-inset ds-interaction-fill\"\n disabled={incrementDisabled}\n tabIndex={-1}\n aria-hidden=\"true\"\n onClick={() => this.handleNumericStep(1)}\n >\n <ds-icon\n class=\"input-control__number-step-icon ds-interaction-fill__content\"\n name=\"ChevronUp\"\n size=\"xs\"\n color=\"inherit\"\n />\n </button>\n <button\n type=\"button\"\n class=\"input-control__number-step input-control__number-step--decrement ds-focus-ring-inset ds-interaction-fill\"\n disabled={decrementDisabled}\n tabIndex={-1}\n aria-hidden=\"true\"\n onClick={() => this.handleNumericStep(-1)}\n >\n <ds-icon\n class=\"input-control__number-step-icon ds-interaction-fill__content\"\n name=\"ChevronDown\"\n size=\"xs\"\n color=\"inherit\"\n />\n </button>\n </span>\n );\n }\n\n render() {\n const inputId = this.inputId ?? this.generatedId;\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n const filled = this.value.length > 0;\n const dirty = this.value !== this.initialValue;\n const showClear = this.type === 'search' && filled && !inactive && !this.readOnly;\n const showError = this.error && Boolean(this.errorMessage);\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const iconSize = ICON_SIZE[this.size];\n const numeric = this.type === 'number';\n const resolvedAutoComplete = this.autoComplete ?? (this.type === 'search' ? 'off' : undefined);\n\n const describedBy =\n [this.ariaDescribedby, showError ? this.errorId : undefined].filter(Boolean).join(' ') ||\n undefined;\n\n return (\n <Host\n class={{\n 'input-host': true,\n 'ds-field-stack': true,\n 'ds-control-inactive': inactive,\n [`ds-control--${this.size}`]: true,\n ...controlWidthClass(this.width),\n }}\n data-disabled={inactive ? '' : undefined}\n data-readonly={this.readOnly ? '' : undefined}\n data-required={this.required ? '' : undefined}\n data-invalid={this.error ? '' : undefined}\n data-filled={filled ? '' : undefined}\n data-focused={this.focused ? '' : undefined}\n data-dirty={dirty ? '' : undefined}\n data-touched={this.touched ? '' : undefined}\n >\n <div\n class={{\n 'input-control': true,\n 'input-control--number': numeric,\n 'input-control--align-start': this.textAlign === 'start',\n 'input-control--align-end': this.textAlign === 'end',\n 'ds-control-frame': true,\n 'input-control--bordered': this.hasBorder,\n 'input-control--error': this.hasBorder && this.error,\n 'ds-interaction-fill': this.hasInteractionFill,\n [`ds-control--${this.size}`]: true,\n }}\n >\n {numeric && this.textAlign === 'end' && this.renderNumericStepper(inactive)}\n {this.icon && (\n <span\n class=\"input-control__prefix ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name={this.icon} size={iconSize} color=\"inherit\" />\n </span>\n )}\n <input\n ref={element => {\n this.inputEl = element;\n }}\n type={this.type}\n id={inputId}\n value={this.value}\n placeholder={this.placeholder}\n min={numeric ? this.min : undefined}\n max={numeric ? this.max : undefined}\n step={numeric ? this.step : undefined}\n disabled={inactive}\n readOnly={this.readOnly}\n required={this.required}\n autoFocus={this.autoFocus}\n autoComplete={resolvedAutoComplete}\n autoCapitalize={this.type === 'search' ? 'none' : undefined}\n autoCorrect={this.type === 'search' ? 'off' : undefined}\n spellcheck={this.type === 'search' ? false : undefined}\n inputMode={this.inputMode || undefined}\n enterKeyHint={this.enterKeyHint || undefined}\n class={`native-input native-input--align-${this.textAlign} ds-control-label-box ds-text--${textVariant.replace('text-', '')} ds-text--regular ds-interaction-fill__content`}\n aria-label={this.ariaLabel}\n aria-labelledby={this.ariaLabelledby}\n aria-describedby={describedBy}\n aria-controls={this.ariaControls}\n aria-activedescendant={this.ariaActiveDescendant}\n aria-invalid={this.error ? 'true' : undefined}\n onInput={this.handleInput}\n onFocus={this.handleFocus}\n onBlur={this.handleBlur}\n />\n <span\n class={{\n 'input-control__suffix': true,\n 'ds-control-icon-box': true,\n 'input-control__suffix--empty': !this.hasSuffix,\n }}\n >\n <slot name=\"suffix\" />\n </span>\n {showClear && (\n <ds-button-unfilled\n class=\"input-control__clear\"\n variant=\"icon\"\n size={CLEAR_BUTTON_SIZE[this.size]}\n icon=\"CrossCircle\"\n hasBorder={false}\n rounded\n ariaLabel={this.clearLabel}\n onDsClick={this.handleClear}\n />\n )}\n {numeric && this.textAlign === 'start' && this.renderNumericStepper(inactive)}\n </div>\n {showError && (\n <ds-text\n class=\"error-text\"\n as=\"div\"\n variant=\"text-body-small\"\n color=\"negative\"\n textId={this.errorId}\n role=\"alert\"\n >\n {this.errorMessage}\n </ds-text>\n )}\n </Host>\n );\n }\n}\n",
771
+ "content": "import {\n AttachInternals,\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Element,\n Method,\n Watch,\n h,\n Host,\n} from '@stencil/core';\nimport {\n controlWidthClass,\n CONTROL_TEXT_VARIANT,\n DEFAULT_REQUIRED_MESSAGE,\n restoreStringFormState,\n setFormControlValue,\n setRequiredValidity,\n type ControlWidth,\n} from '../../utils';\n\nexport type InputType = 'text' | 'email' | 'tel' | 'url' | 'search' | 'password' | 'number';\nexport type InputSize = 'lg' | 'md' | 'sm' | 'xs';\nexport type InputWidth = ControlWidth;\nexport type InputTextAlign = 'start' | 'end';\n\nconst CONTROL_ADORNMENT = 'ds-select, ds-button-unfilled, ds-button-filled';\n\nfunction isControlAdornment(element: Element): boolean {\n return element.matches(CONTROL_ADORNMENT) || Boolean(element.querySelector(CONTROL_ADORNMENT));\n}\n\nconst ICON_SIZE: Record<InputSize, 'lg' | 'md' | 'sm' | 'xs'> = {\n lg: 'lg',\n md: 'md',\n sm: 'sm',\n xs: 'xs',\n};\n\nconst CLEAR_BUTTON_SIZE: Record<InputSize, 'md' | 'sm' | 'xs'> = {\n lg: 'md',\n md: 'sm',\n sm: 'xs',\n xs: 'xs',\n};\n\nlet idCounter = 0;\n\n@Component({\n tag: 'ds-input',\n styleUrl: 'Input.css',\n scoped: true,\n formAssociated: true,\n})\nexport class Input {\n @Element() el!: HTMLElement;\n @AttachInternals() internals!: ElementInternals;\n\n private generatedId = `ds-input-${++idCounter}`;\n private errorId = `${this.generatedId}-error`;\n\n @Prop({ mutable: true }) value: string = '';\n @Prop({ reflect: true }) name: string | undefined;\n @Prop({ reflect: true }) form: string | undefined;\n @Prop({ reflect: true }) disabled: boolean = false;\n /** Keeps the value focusable and submittable while preventing edits. */\n @Prop({ reflect: true }) readOnly: boolean = false;\n @Prop({ reflect: true }) required: boolean = false;\n @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n @Prop() clearLabel: string = 'Clear';\n @Prop() showPasswordLabel: string = 'Show password';\n @Prop() hidePasswordLabel: string = 'Hide password';\n @Prop() placeholder: string | undefined;\n @Prop() type: InputType = 'text';\n /** Minimum accepted value when type is number. */\n @Prop() min: number | undefined;\n /** Maximum accepted value when type is number. */\n @Prop() max: number | undefined;\n /** Numeric increment used by native stepping and constraint validation. */\n @Prop() step: number | undefined;\n /** Align the editable value; number steppers follow the same inline edge. */\n @Prop() textAlign: InputTextAlign = 'start';\n /** Native browser autofill hint. */\n @Prop({ attribute: 'autocomplete' }) autoComplete: string | undefined;\n /** Preferred virtual keyboard without changing the value semantics. */\n @Prop({ attribute: 'inputmode' }) inputMode: string = '';\n /** Preferred virtual-keyboard action label. */\n @Prop({ attribute: 'enterkeyhint' }) enterKeyHint: string = '';\n /** Control density. */\n @Prop() size: InputSize = 'md';\n /** Width fit — fill the parent (default) or hug the available content. */\n @Prop() width: InputWidth = 'fill';\n /** Show the standard inset border, including focused and invalid strokes. */\n @Prop() hasBorder: boolean = true;\n /** Show the standard hover and pressed fill when the field is not embedded in interactive chrome. */\n @Prop() hasInteractionFill: boolean = true;\n /** Optional leading icon name. */\n @Prop() icon: string | undefined;\n @Prop() isInactive: boolean = false;\n @Prop() autoFocus: boolean = false;\n @Prop() error: boolean = false;\n @Prop() errorMessage: string | undefined;\n /** Associates the internal input with an external <label>. */\n @Prop() inputId: string | undefined;\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n /** Identifies the results or popup controlled by the editable field. */\n @Prop({ attribute: 'aria-controls' }) ariaControls: string | undefined;\n /** Identifies the active descendant while focus remains in the editable field. */\n @Prop({ attribute: 'aria-activedescendant' }) ariaActiveDescendant: string | undefined;\n\n @Event() dsChange!: EventEmitter<string>;\n @Event() dsClear!: EventEmitter<void>;\n\n private initialValue = '';\n private inputEl?: HTMLInputElement;\n @State() private formDisabled = false;\n @State() private hasPrefix = false;\n @State() private hasPrefixControl = false;\n @State() private hasSuffix = false;\n @State() private hasSuffixControl = false;\n @State() private focused = false;\n @State() private touched = false;\n @State() private passwordRevealed = false;\n private restorePasswordFocus = false;\n\n componentWillLoad() {\n this.initialValue = this.value;\n this.syncAdornmentSlots();\n this.syncFormValue();\n }\n\n componentDidRender() {\n this.syncAdornmentSlots();\n if (!this.restorePasswordFocus) return;\n this.restorePasswordFocus = false;\n this.inputEl?.focus({ preventScroll: true });\n }\n\n @Watch('type')\n onTypeChange() {\n if (this.type !== 'password') this.passwordRevealed = false;\n }\n\n private syncAdornmentSlots() {\n const prefixNodes = [...this.el.querySelectorAll('[slot=\"prefix\"]')];\n const suffixNodes = [...this.el.querySelectorAll('[slot=\"suffix\"]')];\n const hasPrefix = prefixNodes.length > 0;\n const hasPrefixControl = prefixNodes.some(isControlAdornment);\n const hasSuffix = suffixNodes.length > 0;\n const hasSuffixControl = suffixNodes.some(isControlAdornment);\n if (hasPrefix !== this.hasPrefix) this.hasPrefix = hasPrefix;\n if (hasPrefixControl !== this.hasPrefixControl) this.hasPrefixControl = hasPrefixControl;\n if (hasSuffix !== this.hasSuffix) this.hasSuffix = hasSuffix;\n if (hasSuffixControl !== this.hasSuffixControl) this.hasSuffixControl = hasSuffixControl;\n }\n\n @Watch('value')\n @Watch('disabled')\n @Watch('isInactive')\n @Watch('required')\n syncFormValue() {\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n setFormControlValue(this.internals, this.value, { inactive });\n const missing = this.required && !inactive && this.value.length === 0;\n setRequiredValidity(this.internals, missing, this.requiredMessage);\n }\n\n formDisabledCallback(disabled: boolean) {\n this.formDisabled = disabled;\n this.syncFormValue();\n }\n\n formResetCallback() {\n this.value = this.initialValue;\n this.passwordRevealed = false;\n }\n\n formStateRestoreCallback(state: string | File | FormData | null) {\n this.value = restoreStringFormState(state);\n }\n\n @Method()\n async setFocus() {\n this.inputEl?.focus();\n }\n\n private handleInput = (e: Event) => {\n this.value = (e.target as HTMLInputElement).value;\n this.dsChange.emit(this.value);\n };\n\n private handleFocus = () => {\n this.focused = true;\n };\n\n private handleBlur = () => {\n this.focused = false;\n this.touched = true;\n };\n\n private handleClear = () => {\n this.value = '';\n this.dsChange.emit('');\n this.dsClear.emit();\n this.inputEl?.focus();\n };\n\n private handleTogglePassword = () => {\n this.passwordRevealed = !this.passwordRevealed;\n this.restorePasswordFocus = true;\n };\n\n private numericStepDisabled(direction: 1 | -1): boolean {\n if (this.value.trim() === '') return false;\n const value = Number(this.value);\n if (!Number.isFinite(value)) return false;\n if (direction > 0 && this.max !== undefined) return value >= this.max;\n if (direction < 0 && this.min !== undefined) return value <= this.min;\n return false;\n }\n\n private handleNumericStep = (direction: 1 | -1) => {\n const input = this.inputEl;\n if (!input || input.disabled || input.readOnly || this.numericStepDisabled(direction)) return;\n\n const previous = input.value;\n if (direction > 0) input.stepUp();\n else input.stepDown();\n input.focus({ preventScroll: true });\n if (input.value === previous) return;\n\n this.value = input.value;\n this.dsChange.emit(this.value);\n };\n\n private renderNumericStepper(inactive: boolean) {\n const incrementDisabled = inactive || this.readOnly || this.numericStepDisabled(1);\n const decrementDisabled = inactive || this.readOnly || this.numericStepDisabled(-1);\n\n return (\n <span class=\"input-control__number-stepper\">\n <button\n type=\"button\"\n class=\"input-control__number-step input-control__number-step--increment ds-focus-ring-inset ds-interaction-fill\"\n disabled={incrementDisabled}\n tabIndex={-1}\n aria-hidden=\"true\"\n onClick={() => this.handleNumericStep(1)}\n >\n <ds-icon\n class=\"input-control__number-step-icon ds-interaction-fill__content\"\n name=\"ChevronUp\"\n size=\"xs\"\n color=\"inherit\"\n />\n </button>\n <button\n type=\"button\"\n class=\"input-control__number-step input-control__number-step--decrement ds-focus-ring-inset ds-interaction-fill\"\n disabled={decrementDisabled}\n tabIndex={-1}\n aria-hidden=\"true\"\n onClick={() => this.handleNumericStep(-1)}\n >\n <ds-icon\n class=\"input-control__number-step-icon ds-interaction-fill__content\"\n name=\"ChevronDown\"\n size=\"xs\"\n color=\"inherit\"\n />\n </button>\n </span>\n );\n }\n\n render() {\n const inputId = this.inputId ?? this.generatedId;\n const inactive = this.isInactive || this.disabled || this.formDisabled;\n const filled = this.value.length > 0;\n const dirty = this.value !== this.initialValue;\n const showClear = this.type === 'search' && filled && !inactive && !this.readOnly;\n const showPasswordToggle = this.type === 'password';\n const nativeType = showPasswordToggle && this.passwordRevealed ? 'text' : this.type;\n const showError = this.error && Boolean(this.errorMessage);\n const textVariant = CONTROL_TEXT_VARIANT[this.size];\n const iconSize = ICON_SIZE[this.size];\n const numeric = this.type === 'number';\n const resolvedAutoComplete = this.autoComplete ?? (this.type === 'search' ? 'off' : undefined);\n const suppressBrowserChrome = this.type === 'search' || this.type === 'password';\n\n const describedBy =\n [this.ariaDescribedby, showError ? this.errorId : undefined].filter(Boolean).join(' ') ||\n undefined;\n\n return (\n <Host\n class={{\n 'input-host': true,\n 'ds-field-stack': true,\n 'ds-field-stack--supporting-inset': !this.hasBorder,\n 'ds-control-inactive': inactive,\n [`ds-control--${this.size}`]: true,\n ...controlWidthClass(this.width),\n }}\n data-disabled={inactive ? '' : undefined}\n data-readonly={this.readOnly ? '' : undefined}\n data-required={this.required ? '' : undefined}\n data-invalid={this.error ? '' : undefined}\n data-filled={filled ? '' : undefined}\n data-focused={this.focused ? '' : undefined}\n data-dirty={dirty ? '' : undefined}\n data-touched={this.touched ? '' : undefined}\n >\n <div\n class={{\n 'input-control': true,\n 'input-control--number': numeric,\n 'input-control--align-start': this.textAlign === 'start',\n 'input-control--align-end': this.textAlign === 'end',\n 'ds-control-frame': true,\n 'input-control--bordered': this.hasBorder,\n 'input-control--error': this.hasBorder && this.error,\n 'input-control--prefix-control': this.hasPrefixControl,\n 'input-control--suffix-control': this.hasSuffixControl,\n 'ds-interaction-fill': this.hasInteractionFill,\n [`ds-control--${this.size}`]: true,\n }}\n >\n {numeric && this.textAlign === 'end' && this.renderNumericStepper(inactive)}\n {this.icon && (\n <span\n class=\"input-control__prefix ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name={this.icon} size={iconSize} color=\"inherit\" />\n </span>\n )}\n <span\n class={{\n 'input-control__prefix-text': true,\n 'ds-control-label-box': !this.hasPrefixControl,\n 'ds-interaction-fill__content': true,\n 'input-control__prefix-text--control': this.hasPrefixControl,\n 'input-control__prefix-text--empty': !this.hasPrefix,\n }}\n >\n <slot name=\"prefix\" onSlotchange={() => this.syncAdornmentSlots()} />\n {this.hasPrefixControl && (\n <ds-divider\n class=\"input-control__adornment-divider\"\n orientation=\"vertical\"\n length=\"var(--ds-control-icon)\"\n />\n )}\n </span>\n <input\n ref={element => {\n this.inputEl = element;\n }}\n type={nativeType}\n id={inputId}\n value={this.value}\n placeholder={this.placeholder}\n min={numeric ? this.min : undefined}\n max={numeric ? this.max : undefined}\n step={numeric ? this.step : undefined}\n disabled={inactive}\n readOnly={this.readOnly}\n required={this.required}\n autoFocus={this.autoFocus}\n autoComplete={resolvedAutoComplete}\n autoCapitalize={suppressBrowserChrome ? 'none' : undefined}\n autoCorrect={suppressBrowserChrome ? 'off' : undefined}\n spellcheck={suppressBrowserChrome ? false : undefined}\n inputMode={this.inputMode || undefined}\n enterKeyHint={this.enterKeyHint || undefined}\n class={`native-input native-input--align-${this.textAlign} ds-control-label-box ds-text--${textVariant.replace('text-', '')} ds-text--regular ds-interaction-fill__content`}\n aria-label={this.ariaLabel}\n aria-labelledby={this.ariaLabelledby}\n aria-describedby={describedBy}\n aria-controls={this.ariaControls}\n aria-activedescendant={this.ariaActiveDescendant}\n aria-invalid={this.error ? 'true' : undefined}\n onInput={this.handleInput}\n onFocus={this.handleFocus}\n onBlur={this.handleBlur}\n />\n <span\n class={{\n 'input-control__suffix': true,\n 'ds-control-label-box': !this.hasSuffixControl,\n 'input-control__suffix--control': this.hasSuffixControl,\n 'input-control__suffix--empty': !this.hasSuffix,\n }}\n >\n {this.hasSuffixControl && (\n <ds-divider\n class=\"input-control__adornment-divider\"\n orientation=\"vertical\"\n length=\"var(--ds-control-icon)\"\n />\n )}\n <slot name=\"suffix\" onSlotchange={() => this.syncAdornmentSlots()} />\n </span>\n {showClear && (\n <ds-button-unfilled\n class=\"input-control__clear\"\n variant=\"icon\"\n size={CLEAR_BUTTON_SIZE[this.size]}\n icon=\"CrossCircle\"\n hasBorder={false}\n rounded\n ariaLabel={this.clearLabel}\n onDsClick={this.handleClear}\n />\n )}\n {showPasswordToggle && (\n <ds-button-unfilled\n class=\"input-control__trailing-action\"\n variant=\"icon\"\n size={this.size}\n icon={this.passwordRevealed ? 'EyeDisabled' : 'Eye'}\n hasBorder={false}\n isInset\n isInactive={inactive}\n ariaLabel={this.passwordRevealed ? this.hidePasswordLabel : this.showPasswordLabel}\n onDsClick={this.handleTogglePassword}\n />\n )}\n {numeric && this.textAlign === 'start' && this.renderNumericStepper(inactive)}\n </div>\n {showError && (\n <ds-text\n class=\"error-text\"\n as=\"div\"\n variant=\"text-body-small\"\n color=\"negative\"\n textId={this.errorId}\n role=\"alert\"\n >\n {this.errorMessage}\n </ds-text>\n )}\n </Host>\n );\n }\n}\n",
724
772
  "type": "registry:ui"
725
773
  }
726
774
  ]
@@ -74,7 +74,8 @@
74
74
  "Use a swatch-picker section for a small curated visual preference and apply dsSwatchSelect immediately without closing the popup.",
75
75
  "Ordinary rows, section headings, and supporting text consume the Menu size. At xs, both the primary label and supporting text use caption metrics.",
76
76
  "A trailing tag resolves one density smaller than its menu row and uses the inset recipe so it nests without changing row height. Do not combine tag, dot, and switch suffixes on one item.",
77
- "Reorderable rows use the Drag prefix and ignore icon. Prefix and drag-handle icons inherit the choice-popup secondary foreground, matching Select. When any row in a section has an icon or is reorderable, the remaining rows get a spacer so labels align."
77
+ "Reorderable rows use the Drag prefix and ignore icon. Prefix and drag-handle icons inherit the choice-popup secondary foreground, matching Select. When any row in a section has an icon or is reorderable, the remaining rows get a spacer so labels align.",
78
+ "Embedded mode renders the same controlled content inside a parent-owned surface; the parent owns positioning, initial focus, Escape, Tab exit, and dismissal."
78
79
  ],
79
80
  "responsiveBehavior": [
80
81
  "Keep the default choice-cell anchor alignment so the first or last interactive row edge aligns with its trigger, matching Select; use popup-frame only for deliberate custom frame geometry.",
@@ -157,6 +158,15 @@
157
158
  "mutable": false,
158
159
  "description": "Explicit collision owner; otherwise the nearest data-ds-overlay-boundary ancestor is used."
159
160
  },
161
+ "embedded": {
162
+ "type": "boolean",
163
+ "resolvedType": "boolean",
164
+ "attribute": "embedded",
165
+ "default": "false",
166
+ "required": false,
167
+ "mutable": false,
168
+ "description": "Render content inside a parent-owned surface; the parent owns dismissal and positioning."
169
+ },
160
170
  "emptyMessage": {
161
171
  "type": "string",
162
172
  "resolvedType": "string",
@@ -349,6 +359,15 @@
349
359
  "mutable": false,
350
360
  "description": "Explicit collision owner; otherwise the nearest data-ds-overlay-boundary ancestor is used."
351
361
  },
362
+ "embedded": {
363
+ "type": "boolean",
364
+ "resolvedType": "boolean",
365
+ "attribute": "embedded",
366
+ "default": "false",
367
+ "required": false,
368
+ "mutable": false,
369
+ "description": "Render content inside a parent-owned surface; the parent owns dismissal and positioning."
370
+ },
352
371
  "emptyMessage": {
353
372
  "type": "string",
354
373
  "resolvedType": "string",
@@ -542,12 +561,12 @@
542
561
  "files": [
543
562
  {
544
563
  "path": "src/wc/components/Menu/Menu.css",
545
- "content": "@import '../../utils/radius.css';\n@import '../../utils/choice-list.css';\n@import '../../utils/visually-hidden.css';\n@import '../../utils/forced-colors.css';\n\n/* Inactive: `.ds-control-inactive` (50% opacity). */\n\n.menu-empty {\n padding: var(--dimension-space-200);\n box-sizing: border-box;\n overflow-wrap: anywhere;\n}\n\n.menu-item--destructive .menu-item__label {\n /* Beat ds-text color-* host classes for destructive rows. */\n color: var(--color-foreground-bold-negative) !important;\n}\n\n/* Standard md icon zone keeps the dot aligned with other control suffixes. */\n.menu-item__dot-box {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--ds-control-icon, var(--dimension-iconography-md));\n height: var(--ds-control-icon, var(--dimension-iconography-md));\n pointer-events: none;\n}\n\n/* Switch suffix keeps an 8px label-to-control gap without changing the\n density gap between a prefix icon or drag handle and the label. */\n.menu-item__switch {\n flex-shrink: 0;\n margin-inline-start: calc(\n var(--dimension-space-100) - var(--ds-control-gap, var(--dimension-space-050))\n );\n}\n\n/* ── Prefix icon / reorder handle ────────────────────────────────────────── */\n\n.menu-item > .menu-item__handle {\n pointer-events: auto;\n cursor: grab;\n touch-action: none;\n}\n\n.menu-item > .menu-item__handle:active,\n.menu-item--dragging > .menu-item__handle {\n cursor: grabbing;\n}\n\n.menu-item--dragging {\n opacity: var(--effect-opacity-medium);\n}\n\n.menu-item__drop-rail {\n position: absolute;\n z-index: var(--dimension-z-index-raised);\n inset-inline: var(--dimension-space-050);\n block-size: var(--dimension-stroke-width-025);\n border-radius: var(--dimension-radius-half);\n background: var(--color-border-bold-brand);\n pointer-events: none;\n}\n\n.menu-item__drop-rail--before {\n inset-block-start: calc(-1 * var(--dimension-space-025));\n transform: translateY(-50%);\n}\n\n.menu-item__drop-rail--after {\n inset-block-end: calc(-1 * var(--dimension-space-025));\n transform: translateY(50%);\n}\n\n@media (forced-colors: active) {\n .menu-item__drop-rail {\n background: var(--ds-forced-color-selected);\n }\n}\n",
564
+ "content": "@import '../../utils/radius.css';\n@import '../../utils/choice-list.css';\n@import '../../utils/visually-hidden.css';\n@import '../../utils/forced-colors.css';\n\n/* Inactive: `.ds-control-inactive` (50% opacity). */\n\n.menu-empty {\n padding: var(--dimension-space-200);\n box-sizing: border-box;\n overflow-wrap: anywhere;\n}\n\n.menu-item--destructive .menu-item__label {\n /* Beat ds-text color-* host classes for destructive rows. */\n color: var(--color-foreground-bold-negative) !important;\n}\n\n/* Standard md icon zone keeps the dot aligned with other control suffixes. */\n.menu-item__dot-box {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--ds-control-icon, var(--dimension-iconography-md));\n height: var(--ds-control-icon, var(--dimension-iconography-md));\n pointer-events: none;\n}\n\n/* Switch suffix keeps an 8px label-to-control gap without changing the\n density gap between a prefix icon or drag handle and the label. */\n.menu-item__switch {\n flex-shrink: 0;\n margin-inline-start: calc(\n var(--dimension-space-100) - var(--ds-control-gap, var(--dimension-space-050))\n );\n}\n\n/* ── Prefix icon / reorder handle ────────────────────────────────────────── */\n\n.menu-item > .menu-item__handle {\n pointer-events: auto;\n cursor: grab;\n touch-action: none;\n}\n\n.menu-item > .menu-item__handle:active,\n.menu-item--dragging > .menu-item__handle {\n cursor: grabbing;\n}\n\n.menu-item--dragging {\n opacity: var(--effect-opacity-medium);\n}\n\n.menu-item__drop-rail {\n position: absolute;\n z-index: var(--dimension-z-index-raised);\n inset-inline: var(--dimension-space-050);\n block-size: var(--dimension-stroke-width-025);\n border-radius: var(--dimension-radius-half);\n background: var(--color-border-bold-brand);\n pointer-events: none;\n}\n\n.menu-item__drop-rail--before {\n inset-block-start: calc(-1 * var(--dimension-space-025));\n transform: translateY(-50%);\n}\n\n.menu-item__drop-rail--after {\n inset-block-end: calc(-1 * var(--dimension-space-025));\n transform: translateY(50%);\n}\n\n@media (forced-colors: active) {\n .menu-item__drop-rail {\n background: var(--ds-forced-color-selected);\n }\n}\n\n.table-preferences-embedded {\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n color: var(--color-foreground-secondary);\n}\n",
546
565
  "type": "registry:ui"
547
566
  },
548
567
  {
549
568
  "path": "src/wc/components/Menu/Menu.tsx",
550
- "content": "import {\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Element,\n Watch,\n Listen,\n h,\n Host,\n} from '@stencil/core';\nimport {\n choicePopupMinWidth,\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n resolveChoicePopupAlignOffset,\n resolveCssLengthPx,\n resolveMotionTimeMs,\n TOKEN_CSS_LENGTHS,\n TOKEN_DEFAULTS,\n type ChoicePopupAnchorAlignment,\n type ControlSize,\n} from '../../utils';\nimport { AnchoredPositionController } from '../../utils/anchored-position-controller';\nimport { AnchoredOverlayInteractionController } from '../../utils/anchored-overlay-interaction-controller';\nimport { resolveAnchoredOverlayBoundaryRect } from '../../utils/anchored-overlay-boundary';\nimport { computeAnchoredPosition, type AnchoredPositionInput } from '../../utils/anchored-position';\nimport type { MenuAlign, MenuSide } from './menu-position';\nimport {\n isMenuPickerSection,\n isMenuSwatchPickerSection,\n type MenuItemData,\n type MenuReorderDetail,\n type MenuSection,\n} from './menu-types';\nimport { snapshotMenuSections } from './menu-sections';\nimport {\n createMenuReorderDetail,\n locateMenuItem,\n menuReorderableRange,\n moveReorderableMenuItemBefore,\n moveReorderableMenuItemBy,\n} from './menu-reorder';\n\nexport type MenuSelectionMode = 'none' | 'single';\nexport type MenuSize = ControlSize;\nlet menuId = 0;\nconst MENU_ITEM_TAG_SIZE: Record<MenuSize, 'md' | 'sm' | 'xs'> = {\n lg: 'md',\n md: 'sm',\n sm: 'xs',\n xs: 'xs',\n};\n\n@Component({\n tag: 'ds-menu',\n styleUrl: 'Menu.css',\n scoped: true,\n})\nexport class Menu {\n @Element() el!: HTMLElement;\n\n @Prop({ mutable: true }) open: boolean = false;\n @Prop() items: MenuItemData[] = [];\n /** Choice-row density. */\n @Prop() size: MenuSize = 'md';\n /** Give ordinary rows mutually-exclusive menu semantics using isSelected. */\n @Prop() selectionMode: MenuSelectionMode = 'none';\n @Prop() sections: MenuSection[] = [];\n /** Preferred side; placement flips to the opposite side when that offers a better fit. */\n @Prop() side: MenuSide = 'bottom';\n @Prop() align: MenuAlign = 'start';\n /** Align choice-row edges to the anchor by default; use `popup-frame` only for custom frame geometry. */\n @Prop() anchorAlignment: ChoicePopupAnchorAlignment = 'choice-cell';\n /** Gap between anchor and menu — number (px) or TokoMo length (`var(--dimension-space-050)`, etc.). */\n @Prop() sideOffset: number | string = TOKEN_CSS_LENGTHS.space050;\n /** Cross-axis offset — number (px) or TokoMo length. */\n @Prop() alignOffset: number | string = 0;\n @Prop() menuWidth: string | undefined;\n @Prop() minWidth: string | undefined;\n /** External trigger element to position against. Set via JS: menuEl.anchor = buttonEl */\n @Prop() anchor: HTMLElement | undefined;\n /** ID of the external trigger element for positioning */\n @Prop() anchorId: string | undefined;\n /** Explicit collision owner; otherwise the nearest data-ds-overlay-boundary ancestor is used. */\n @Prop() boundary: HTMLElement | undefined;\n /** Show a visible ring on the initially focused menu item. Use only when the opener was keyboard-driven. */\n @Prop() initialFocusVisible: boolean = false;\n /** Accessible name for the popup menu. */\n @Prop() menuLabel: string = 'Menu';\n /** Informational message shown when there are no choices. Uses a non-modal dialog, not a fake menu item. */\n @Prop() emptyMessage: string = '';\n\n @State() private shouldRender: boolean = false;\n @State() private closing: boolean = false;\n @State() private pos: { x: number; y: number } = { x: 0, y: 0 };\n @State() private focusedIndex: number = 0;\n @State() private positionReady: boolean = false;\n @State() private availableHeight: number | null = null;\n @State() private focusRingVisible: boolean = false;\n @State() private reorderFromFlat: number | null = null;\n @State() private reorderInsertBefore: number | null = null;\n @State() private reorderAnnouncement = '';\n\n @Event() dsClose!: EventEmitter<void>;\n /** Emitted after the popup's exit motion is complete and its rendered content is removed. */\n @Event() dsAfterClose!: EventEmitter<void>;\n @Event() dsSelect!: EventEmitter<MenuItemData>;\n /** Emitted when a generic `swatch-picker` section option is chosen. */\n @Event() dsSwatchSelect!: EventEmitter<string>;\n /** Emitted after a pointer drop or keyboard move; Menu never mutates item order. */\n @Event() dsReorder!: EventEmitter<MenuReorderDetail>;\n\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private positionReadyCallback: (() => void) | undefined;\n private suppressItemClick = false;\n private reorderPointer: {\n pointerId: number;\n sectionIndex: number;\n fromIndex: number;\n } | null = null;\n /**\n * Anchored placement. The choice-cell measurement stays here on purpose: the\n * inner-cell align offset and the `--dimension-menu-width-xs` floor are Menu\n * semantics, not shared geometry.\n */\n private readonly position = new AnchoredPositionController({\n getAnchor: () => this.resolvedAnchor ?? null,\n getPopup: () => this.el.querySelector<HTMLElement>('.menu-popup'),\n getOwnerDocument: () => this.el.ownerDocument,\n measure: (anchorEl, popup) => {\n if (!this.open) return null;\n\n const anchorRect = anchorEl.getBoundingClientRect();\n const sectionInsetPx = this.viewportPadPx;\n if (!this.minWidth) {\n if (\n this.anchorAlignment === 'choice-cell' &&\n (this.side === 'top' || this.side === 'bottom')\n ) {\n popup.style.minWidth = `max(var(--dimension-menu-width-xs), ${choicePopupMinWidth(\n anchorRect.width,\n sectionInsetPx\n )}px)`;\n } else {\n popup.style.removeProperty('min-width');\n }\n }\n\n const viewportPadPx = this.viewportPadPx;\n const list = popup.querySelector<HTMLElement>('.ds-choice-list');\n const naturalPopupHeight = Math.min(\n Math.max(popup.offsetHeight, list?.scrollHeight ?? 0),\n window.innerHeight - viewportPadPx * 2\n );\n const input: AnchoredPositionInput = {\n anchorRect,\n popupWidth: popup.offsetWidth || this.popupFallbackWidthPx,\n popupHeight: naturalPopupHeight || this.popupFallbackHeightPx,\n side: this.side,\n align: this.align,\n sideOffsetPx: this.sideOffsetPx,\n alignOffsetPx: resolveChoicePopupAlignOffset({\n align: this.align,\n alignOffsetPx: this.alignOffsetPx,\n sectionInsetPx,\n anchorAlignment: this.anchorAlignment,\n }),\n viewportPadPx,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n collisionRect: resolveAnchoredOverlayBoundaryRect(anchorEl, this.boundary),\n };\n const unconstrained = computeAnchoredPosition(input);\n return {\n ...input,\n popupHeight: Math.min(input.popupHeight, unconstrained.availableHeight),\n };\n },\n apply: ({ x, y, availableHeight }) => {\n this.pos = { x, y };\n this.availableHeight = availableHeight;\n },\n onReady: () => {\n this.positionReady = true;\n },\n liveUpdate: 'double-frame',\n topLayer: true,\n observeResize: true,\n observeAnchorMotion: true,\n });\n private readonly interaction = new AnchoredOverlayInteractionController({\n getAnchor: () => this.resolvedAnchor,\n getPopup: () => this.el.querySelector<HTMLElement>('.menu-popup'),\n getOwnerDocument: () => this.el.ownerDocument,\n onOutsideActivation: () => this.close(),\n });\n /** Last content actually painted while open; retained unchanged through exit motion. */\n private lastRenderedSections: MenuSection[] = [];\n private closingSections: MenuSection[] | null = null;\n private readonly emptyMessageId = `ds-menu-empty-${++menuId}`;\n private lastRenderedEmptyMessage = '';\n\n componentDidLoad() {\n if (this.open) this.onOpenChange(true);\n }\n\n disconnectedCallback() {\n this.cancelPositionRetry();\n this.cancelLivePositionUpdate();\n this.clearReorderPointer();\n this.teardownListeners();\n }\n\n @Watch('open')\n onOpenChange(isOpen: boolean) {\n if (isOpen) {\n this.teardownListeners();\n this.closingSections = null;\n this.shouldRender = true;\n this.closing = false;\n this.positionReady = false;\n this.availableHeight = null;\n this.focusRingVisible = this.initialFocusVisible;\n this.setupListeners();\n this.schedulePositionUpdate(() => {\n this.focusInitialItem();\n });\n } else if (this.shouldRender) {\n this.positionReadyCallback = undefined;\n this.cancelPositionRetry();\n this.captureClosingSections();\n this.closing = true;\n this.teardownListeners();\n const closeAnimationMs = this.closeAnimationMs;\n if (closeAnimationMs <= 0) {\n this.finishClose();\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.finishClose();\n }, closeAnimationMs);\n }\n }\n\n @Watch('anchor')\n @Watch('anchorId')\n @Watch('boundary')\n onAnchorChange() {\n if (this.open) this.schedulePositionUpdate();\n }\n\n @Watch('side')\n @Watch('align')\n @Watch('anchorAlignment')\n @Watch('sideOffset')\n @Watch('alignOffset')\n onPositionPropsChange() {\n if (this.open) this.schedulePositionUpdate();\n }\n\n private get viewportPadPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);\n }\n\n private get sideOffsetPx(): number {\n return resolveCssLengthPx(this.sideOffset, TOKEN_DEFAULTS.space050);\n }\n\n private get alignOffsetPx(): number {\n return resolveCssLengthPx(this.alignOffset, 0);\n }\n\n private get popupFallbackWidthPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.menuWidthXs, TOKEN_DEFAULTS.menuWidthXs);\n }\n\n private get popupFallbackHeightPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.menuFallbackHeight, TOKEN_DEFAULTS.menuFallbackHeight);\n }\n\n private get closeAnimationMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort2, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private finishClose() {\n const popup = this.el.querySelector<HTMLElement>('.menu-popup');\n if (popup?.matches(':popover-open')) popup.hidePopover();\n this.shouldRender = false;\n this.closing = false;\n this.lastRenderedSections = [];\n this.closingSections = null;\n this.clearReorderPointer();\n this.reorderAnnouncement = '';\n this.dsAfterClose.emit();\n }\n\n private captureClosingSections() {\n if (this.closingSections) return;\n const visibleSections = this.lastRenderedSections.length\n ? this.lastRenderedSections\n : this.activeSections;\n this.closingSections = snapshotMenuSections(visibleSections);\n }\n\n private get resolvedAnchor(): HTMLElement | null {\n if (this.anchor) return this.anchor;\n if (this.anchorId) return document.getElementById(this.anchorId);\n return null;\n }\n\n private compositeTabLeavesPopup(event: KeyboardEvent): boolean {\n return this.interaction.tabLeavesPopup(event);\n }\n\n private get activeSections(): MenuSection[] {\n if (this.sections.length > 0) return this.sections;\n if (this.items.length > 0) return [{ items: this.items }];\n return [];\n }\n\n private get flatItems(): MenuItemData[] {\n return this.activeSections.flatMap(section =>\n isMenuPickerSection(section) ? [] : section.items\n );\n }\n\n /** Rich preference content is a non-modal dialog, not an ARIA action menu. */\n private get hasCompositeSections(): boolean {\n return this.activeSections.some(isMenuPickerSection);\n }\n\n private cancelPositionRetry() {\n this.position.cancel();\n }\n\n private cancelLivePositionUpdate() {\n this.position.cancel();\n }\n\n /** Reposition after the current scroll/layout frame without hiding the open popup. */\n private scheduleLivePositionUpdate() {\n this.position.scheduleLiveUpdate();\n }\n\n /** Retry until anchor + popup exist — do not reveal at 0,0 on a failed first pass. */\n private schedulePositionUpdate(onReady?: () => void) {\n if (!this.open) return;\n this.positionReady = false;\n // Opening often sets `open` and `anchorId` together. Each Watch reschedules\n // placement; keep the open-path ready callback so initial focus is not dropped.\n if (onReady) this.positionReadyCallback = onReady;\n const callback = this.positionReadyCallback;\n this.position.schedule(() => {\n if (this.positionReadyCallback === callback) {\n this.positionReadyCallback = undefined;\n }\n callback?.();\n });\n }\n\n /** @returns `true` when anchor and popup were found and `pos` was updated. */\n private calculatePosition(): boolean {\n return this.position.update();\n }\n\n /** Focus the selected item when present, otherwise the first enabled item. */\n private focusInitialItem() {\n if (!this.hasCompositeSections && !this.flatItems.length && this.emptyMessage.trim()) {\n requestAnimationFrame(() => {\n if (this.open) this.el.querySelector<HTMLElement>('.menu-popup')?.focus();\n });\n return;\n }\n if (this.hasCompositeSections) {\n requestAnimationFrame(() => {\n this.el.querySelector<HTMLElement>('.menu-popup [tabindex=\"0\"]')?.focus();\n });\n return;\n }\n\n const flat = this.flatItems;\n const selectedIdx = flat.findIndex(it => it.isSelected && !it.isInactive);\n const firstEnabledIdx = flat.findIndex(it => !it.isInactive || !!it.reorderable);\n this.focusedIndex = selectedIdx >= 0 ? selectedIdx : firstEnabledIdx >= 0 ? firstEnabledIdx : 0;\n\n requestAnimationFrame(() => {\n const btns = this.el.querySelectorAll<HTMLElement>('.menu-item');\n btns[this.focusedIndex]?.focus();\n });\n }\n\n private setupListeners() {\n this.interaction.connect();\n this.position.observe();\n }\n\n private teardownListeners() {\n this.position.unobserve();\n this.interaction.disconnect();\n if (this.closeTimer) {\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n }\n\n private close(restoreFocus = true) {\n this.captureClosingSections();\n if (restoreFocus) this.interaction.focusAnchor();\n this.dsClose.emit();\n this.open = false;\n this.onOpenChange(false);\n }\n\n @Listen('keydown')\n handleKeyDown(e: KeyboardEvent) {\n if (!this.shouldRender || this.closing) return;\n\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n\n if (e.key === 'Tab') {\n if (this.hasCompositeSections && !this.compositeTabLeavesPopup(e)) return;\n e.preventDefault();\n this.close(false);\n this.interaction.moveFocusAfterTab(e.shiftKey);\n return;\n }\n\n if (this.hasCompositeSections) return;\n\n if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && e.altKey && !e.ctrlKey && !e.shiftKey) {\n this.handleReorderKey(e);\n return;\n }\n\n const flat = this.flatItems;\n const enabled = flat\n .map((it, i) => ({ it, i }))\n .filter(({ it }) => !it.isInactive || !!it.reorderable)\n .map(({ i }) => i);\n if (!enabled.length) return;\n\n const cur = enabled.indexOf(this.focusedIndex);\n const safe = cur < 0 ? 0 : cur;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[(safe + 1) % enabled.length];\n this.focusItem(this.focusedIndex);\n break;\n case 'ArrowUp':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[(safe - 1 + enabled.length) % enabled.length];\n this.focusItem(this.focusedIndex);\n break;\n case 'Home':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[0];\n this.focusItem(this.focusedIndex);\n break;\n case 'End':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[enabled.length - 1];\n this.focusItem(this.focusedIndex);\n break;\n }\n }\n\n private focusItem(idx: number) {\n const btns = this.el.querySelectorAll<HTMLElement>('.menu-item');\n btns[idx]?.focus();\n }\n\n private handleItemClick(item: MenuItemData) {\n if (this.suppressItemClick) {\n this.suppressItemClick = false;\n return;\n }\n if (item.isInactive) return;\n // Consumers commonly change the item source while handling selection.\n // Capture first so the exit animation never paints the next menu context.\n this.captureClosingSections();\n this.dsSelect.emit(item);\n }\n\n private handleSwatchSelect(value: string) {\n this.dsSwatchSelect.emit(value);\n }\n\n private handleReorderKey(event: KeyboardEvent): void {\n const located = locateMenuItem(this.activeSections, this.focusedIndex);\n if (!located || !located.items[located.itemIndex]?.reorderable) return;\n\n const nextItems = moveReorderableMenuItemBy(\n located.items,\n located.itemIndex,\n event.key === 'ArrowUp' ? -1 : 1\n );\n if (!nextItems) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.emitReorder(located.items, located.itemIndex, nextItems, located.sectionIndex);\n }\n\n private emitReorder(\n items: MenuItemData[],\n fromIndex: number,\n nextItems: MenuItemData[],\n sectionIndex: number\n ): void {\n const detail = createMenuReorderDetail(items, fromIndex, nextItems, sectionIndex);\n if (!detail) return;\n this.focusedIndex = this.flatIndexFor(sectionIndex, detail.toIndex);\n this.announceReorder(detail);\n this.dsReorder.emit(detail);\n }\n\n private flatIndexFor(sectionIndex: number, itemIndex: number): number {\n let flat = 0;\n for (let index = 0; index < this.activeSections.length; index += 1) {\n const section = this.activeSections[index];\n if (isMenuPickerSection(section)) continue;\n if (index === sectionIndex) return flat + itemIndex;\n flat += section.items.length;\n }\n return itemIndex;\n }\n\n private announceReorder(detail: MenuReorderDetail): void {\n const range = menuReorderableRange(detail.items, detail.toIndex);\n const count = range ? range.end - range.start + 1 : detail.items.length;\n const position = range ? detail.toIndex - range.start + 1 : detail.toIndex + 1;\n this.reorderAnnouncement = `${detail.item.label} moved to position ${position} of ${count}`;\n }\n\n private onReorderPointerDown(\n event: PointerEvent,\n sectionIndex: number,\n itemIndex: number,\n flatIndex: number\n ): void {\n if (event.button !== 0) return;\n const section = this.activeSections[sectionIndex];\n if (!section || isMenuPickerSection(section)) return;\n if (!section.items[itemIndex]?.reorderable) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.suppressItemClick = true;\n this.focusRingVisible = false;\n this.focusedIndex = flatIndex;\n this.focusItem(flatIndex);\n this.reorderPointer = {\n pointerId: event.pointerId,\n sectionIndex,\n fromIndex: itemIndex,\n };\n this.reorderFromFlat = flatIndex;\n this.reorderInsertBefore = itemIndex;\n window.addEventListener('pointermove', this.onReorderPointerMove);\n window.addEventListener('pointerup', this.onReorderPointerUp);\n window.addEventListener('pointercancel', this.onReorderPointerCancel);\n }\n\n private onReorderPointerMove = (event: PointerEvent) => {\n const drag = this.reorderPointer;\n if (!drag || event.pointerId !== drag.pointerId) return;\n const section = this.activeSections[drag.sectionIndex];\n if (!section || isMenuPickerSection(section)) return;\n\n const insertBefore = this.resolveReorderInsertBefore(\n event.clientY,\n drag.sectionIndex,\n drag.fromIndex,\n section.items\n );\n if (insertBefore !== this.reorderInsertBefore) this.reorderInsertBefore = insertBefore;\n };\n\n private onReorderPointerUp = (event: PointerEvent) => {\n const drag = this.reorderPointer;\n if (!drag || event.pointerId !== drag.pointerId) return;\n\n const section = this.activeSections[drag.sectionIndex];\n const insertBefore = this.reorderInsertBefore;\n this.clearReorderPointer();\n queueMicrotask(() => {\n this.suppressItemClick = false;\n });\n if (!section || isMenuPickerSection(section) || insertBefore === null) return;\n\n const nextItems = moveReorderableMenuItemBefore(section.items, drag.fromIndex, insertBefore);\n if (!nextItems) return;\n this.emitReorder(section.items, drag.fromIndex, nextItems, drag.sectionIndex);\n };\n\n private onReorderPointerCancel = (event: PointerEvent) => {\n if (!this.reorderPointer || event.pointerId !== this.reorderPointer.pointerId) return;\n this.clearReorderPointer();\n queueMicrotask(() => {\n this.suppressItemClick = false;\n });\n };\n\n private resolveReorderInsertBefore(\n clientY: number,\n sectionIndex: number,\n fromIndex: number,\n items: MenuItemData[]\n ): number {\n const range = menuReorderableRange(items, fromIndex);\n if (!range) return fromIndex;\n\n const buttons = [...this.el.querySelectorAll<HTMLElement>('.menu-item')];\n const sectionStart = this.flatIndexFor(sectionIndex, 0);\n let insertBefore = range.end + 1;\n for (let itemIndex = range.start; itemIndex <= range.end; itemIndex += 1) {\n const row = buttons[sectionStart + itemIndex];\n if (!row) continue;\n const rect = row.getBoundingClientRect();\n if (clientY < rect.top + rect.height / 2) {\n insertBefore = itemIndex;\n break;\n }\n }\n return Math.min(Math.max(insertBefore, range.start), range.end + 1);\n }\n\n private clearReorderPointer(): void {\n if (this.reorderPointer) {\n window.removeEventListener('pointermove', this.onReorderPointerMove);\n window.removeEventListener('pointerup', this.onReorderPointerUp);\n window.removeEventListener('pointercancel', this.onReorderPointerCancel);\n }\n this.reorderPointer = null;\n this.reorderFromFlat = null;\n this.reorderInsertBefore = null;\n }\n\n render() {\n if (!this.shouldRender) return <Host style={{ display: 'contents' }} />;\n\n const sections = this.closing\n ? (this.closingSections ?? this.lastRenderedSections)\n : this.activeSections;\n const hasCompositeSections = sections.some(isMenuPickerSection);\n const emptyMessage = this.closing ? this.lastRenderedEmptyMessage : this.emptyMessage.trim();\n const showEmpty =\n Boolean(emptyMessage) &&\n !hasCompositeSections &&\n sections.every(section => !isMenuPickerSection(section) && section.items.length === 0);\n if (!this.closing) {\n this.lastRenderedEmptyMessage = emptyMessage;\n this.lastRenderedSections = snapshotMenuSections(sections);\n this.closingSections = null;\n }\n let flatIdx = 0;\n const dragLocated =\n this.reorderFromFlat === null ? null : locateMenuItem(sections, this.reorderFromFlat);\n const dragRange = dragLocated\n ? menuReorderableRange(dragLocated.items, dragLocated.itemIndex)\n : null;\n\n const popupStyle: Record<string, string> = {\n position: 'fixed',\n left: '0',\n top: '0',\n transform: `translate(${Math.round(this.pos.x)}px, ${Math.round(this.pos.y)}px)`,\n zIndex: '9998',\n visibility: this.positionReady ? 'visible' : 'hidden',\n };\n\n if (this.menuWidth) popupStyle['width'] = this.menuWidth;\n if (this.minWidth) popupStyle['min-width'] = this.minWidth;\n if (this.availableHeight !== null) {\n popupStyle['max-height'] = `${Math.floor(this.availableHeight)}px`;\n }\n\n return (\n <Host style={{ display: 'contents' }}>\n <div\n popover=\"manual\"\n class={{\n 'menu-popup': true,\n 'menu-popup--closing': this.closing,\n 'ds-choice-popup': true,\n 'ds-choice-popup--closing': this.closing,\n 'ds-focus-ring': showEmpty,\n 'ds-focus-ring--visible': showEmpty && this.focusRingVisible,\n }}\n style={popupStyle}\n role={hasCompositeSections || showEmpty ? 'dialog' : 'menu'}\n aria-label={this.menuLabel}\n aria-orientation={hasCompositeSections || showEmpty ? undefined : 'vertical'}\n aria-describedby={showEmpty ? this.emptyMessageId : undefined}\n tabIndex={showEmpty ? -1 : undefined}\n >\n <div class=\"ds-choice-list\">\n {showEmpty ? (\n <ds-text\n textId={this.emptyMessageId}\n class=\"menu-empty\"\n as=\"p\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n color=\"secondary\"\n >\n {emptyMessage}\n </ds-text>\n ) : (\n sections.map((section, si) => (\n <div\n key={si}\n class={{\n 'menu-section': true,\n 'menu-section--divided': si < sections.length - 1,\n 'menu-section--swatch-picker': isMenuPickerSection(section),\n 'ds-choice-section': true,\n 'ds-chrome-column': true,\n 'ds-chrome-space--sm': true,\n 'ds-choice-section--divided': si < sections.length - 1,\n 'ds-choice-section--headed-after-first': si > 0 && Boolean(section.header),\n }}\n role={section.header ? 'group' : undefined}\n aria-label={section.header}\n >\n {section.header && (\n <ds-text\n class={`section-header ds-choice-section__header ds-control-section-heading ds-control--${this.size}`}\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n emphasis\n color=\"primary\"\n aria-hidden=\"true\"\n >\n <span class=\"ds-choice-section__header-label\">{section.header}</span>\n </ds-text>\n )}\n {isMenuSwatchPickerSection(section) ? (\n <ds-swatch-picker\n value={section.value}\n groupLabel={section.groupLabel ?? section.header ?? 'Swatch options'}\n options={section.options ?? []}\n sections={section.sections ?? []}\n onDsChange={(e: CustomEvent<string>) => {\n e.stopPropagation();\n this.handleSwatchSelect(e.detail);\n }}\n />\n ) : (\n section.items.map((item, itemIndex) => {\n const idx = flatIdx++;\n const isFocused = this.focusedIndex === idx;\n const isSingleSelectionItem =\n !hasCompositeSections &&\n this.selectionMode === 'single' &&\n !item.showSwitch;\n const usesLeading = section.items.some(\n candidate => candidate.reorderable || !!candidate.icon\n );\n const locked = !!item.isInactive && !item.reorderable;\n const dragging = this.reorderFromFlat === idx;\n const dropBefore =\n dragLocated?.sectionIndex === si &&\n dragRange !== null &&\n itemIndex <= dragRange.end &&\n this.reorderInsertBefore === itemIndex;\n const dropAfter =\n dragLocated?.sectionIndex === si &&\n dragRange !== null &&\n this.reorderInsertBefore === dragRange.end + 1 &&\n itemIndex === dragRange.end;\n return (\n <button\n key={item.value ?? `${si}-${item.label}`}\n type=\"button\"\n class={{\n 'menu-item': true,\n 'ds-choice-item': true,\n 'ds-control-frame': true,\n [`ds-control--${this.size}`]: true,\n 'ds-focus-ring-inset': true,\n 'ds-focus-ring--visible': isFocused && this.focusRingVisible,\n 'ds-interaction-fill': !locked,\n 'ds-interaction-fill--selected': !!item.isSelected && !locked,\n 'menu-item--selected': !!item.isSelected,\n 'menu-item--switch': !!item.showSwitch,\n 'ds-control-inactive': locked,\n 'menu-item--destructive': !!item.isDestructive,\n 'menu-item--focused': isFocused,\n 'menu-item--dragging': dragging,\n }}\n role={\n hasCompositeSections\n ? undefined\n : item.showSwitch\n ? 'menuitemcheckbox'\n : isSingleSelectionItem\n ? 'menuitemradio'\n : 'menuitem'\n }\n aria-checked={\n !hasCompositeSections && item.showSwitch\n ? String(!!item.switchValue)\n : isSingleSelectionItem\n ? String(!!item.isSelected)\n : undefined\n }\n aria-pressed={\n hasCompositeSections\n ? String(item.showSwitch ? !!item.switchValue : !!item.isSelected)\n : undefined\n }\n aria-current={\n !hasCompositeSections &&\n !item.showSwitch &&\n !isSingleSelectionItem &&\n item.isSelected\n ? 'true'\n : undefined\n }\n aria-description={\n item.reorderable\n ? item.isInactive\n ? 'Visibility cannot be changed. Alt + Arrow Up or Alt + Arrow Down moves this row.'\n : 'Drag to reorder. Alt + Arrow Up or Alt + Arrow Down moves this row.'\n : undefined\n }\n aria-disabled={item.isInactive ? 'true' : undefined}\n disabled={locked}\n tabIndex={hasCompositeSections ? 0 : isFocused ? 0 : -1}\n onMouseDown={() => {\n this.focusRingVisible = false;\n }}\n onClick={() => this.handleItemClick(item)}\n onFocus={() => {\n this.focusedIndex = idx;\n }}\n >\n {(dropBefore || dropAfter) && (\n <span\n class={{\n 'menu-item__drop-rail': true,\n 'menu-item__drop-rail--before': dropBefore,\n 'menu-item__drop-rail--after': dropAfter,\n }}\n data-menu-drop-rail\n aria-hidden=\"true\"\n />\n )}\n {item.reorderable ? (\n <span\n class=\"menu-item__handle ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n data-menu-handle\n aria-hidden=\"true\"\n onPointerDown={event =>\n this.onReorderPointerDown(event, si, itemIndex, idx)\n }\n onClick={event => {\n event.preventDefault();\n event.stopPropagation();\n }}\n >\n <ds-icon name=\"Drag\" size={this.size} color=\"inherit\" />\n </span>\n ) : usesLeading && item.icon ? (\n <span\n class=\"menu-item__icon ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name={item.icon} size={this.size} color=\"inherit\" />\n </span>\n ) : usesLeading ? (\n <span\n class=\"menu-item__icon-spacer ds-choice-item__icon ds-control-icon-box\"\n aria-hidden=\"true\"\n />\n ) : null}\n <div class=\"menu-item__content ds-choice-item__content ds-interaction-fill__content\">\n <ds-text\n class=\"menu-item__label ds-choice-item__label ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n color={item.isSelected ? 'primary' : 'secondary'}\n >\n {item.label}\n </ds-text>\n {item.subtext && (\n <ds-text\n class=\"menu-item__subtext ds-choice-item__subtext ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"secondary\"\n >\n {item.subtext}\n </ds-text>\n )}\n </div>\n {item.tag && (\n <ds-tag\n class=\"ds-choice-item__tag ds-interaction-fill__content\"\n label={item.tag.label}\n size={MENU_ITEM_TAG_SIZE[this.size]}\n intent={item.tag.intent ?? 'neutral'}\n contrast={item.tag.contrast ?? 'faint'}\n rounded={item.tag.rounded ?? false}\n isInset\n />\n )}\n {item.dot && (\n <span\n class=\"menu-item__dot-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-badge\n class=\"menu-item__dot\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n />\n </span>\n )}\n {item.showSwitch && (\n <ds-switch\n class=\"menu-item__switch ds-interaction-fill__content\"\n size={this.size}\n checked={!!item.switchValue}\n isInactive={!!item.isInactive}\n presentation\n />\n )}\n </button>\n );\n })\n )}\n </div>\n ))\n )}\n </div>\n </div>\n <div class=\"ds-visually-hidden\" role=\"status\" aria-live=\"polite\">\n {this.reorderAnnouncement}\n </div>\n </Host>\n );\n }\n}\n",
569
+ "content": "import {\n Component,\n Prop,\n State,\n Event,\n EventEmitter,\n Element,\n Watch,\n Listen,\n h,\n Host,\n} from '@stencil/core';\nimport {\n choicePopupMinWidth,\n CONTROL_SUPPORTING_TEXT_VARIANT,\n CONTROL_TEXT_VARIANT,\n resolveChoicePopupAlignOffset,\n resolveCssLengthPx,\n resolveMotionTimeMs,\n TOKEN_CSS_LENGTHS,\n TOKEN_DEFAULTS,\n type ChoicePopupAnchorAlignment,\n type ControlSize,\n} from '../../utils';\nimport { AnchoredPositionController } from '../../utils/anchored-position-controller';\nimport { AnchoredOverlayInteractionController } from '../../utils/anchored-overlay-interaction-controller';\nimport { resolveAnchoredOverlayBoundaryRect } from '../../utils/anchored-overlay-boundary';\nimport { computeAnchoredPosition, type AnchoredPositionInput } from '../../utils/anchored-position';\nimport type { MenuAlign, MenuSide } from './menu-position';\nimport {\n isMenuPickerSection,\n isMenuSwatchPickerSection,\n type MenuItemData,\n type MenuReorderDetail,\n type MenuSection,\n} from './menu-types';\nimport { snapshotMenuSections } from './menu-sections';\nimport {\n createMenuReorderDetail,\n locateMenuItem,\n menuReorderableRange,\n moveReorderableMenuItemBefore,\n moveReorderableMenuItemBy,\n} from './menu-reorder';\n\nexport type MenuSelectionMode = 'none' | 'single';\nexport type MenuSize = ControlSize;\nlet menuId = 0;\nconst MENU_ITEM_TAG_SIZE: Record<MenuSize, 'md' | 'sm' | 'xs'> = {\n lg: 'md',\n md: 'sm',\n sm: 'xs',\n xs: 'xs',\n};\n\n@Component({\n tag: 'ds-menu',\n styleUrl: 'Menu.css',\n scoped: true,\n})\nexport class Menu {\n @Element() el!: HTMLElement;\n\n /** Render content inside a parent-owned surface; the parent owns dismissal and positioning. */\n @Prop() embedded: boolean = false;\n @Prop({ mutable: true }) open: boolean = false;\n @Prop() items: MenuItemData[] = [];\n /** Choice-row density. */\n @Prop() size: MenuSize = 'md';\n /** Give ordinary rows mutually-exclusive menu semantics using isSelected. */\n @Prop() selectionMode: MenuSelectionMode = 'none';\n @Prop() sections: MenuSection[] = [];\n /** Preferred side; placement flips to the opposite side when that offers a better fit. */\n @Prop() side: MenuSide = 'bottom';\n @Prop() align: MenuAlign = 'start';\n /** Align choice-row edges to the anchor by default; use `popup-frame` only for custom frame geometry. */\n @Prop() anchorAlignment: ChoicePopupAnchorAlignment = 'choice-cell';\n /** Gap between anchor and menu — number (px) or TokoMo length (`var(--dimension-space-050)`, etc.). */\n @Prop() sideOffset: number | string = TOKEN_CSS_LENGTHS.space050;\n /** Cross-axis offset — number (px) or TokoMo length. */\n @Prop() alignOffset: number | string = 0;\n @Prop() menuWidth: string | undefined;\n @Prop() minWidth: string | undefined;\n /** External trigger element to position against. Set via JS: menuEl.anchor = buttonEl */\n @Prop() anchor: HTMLElement | undefined;\n /** ID of the external trigger element for positioning */\n @Prop() anchorId: string | undefined;\n /** Explicit collision owner; otherwise the nearest data-ds-overlay-boundary ancestor is used. */\n @Prop() boundary: HTMLElement | undefined;\n /** Show a visible ring on the initially focused menu item. Use only when the opener was keyboard-driven. */\n @Prop() initialFocusVisible: boolean = false;\n /** Accessible name for the popup menu. */\n @Prop() menuLabel: string = 'Menu';\n /** Informational message shown when there are no choices. Uses a non-modal dialog, not a fake menu item. */\n @Prop() emptyMessage: string = '';\n\n @State() private shouldRender: boolean = false;\n @State() private closing: boolean = false;\n @State() private pos: { x: number; y: number } = { x: 0, y: 0 };\n @State() private focusedIndex: number = 0;\n @State() private positionReady: boolean = false;\n @State() private availableHeight: number | null = null;\n @State() private focusRingVisible: boolean = false;\n @State() private reorderFromFlat: number | null = null;\n @State() private reorderInsertBefore: number | null = null;\n @State() private reorderAnnouncement = '';\n\n @Event() dsClose!: EventEmitter<void>;\n /** Emitted after the popup's exit motion is complete and its rendered content is removed. */\n @Event() dsAfterClose!: EventEmitter<void>;\n @Event() dsSelect!: EventEmitter<MenuItemData>;\n /** Emitted when a generic `swatch-picker` section option is chosen. */\n @Event() dsSwatchSelect!: EventEmitter<string>;\n /** Emitted after a pointer drop or keyboard move; Menu never mutates item order. */\n @Event() dsReorder!: EventEmitter<MenuReorderDetail>;\n\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private positionReadyCallback: (() => void) | undefined;\n private suppressItemClick = false;\n private reorderPointer: {\n pointerId: number;\n sectionIndex: number;\n fromIndex: number;\n } | null = null;\n /**\n * Anchored placement. The choice-cell measurement stays here on purpose: the\n * inner-cell align offset and the `--dimension-menu-width-xs` floor are Menu\n * semantics, not shared geometry.\n */\n private readonly position = new AnchoredPositionController({\n getAnchor: () => this.resolvedAnchor ?? null,\n getPopup: () => this.el.querySelector<HTMLElement>('.menu-popup'),\n getOwnerDocument: () => this.el.ownerDocument,\n measure: (anchorEl, popup) => {\n if (!this.open) return null;\n\n const anchorRect = anchorEl.getBoundingClientRect();\n const sectionInsetPx = this.viewportPadPx;\n if (!this.minWidth) {\n if (\n this.anchorAlignment === 'choice-cell' &&\n (this.side === 'top' || this.side === 'bottom')\n ) {\n popup.style.minWidth = `max(var(--dimension-menu-width-xs), ${choicePopupMinWidth(\n anchorRect.width,\n sectionInsetPx\n )}px)`;\n } else {\n popup.style.removeProperty('min-width');\n }\n }\n\n const viewportPadPx = this.viewportPadPx;\n const list = popup.querySelector<HTMLElement>('.ds-choice-list');\n const naturalPopupHeight = Math.min(\n Math.max(popup.offsetHeight, list?.scrollHeight ?? 0),\n window.innerHeight - viewportPadPx * 2\n );\n const input: AnchoredPositionInput = {\n anchorRect,\n popupWidth: popup.offsetWidth || this.popupFallbackWidthPx,\n popupHeight: naturalPopupHeight || this.popupFallbackHeightPx,\n side: this.side,\n align: this.align,\n sideOffsetPx: this.sideOffsetPx,\n alignOffsetPx: resolveChoicePopupAlignOffset({\n align: this.align,\n alignOffsetPx: this.alignOffsetPx,\n sectionInsetPx,\n anchorAlignment: this.anchorAlignment,\n }),\n viewportPadPx,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n collisionRect: resolveAnchoredOverlayBoundaryRect(anchorEl, this.boundary),\n };\n const unconstrained = computeAnchoredPosition(input);\n return {\n ...input,\n popupHeight: Math.min(input.popupHeight, unconstrained.availableHeight),\n };\n },\n apply: ({ x, y, availableHeight }) => {\n this.pos = { x, y };\n this.availableHeight = availableHeight;\n },\n onReady: () => {\n this.positionReady = true;\n },\n liveUpdate: 'double-frame',\n topLayer: true,\n observeResize: true,\n observeAnchorMotion: true,\n });\n private readonly interaction = new AnchoredOverlayInteractionController({\n getAnchor: () => this.resolvedAnchor,\n getPopup: () => this.el.querySelector<HTMLElement>('.menu-popup'),\n getOwnerDocument: () => this.el.ownerDocument,\n onOutsideActivation: () => this.close(),\n });\n /** Last content actually painted while open; retained unchanged through exit motion. */\n private lastRenderedSections: MenuSection[] = [];\n private closingSections: MenuSection[] | null = null;\n private readonly emptyMessageId = `ds-menu-empty-${++menuId}`;\n private lastRenderedEmptyMessage = '';\n\n componentDidLoad() {\n if (this.open) this.onOpenChange(true);\n }\n\n disconnectedCallback() {\n this.cancelPositionRetry();\n this.cancelLivePositionUpdate();\n this.clearReorderPointer();\n this.teardownListeners();\n }\n\n @Watch('open')\n onOpenChange(isOpen: boolean) {\n if (this.embedded) return;\n if (isOpen) {\n this.teardownListeners();\n this.closingSections = null;\n this.shouldRender = true;\n this.closing = false;\n this.positionReady = false;\n this.availableHeight = null;\n this.focusRingVisible = this.initialFocusVisible;\n this.setupListeners();\n this.schedulePositionUpdate(() => {\n this.focusInitialItem();\n });\n } else if (this.shouldRender) {\n this.positionReadyCallback = undefined;\n this.cancelPositionRetry();\n this.captureClosingSections();\n this.closing = true;\n this.teardownListeners();\n const closeAnimationMs = this.closeAnimationMs;\n if (closeAnimationMs <= 0) {\n this.finishClose();\n return;\n }\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.finishClose();\n }, closeAnimationMs);\n }\n }\n\n @Watch('anchor')\n @Watch('anchorId')\n @Watch('boundary')\n onAnchorChange() {\n if (this.open) this.schedulePositionUpdate();\n }\n\n @Watch('side')\n @Watch('align')\n @Watch('anchorAlignment')\n @Watch('sideOffset')\n @Watch('alignOffset')\n onPositionPropsChange() {\n if (this.open) this.schedulePositionUpdate();\n }\n\n private get viewportPadPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);\n }\n\n private get sideOffsetPx(): number {\n return resolveCssLengthPx(this.sideOffset, TOKEN_DEFAULTS.space050);\n }\n\n private get alignOffsetPx(): number {\n return resolveCssLengthPx(this.alignOffset, 0);\n }\n\n private get popupFallbackWidthPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.menuWidthXs, TOKEN_DEFAULTS.menuWidthXs);\n }\n\n private get popupFallbackHeightPx(): number {\n return resolveCssLengthPx(TOKEN_DEFAULTS.menuFallbackHeight, TOKEN_DEFAULTS.menuFallbackHeight);\n }\n\n private get closeAnimationMs(): number {\n return resolveMotionTimeMs(TOKEN_DEFAULTS.motionShort2, TOKEN_DEFAULTS.animationDurationShort3);\n }\n\n private finishClose() {\n const popup = this.el.querySelector<HTMLElement>('.menu-popup');\n if (popup?.matches(':popover-open')) popup.hidePopover();\n this.shouldRender = false;\n this.closing = false;\n this.lastRenderedSections = [];\n this.closingSections = null;\n this.clearReorderPointer();\n this.reorderAnnouncement = '';\n this.dsAfterClose.emit();\n }\n\n private captureClosingSections() {\n if (this.closingSections) return;\n const visibleSections = this.lastRenderedSections.length\n ? this.lastRenderedSections\n : this.activeSections;\n this.closingSections = snapshotMenuSections(visibleSections);\n }\n\n private get resolvedAnchor(): HTMLElement | null {\n if (this.anchor) return this.anchor;\n if (this.anchorId) return document.getElementById(this.anchorId);\n return null;\n }\n\n private compositeTabLeavesPopup(event: KeyboardEvent): boolean {\n return this.interaction.tabLeavesPopup(event);\n }\n\n private get activeSections(): MenuSection[] {\n if (this.sections.length > 0) return this.sections;\n if (this.items.length > 0) return [{ items: this.items }];\n return [];\n }\n\n private get flatItems(): MenuItemData[] {\n return this.activeSections.flatMap(section =>\n isMenuPickerSection(section) ? [] : section.items\n );\n }\n\n /** Rich preference content is a non-modal dialog, not an ARIA action menu. */\n private get hasCompositeSections(): boolean {\n return this.activeSections.some(isMenuPickerSection);\n }\n\n private cancelPositionRetry() {\n this.position.cancel();\n }\n\n private cancelLivePositionUpdate() {\n this.position.cancel();\n }\n\n /** Reposition after the current scroll/layout frame without hiding the open popup. */\n private scheduleLivePositionUpdate() {\n this.position.scheduleLiveUpdate();\n }\n\n /** Retry until anchor + popup exist — do not reveal at 0,0 on a failed first pass. */\n private schedulePositionUpdate(onReady?: () => void) {\n if (!this.open) return;\n this.positionReady = false;\n // Opening often sets `open` and `anchorId` together. Each Watch reschedules\n // placement; keep the open-path ready callback so initial focus is not dropped.\n if (onReady) this.positionReadyCallback = onReady;\n const callback = this.positionReadyCallback;\n this.position.schedule(() => {\n if (this.positionReadyCallback === callback) {\n this.positionReadyCallback = undefined;\n }\n callback?.();\n });\n }\n\n /** @returns `true` when anchor and popup were found and `pos` was updated. */\n private calculatePosition(): boolean {\n return this.position.update();\n }\n\n /** Focus the selected item when present, otherwise the first enabled item. */\n private focusInitialItem() {\n if (!this.hasCompositeSections && !this.flatItems.length && this.emptyMessage.trim()) {\n requestAnimationFrame(() => {\n if (this.open) this.el.querySelector<HTMLElement>('.menu-popup')?.focus();\n });\n return;\n }\n if (this.hasCompositeSections) {\n requestAnimationFrame(() => {\n this.el.querySelector<HTMLElement>('.menu-popup [tabindex=\"0\"]')?.focus();\n });\n return;\n }\n\n const flat = this.flatItems;\n const selectedIdx = flat.findIndex(it => it.isSelected && !it.isInactive);\n const firstEnabledIdx = flat.findIndex(it => !it.isInactive || !!it.reorderable);\n this.focusedIndex = selectedIdx >= 0 ? selectedIdx : firstEnabledIdx >= 0 ? firstEnabledIdx : 0;\n\n requestAnimationFrame(() => {\n const btns = this.el.querySelectorAll<HTMLElement>('.menu-item');\n btns[this.focusedIndex]?.focus();\n });\n }\n\n private setupListeners() {\n this.interaction.connect();\n this.position.observe();\n }\n\n private teardownListeners() {\n this.position.unobserve();\n this.interaction.disconnect();\n if (this.closeTimer) {\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n }\n\n private close(restoreFocus = true) {\n this.captureClosingSections();\n if (restoreFocus) this.interaction.focusAnchor();\n this.dsClose.emit();\n this.open = false;\n this.onOpenChange(false);\n }\n\n @Listen('keydown')\n handleKeyDown(e: KeyboardEvent) {\n if (this.embedded && (e.key === 'Escape' || e.key === 'Tab')) return;\n if ((!this.shouldRender && !this.embedded) || this.closing) return;\n\n if (e.key === 'Escape') {\n e.preventDefault();\n this.close();\n return;\n }\n\n if (e.key === 'Tab') {\n if (this.hasCompositeSections && !this.compositeTabLeavesPopup(e)) return;\n e.preventDefault();\n this.close(false);\n this.interaction.moveFocusAfterTab(e.shiftKey);\n return;\n }\n\n if (this.hasCompositeSections) return;\n\n if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && e.altKey && !e.ctrlKey && !e.shiftKey) {\n this.handleReorderKey(e);\n return;\n }\n\n const flat = this.flatItems;\n const enabled = flat\n .map((it, i) => ({ it, i }))\n .filter(({ it }) => !it.isInactive || !!it.reorderable)\n .map(({ i }) => i);\n if (!enabled.length) return;\n\n const cur = enabled.indexOf(this.focusedIndex);\n const safe = cur < 0 ? 0 : cur;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[(safe + 1) % enabled.length];\n this.focusItem(this.focusedIndex);\n break;\n case 'ArrowUp':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[(safe - 1 + enabled.length) % enabled.length];\n this.focusItem(this.focusedIndex);\n break;\n case 'Home':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[0];\n this.focusItem(this.focusedIndex);\n break;\n case 'End':\n e.preventDefault();\n e.stopPropagation();\n this.focusRingVisible = true;\n this.focusedIndex = enabled[enabled.length - 1];\n this.focusItem(this.focusedIndex);\n break;\n }\n }\n\n private focusItem(idx: number) {\n const btns = this.el.querySelectorAll<HTMLElement>('.menu-item');\n btns[idx]?.focus();\n }\n\n private handleItemClick(item: MenuItemData) {\n if (this.suppressItemClick) {\n this.suppressItemClick = false;\n return;\n }\n if (item.isInactive) return;\n // Consumers commonly change the item source while handling selection.\n // Capture first so the exit animation never paints the next menu context.\n this.captureClosingSections();\n this.dsSelect.emit(item);\n }\n\n private handleSwatchSelect(value: string) {\n this.dsSwatchSelect.emit(value);\n }\n\n private handleReorderKey(event: KeyboardEvent): void {\n const located = locateMenuItem(this.activeSections, this.focusedIndex);\n if (!located || !located.items[located.itemIndex]?.reorderable) return;\n\n const nextItems = moveReorderableMenuItemBy(\n located.items,\n located.itemIndex,\n event.key === 'ArrowUp' ? -1 : 1\n );\n if (!nextItems) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.emitReorder(located.items, located.itemIndex, nextItems, located.sectionIndex);\n }\n\n private emitReorder(\n items: MenuItemData[],\n fromIndex: number,\n nextItems: MenuItemData[],\n sectionIndex: number\n ): void {\n const detail = createMenuReorderDetail(items, fromIndex, nextItems, sectionIndex);\n if (!detail) return;\n this.focusedIndex = this.flatIndexFor(sectionIndex, detail.toIndex);\n this.announceReorder(detail);\n this.dsReorder.emit(detail);\n }\n\n private flatIndexFor(sectionIndex: number, itemIndex: number): number {\n let flat = 0;\n for (let index = 0; index < this.activeSections.length; index += 1) {\n const section = this.activeSections[index];\n if (isMenuPickerSection(section)) continue;\n if (index === sectionIndex) return flat + itemIndex;\n flat += section.items.length;\n }\n return itemIndex;\n }\n\n private announceReorder(detail: MenuReorderDetail): void {\n const range = menuReorderableRange(detail.items, detail.toIndex);\n const count = range ? range.end - range.start + 1 : detail.items.length;\n const position = range ? detail.toIndex - range.start + 1 : detail.toIndex + 1;\n this.reorderAnnouncement = `${detail.item.label} moved to position ${position} of ${count}`;\n }\n\n private onReorderPointerDown(\n event: PointerEvent,\n sectionIndex: number,\n itemIndex: number,\n flatIndex: number\n ): void {\n if (event.button !== 0) return;\n const section = this.activeSections[sectionIndex];\n if (!section || isMenuPickerSection(section)) return;\n if (!section.items[itemIndex]?.reorderable) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.suppressItemClick = true;\n this.focusRingVisible = false;\n this.focusedIndex = flatIndex;\n this.focusItem(flatIndex);\n this.reorderPointer = {\n pointerId: event.pointerId,\n sectionIndex,\n fromIndex: itemIndex,\n };\n this.reorderFromFlat = flatIndex;\n this.reorderInsertBefore = itemIndex;\n window.addEventListener('pointermove', this.onReorderPointerMove);\n window.addEventListener('pointerup', this.onReorderPointerUp);\n window.addEventListener('pointercancel', this.onReorderPointerCancel);\n }\n\n private onReorderPointerMove = (event: PointerEvent) => {\n const drag = this.reorderPointer;\n if (!drag || event.pointerId !== drag.pointerId) return;\n const section = this.activeSections[drag.sectionIndex];\n if (!section || isMenuPickerSection(section)) return;\n\n const insertBefore = this.resolveReorderInsertBefore(\n event.clientY,\n drag.sectionIndex,\n drag.fromIndex,\n section.items\n );\n if (insertBefore !== this.reorderInsertBefore) this.reorderInsertBefore = insertBefore;\n };\n\n private onReorderPointerUp = (event: PointerEvent) => {\n const drag = this.reorderPointer;\n if (!drag || event.pointerId !== drag.pointerId) return;\n\n const section = this.activeSections[drag.sectionIndex];\n const insertBefore = this.reorderInsertBefore;\n this.clearReorderPointer();\n queueMicrotask(() => {\n this.suppressItemClick = false;\n });\n if (!section || isMenuPickerSection(section) || insertBefore === null) return;\n\n const nextItems = moveReorderableMenuItemBefore(section.items, drag.fromIndex, insertBefore);\n if (!nextItems) return;\n this.emitReorder(section.items, drag.fromIndex, nextItems, drag.sectionIndex);\n };\n\n private onReorderPointerCancel = (event: PointerEvent) => {\n if (!this.reorderPointer || event.pointerId !== this.reorderPointer.pointerId) return;\n this.clearReorderPointer();\n queueMicrotask(() => {\n this.suppressItemClick = false;\n });\n };\n\n private resolveReorderInsertBefore(\n clientY: number,\n sectionIndex: number,\n fromIndex: number,\n items: MenuItemData[]\n ): number {\n const range = menuReorderableRange(items, fromIndex);\n if (!range) return fromIndex;\n\n const buttons = [...this.el.querySelectorAll<HTMLElement>('.menu-item')];\n const sectionStart = this.flatIndexFor(sectionIndex, 0);\n let insertBefore = range.end + 1;\n for (let itemIndex = range.start; itemIndex <= range.end; itemIndex += 1) {\n const row = buttons[sectionStart + itemIndex];\n if (!row) continue;\n const rect = row.getBoundingClientRect();\n if (clientY < rect.top + rect.height / 2) {\n insertBefore = itemIndex;\n break;\n }\n }\n return Math.min(Math.max(insertBefore, range.start), range.end + 1);\n }\n\n private clearReorderPointer(): void {\n if (this.reorderPointer) {\n window.removeEventListener('pointermove', this.onReorderPointerMove);\n window.removeEventListener('pointerup', this.onReorderPointerUp);\n window.removeEventListener('pointercancel', this.onReorderPointerCancel);\n }\n this.reorderPointer = null;\n this.reorderFromFlat = null;\n this.reorderInsertBefore = null;\n }\n\n render() {\n if (!this.shouldRender && !this.embedded) return <Host style={{ display: 'contents' }} />;\n\n const sections = this.closing\n ? (this.closingSections ?? this.lastRenderedSections)\n : this.activeSections;\n const hasCompositeSections = sections.some(isMenuPickerSection);\n const emptyMessage = this.closing ? this.lastRenderedEmptyMessage : this.emptyMessage.trim();\n const showEmpty =\n Boolean(emptyMessage) &&\n !hasCompositeSections &&\n sections.every(section => !isMenuPickerSection(section) && section.items.length === 0);\n if (!this.closing) {\n this.lastRenderedEmptyMessage = emptyMessage;\n this.lastRenderedSections = snapshotMenuSections(sections);\n this.closingSections = null;\n }\n let flatIdx = 0;\n const dragLocated =\n this.reorderFromFlat === null ? null : locateMenuItem(sections, this.reorderFromFlat);\n const dragRange = dragLocated\n ? menuReorderableRange(dragLocated.items, dragLocated.itemIndex)\n : null;\n\n const popupStyle: Record<string, string> = {\n position: 'fixed',\n left: '0',\n top: '0',\n transform: `translate(${Math.round(this.pos.x)}px, ${Math.round(this.pos.y)}px)`,\n zIndex: '9998',\n visibility: this.positionReady ? 'visible' : 'hidden',\n };\n\n if (this.menuWidth) popupStyle['width'] = this.menuWidth;\n if (this.minWidth) popupStyle['min-width'] = this.minWidth;\n if (this.availableHeight !== null) {\n popupStyle['max-height'] = `${Math.floor(this.availableHeight)}px`;\n }\n\n return (\n <Host style={{ display: 'contents' }}>\n <div\n popover={this.embedded ? undefined : 'manual'}\n class={{\n 'menu-popup': true,\n 'menu-popup--closing': this.closing,\n 'ds-choice-popup': !this.embedded,\n 'table-preferences-embedded': this.embedded,\n 'ds-choice-popup--closing': this.closing,\n 'ds-focus-ring': showEmpty,\n 'ds-focus-ring--visible': showEmpty && this.focusRingVisible,\n }}\n style={this.embedded ? undefined : popupStyle}\n role={hasCompositeSections || showEmpty ? 'dialog' : 'menu'}\n aria-label={this.menuLabel}\n aria-orientation={hasCompositeSections || showEmpty ? undefined : 'vertical'}\n aria-describedby={showEmpty ? this.emptyMessageId : undefined}\n tabIndex={showEmpty ? -1 : undefined}\n >\n <div class=\"ds-choice-list\">\n {showEmpty ? (\n <ds-text\n textId={this.emptyMessageId}\n class=\"menu-empty\"\n as=\"p\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n color=\"secondary\"\n >\n {emptyMessage}\n </ds-text>\n ) : (\n sections.map((section, si) => (\n <div\n key={si}\n class={{\n 'menu-section': true,\n 'menu-section--divided': si < sections.length - 1,\n 'menu-section--swatch-picker': isMenuPickerSection(section),\n 'ds-choice-section': true,\n 'ds-chrome-column': true,\n 'ds-chrome-space--sm': true,\n 'ds-choice-section--divided': si < sections.length - 1,\n 'ds-choice-section--headed-after-first': si > 0 && Boolean(section.header),\n }}\n role={section.header ? 'group' : undefined}\n aria-label={section.header}\n >\n {section.header && (\n <ds-text\n class={`section-header ds-choice-section__header ds-control-section-heading ds-control--${this.size}`}\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n emphasis\n color=\"primary\"\n aria-hidden=\"true\"\n >\n <span class=\"ds-choice-section__header-label\">{section.header}</span>\n </ds-text>\n )}\n {isMenuSwatchPickerSection(section) ? (\n <ds-swatch-picker\n value={section.value}\n groupLabel={section.groupLabel ?? section.header ?? 'Swatch options'}\n options={section.options ?? []}\n sections={section.sections ?? []}\n onDsChange={(e: CustomEvent<string>) => {\n e.stopPropagation();\n this.handleSwatchSelect(e.detail);\n }}\n />\n ) : (\n section.items.map((item, itemIndex) => {\n const idx = flatIdx++;\n const isFocused = this.focusedIndex === idx;\n const isSingleSelectionItem =\n !hasCompositeSections &&\n this.selectionMode === 'single' &&\n !item.showSwitch;\n const usesLeading = section.items.some(\n candidate => candidate.reorderable || !!candidate.icon\n );\n const locked = !!item.isInactive && !item.reorderable;\n const dragging = this.reorderFromFlat === idx;\n const dropBefore =\n dragLocated?.sectionIndex === si &&\n dragRange !== null &&\n itemIndex <= dragRange.end &&\n this.reorderInsertBefore === itemIndex;\n const dropAfter =\n dragLocated?.sectionIndex === si &&\n dragRange !== null &&\n this.reorderInsertBefore === dragRange.end + 1 &&\n itemIndex === dragRange.end;\n return (\n <button\n key={item.value ?? `${si}-${item.label}`}\n type=\"button\"\n class={{\n 'menu-item': true,\n 'ds-choice-item': true,\n 'ds-control-frame': true,\n [`ds-control--${this.size}`]: true,\n 'ds-focus-ring-inset': true,\n 'ds-focus-ring--visible': isFocused && this.focusRingVisible,\n 'ds-interaction-fill': !locked,\n 'ds-interaction-fill--selected': !!item.isSelected && !locked,\n 'menu-item--selected': !!item.isSelected,\n 'menu-item--switch': !!item.showSwitch,\n 'ds-control-inactive': locked,\n 'menu-item--destructive': !!item.isDestructive,\n 'menu-item--focused': isFocused,\n 'menu-item--dragging': dragging,\n }}\n role={\n hasCompositeSections\n ? undefined\n : item.showSwitch\n ? 'menuitemcheckbox'\n : isSingleSelectionItem\n ? 'menuitemradio'\n : 'menuitem'\n }\n aria-checked={\n !hasCompositeSections && item.showSwitch\n ? String(!!item.switchValue)\n : isSingleSelectionItem\n ? String(!!item.isSelected)\n : undefined\n }\n aria-pressed={\n hasCompositeSections\n ? String(item.showSwitch ? !!item.switchValue : !!item.isSelected)\n : undefined\n }\n aria-current={\n !hasCompositeSections &&\n !item.showSwitch &&\n !isSingleSelectionItem &&\n item.isSelected\n ? 'true'\n : undefined\n }\n aria-description={\n item.reorderable\n ? item.isInactive\n ? 'Visibility cannot be changed. Alt + Arrow Up or Alt + Arrow Down moves this row.'\n : 'Drag to reorder. Alt + Arrow Up or Alt + Arrow Down moves this row.'\n : undefined\n }\n aria-disabled={item.isInactive ? 'true' : undefined}\n disabled={locked}\n tabIndex={\n hasCompositeSections || (this.embedded && idx === 0)\n ? 0\n : isFocused\n ? 0\n : -1\n }\n onMouseDown={() => {\n this.focusRingVisible = false;\n }}\n onClick={() => this.handleItemClick(item)}\n onFocus={() => {\n this.focusedIndex = idx;\n }}\n >\n {(dropBefore || dropAfter) && (\n <span\n class={{\n 'menu-item__drop-rail': true,\n 'menu-item__drop-rail--before': dropBefore,\n 'menu-item__drop-rail--after': dropAfter,\n }}\n data-menu-drop-rail\n aria-hidden=\"true\"\n />\n )}\n {item.reorderable ? (\n <span\n class=\"menu-item__handle ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n data-menu-handle\n aria-hidden=\"true\"\n onPointerDown={event =>\n this.onReorderPointerDown(event, si, itemIndex, idx)\n }\n onClick={event => {\n event.preventDefault();\n event.stopPropagation();\n }}\n >\n <ds-icon name=\"Drag\" size={this.size} color=\"inherit\" />\n </span>\n ) : usesLeading && item.icon ? (\n <span\n class=\"menu-item__icon ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name={item.icon} size={this.size} color=\"inherit\" />\n </span>\n ) : usesLeading ? (\n <span\n class=\"menu-item__icon-spacer ds-choice-item__icon ds-control-icon-box\"\n aria-hidden=\"true\"\n />\n ) : null}\n <div class=\"menu-item__content ds-choice-item__content ds-interaction-fill__content\">\n <ds-text\n class=\"menu-item__label ds-choice-item__label ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_TEXT_VARIANT[this.size]}\n color={item.isSelected ? 'primary' : 'secondary'}\n >\n {item.label}\n </ds-text>\n {item.subtext && (\n <ds-text\n class=\"menu-item__subtext ds-choice-item__subtext ds-control-label-box\"\n as=\"span\"\n variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n color=\"secondary\"\n >\n {item.subtext}\n </ds-text>\n )}\n </div>\n {item.tag && (\n <ds-tag\n class=\"ds-choice-item__tag ds-interaction-fill__content\"\n label={item.tag.label}\n size={MENU_ITEM_TAG_SIZE[this.size]}\n intent={item.tag.intent ?? 'neutral'}\n contrast={item.tag.contrast ?? 'faint'}\n rounded={item.tag.rounded ?? false}\n isInset\n />\n )}\n {item.dot && (\n <span\n class=\"menu-item__dot-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-badge\n class=\"menu-item__dot\"\n variant=\"dot\"\n hasRing={false}\n label=\"\"\n />\n </span>\n )}\n {item.showSwitch && (\n <ds-switch\n class=\"menu-item__switch ds-interaction-fill__content\"\n size={this.size}\n checked={!!item.switchValue}\n isInactive={!!item.isInactive}\n presentation\n />\n )}\n </button>\n );\n })\n )}\n </div>\n ))\n )}\n </div>\n </div>\n <div class=\"ds-visually-hidden\" role=\"status\" aria-live=\"polite\">\n {this.reorderAnnouncement}\n </div>\n </Host>\n );\n }\n}\n",
551
570
  "type": "registry:ui"
552
571
  },
553
572
  {