@pine-ds/core 4.2.0 → 4.4.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 (545) hide show
  1. package/components/closest.js +1 -1
  2. package/components/floating-ui.dom.js +1 -1
  3. package/components/i18n-format.js +1 -1
  4. package/components/index.d.ts +2 -0
  5. package/components/index.js +1 -1
  6. package/components/index2.js +1 -1
  7. package/components/mock-pds-modal.js +1 -1
  8. package/components/pds-button2.js +1 -1
  9. package/components/pds-checkbox2.js +1 -1
  10. package/components/pds-chip2.js +1 -1
  11. package/components/pds-combobox.js +2 -2
  12. package/components/pds-container.js +1 -1
  13. package/components/pds-copytext.js +1 -1
  14. package/components/pds-datepicker.d.ts +11 -0
  15. package/components/pds-datepicker.js +1 -0
  16. package/components/pds-divider.js +1 -1
  17. package/components/pds-drawer-content.js +1 -1
  18. package/components/pds-drawer-footer.js +1 -1
  19. package/components/pds-drawer-header.js +1 -1
  20. package/components/pds-drawer.js +1 -1
  21. package/components/pds-dropdown-menu-item.js +1 -1
  22. package/components/pds-dropdown-menu-separator.js +1 -1
  23. package/components/pds-dropdown-menu.js +1 -1
  24. package/components/pds-filter.js +1 -1
  25. package/components/pds-filters.js +1 -1
  26. package/components/pds-icon2.js +1 -1
  27. package/components/pds-image.js +1 -1
  28. package/components/pds-input.js +1 -1
  29. package/components/pds-input2.js +1 -0
  30. package/components/pds-link2.js +1 -1
  31. package/components/pds-loader2.js +1 -1
  32. package/components/pds-modal-content.js +1 -1
  33. package/components/pds-modal-footer.js +1 -1
  34. package/components/pds-modal-header.js +1 -1
  35. package/components/pds-modal2.js +1 -1
  36. package/components/pds-multiselect.js +1 -1
  37. package/components/pds-popover.js +1 -1
  38. package/components/pds-progress.js +1 -1
  39. package/components/pds-property.js +1 -1
  40. package/components/pds-radio-group.js +1 -1
  41. package/components/pds-radio.js +1 -1
  42. package/components/pds-row.js +1 -1
  43. package/components/pds-select.js +1 -1
  44. package/components/pds-sortable-item.js +1 -1
  45. package/components/pds-sortable.js +2 -2
  46. package/components/pds-switch.js +1 -1
  47. package/components/pds-table-body.js +1 -1
  48. package/components/pds-table-cell2.js +1 -1
  49. package/components/pds-table-head-cell2.js +1 -1
  50. package/components/pds-table-head.js +1 -1
  51. package/components/pds-table-row.js +1 -1
  52. package/components/pds-tabpanel.js +1 -1
  53. package/components/pds-tabs.js +1 -1
  54. package/components/pds-text2.js +1 -1
  55. package/components/pds-textarea.js +1 -1
  56. package/components/pds-toast.js +1 -1
  57. package/components/pds-tooltip.js +1 -1
  58. package/components/utils.js +1 -1
  59. package/dist/cjs/closest-D0qyYtJ3.js +20 -0
  60. package/dist/cjs/{floating-ui.dom-BZk7Blsu.js → floating-ui.dom-Dc8Anyel.js} +79 -0
  61. package/dist/cjs/{i18n-format-CrpIhahc.js → i18n-format-BD0PmYzT.js} +1 -0
  62. package/dist/cjs/{index-CuQil7H0.js → index-Bl5KnQay.js} +4 -1
  63. package/dist/cjs/{index-Bu3NXpbK.js → index-D-QQKB0y.js} +9 -1
  64. package/dist/cjs/loader.cjs.js +2 -2
  65. package/dist/cjs/mock-pds-modal.cjs.entry.js +3 -3
  66. package/dist/cjs/pds-accordion.cjs.entry.js +2 -2
  67. package/dist/cjs/pds-alert.cjs.entry.js +1 -1
  68. package/dist/cjs/pds-avatar.cjs.entry.js +2 -2
  69. package/dist/cjs/pds-box.cjs.entry.js +2 -2
  70. package/dist/cjs/pds-button.cjs.entry.js +54 -5
  71. package/dist/cjs/pds-checkbox.cjs.entry.js +14 -6
  72. package/dist/cjs/pds-chip.cjs.entry.js +34 -9
  73. package/dist/cjs/pds-combobox.cjs.entry.js +1225 -117
  74. package/dist/cjs/pds-container.cjs.entry.js +2 -2
  75. package/dist/cjs/pds-copytext.cjs.entry.js +5 -5
  76. package/dist/cjs/pds-datepicker.cjs.entry.js +1962 -0
  77. package/dist/cjs/pds-divider.cjs.entry.js +2 -2
  78. package/dist/cjs/pds-drawer-content.cjs.entry.js +2 -2
  79. package/dist/cjs/pds-drawer-footer.cjs.entry.js +2 -2
  80. package/dist/cjs/pds-drawer-header.cjs.entry.js +2 -2
  81. package/dist/cjs/pds-drawer.cjs.entry.js +11 -4
  82. package/dist/cjs/pds-dropdown-menu-item.cjs.entry.js +2 -2
  83. package/dist/cjs/pds-dropdown-menu-separator.cjs.entry.js +2 -2
  84. package/dist/cjs/pds-dropdown-menu.cjs.entry.js +11 -4
  85. package/dist/cjs/pds-filter.cjs.entry.js +3 -3
  86. package/dist/cjs/pds-filters.cjs.entry.js +2 -2
  87. package/dist/cjs/pds-icon.cjs.entry.js +2 -2
  88. package/dist/cjs/pds-image.cjs.entry.js +3 -3
  89. package/dist/cjs/pds-input.cjs.entry.js +8 -5
  90. package/dist/cjs/pds-link.cjs.entry.js +5 -5
  91. package/dist/cjs/pds-loader.cjs.entry.js +2 -2
  92. package/dist/cjs/pds-modal-content.cjs.entry.js +3 -3
  93. package/dist/cjs/pds-modal-footer.cjs.entry.js +2 -2
  94. package/dist/cjs/pds-modal-header.cjs.entry.js +2 -2
  95. package/dist/cjs/pds-modal.cjs.entry.js +42 -8
  96. package/dist/cjs/pds-multiselect.cjs.entry.js +264 -51
  97. package/dist/cjs/pds-popover.cjs.entry.js +30 -12
  98. package/dist/cjs/pds-progress.cjs.entry.js +4 -2
  99. package/dist/cjs/pds-property.cjs.entry.js +2 -2
  100. package/dist/cjs/pds-radio-group.cjs.entry.js +5 -5
  101. package/dist/cjs/pds-radio.cjs.entry.js +6 -6
  102. package/dist/cjs/pds-row.cjs.entry.js +2 -2
  103. package/dist/cjs/pds-select.cjs.entry.js +3 -3
  104. package/dist/cjs/pds-sortable-item.cjs.entry.js +3 -3
  105. package/dist/cjs/pds-sortable.cjs.entry.js +2 -2
  106. package/dist/cjs/pds-switch.cjs.entry.js +14 -6
  107. package/dist/cjs/pds-tab.cjs.entry.js +1 -1
  108. package/dist/cjs/pds-table-body.cjs.entry.js +2 -2
  109. package/dist/cjs/pds-table-cell.cjs.entry.js +5 -5
  110. package/dist/cjs/pds-table-head-cell.cjs.entry.js +4 -4
  111. package/dist/cjs/pds-table-head.cjs.entry.js +3 -3
  112. package/dist/cjs/pds-table-row.cjs.entry.js +3 -3
  113. package/dist/cjs/pds-table.cjs.entry.js +1 -1
  114. package/dist/cjs/pds-tabpanel.cjs.entry.js +2 -2
  115. package/dist/cjs/pds-tabs.cjs.entry.js +2 -2
  116. package/dist/cjs/pds-text.cjs.entry.js +5 -5
  117. package/dist/cjs/pds-textarea.cjs.entry.js +14 -9
  118. package/dist/cjs/pds-toast.cjs.entry.js +4 -4
  119. package/dist/cjs/pds-tooltip.cjs.entry.js +4 -4
  120. package/dist/cjs/pine-core.cjs.js +2 -2
  121. package/dist/cjs/{truncation-tooltip-Bps7_rUy.js → truncation-tooltip-CYgQfBLt.js} +1 -1
  122. package/dist/cjs/{utils-7jx8T2mW.js → utils-BhRmyxT8.js} +51 -6
  123. package/dist/collection/collection-manifest.json +1 -0
  124. package/dist/collection/components/pds-button/pds-button.css +1 -2
  125. package/dist/collection/components/pds-button/pds-button.js +53 -2
  126. package/dist/collection/components/pds-checkbox/pds-checkbox.css +13 -4
  127. package/dist/collection/components/pds-checkbox/pds-checkbox.js +19 -3
  128. package/dist/collection/components/pds-chip/pds-chip.css +60 -6
  129. package/dist/collection/components/pds-chip/pds-chip.js +69 -4
  130. package/dist/collection/components/pds-chip/pds-chip.tokens.css +16 -0
  131. package/dist/collection/components/pds-chip/stories/pds-chip.stories.js +39 -1
  132. package/dist/collection/components/pds-combobox/grouped-options-panel.js +262 -0
  133. package/dist/collection/components/pds-combobox/pds-combobox.css +243 -15
  134. package/dist/collection/components/pds-combobox/pds-combobox.js +1129 -115
  135. package/dist/collection/components/pds-combobox/stories/pds-combobox.stories.js +240 -0
  136. package/dist/collection/components/pds-container/pds-container.js +1 -1
  137. package/dist/collection/components/pds-copytext/pds-copytext.js +1 -1
  138. package/dist/collection/components/pds-datepicker/calendar-portal.js +136 -0
  139. package/dist/collection/components/pds-datepicker/date-utils.js +132 -0
  140. package/dist/collection/components/pds-datepicker/datepicker-interface.js +1 -0
  141. package/dist/collection/components/pds-datepicker/pds-datepicker.css +545 -0
  142. package/dist/collection/components/pds-datepicker/pds-datepicker.js +2406 -0
  143. package/dist/collection/components/pds-datepicker/presets.js +96 -0
  144. package/dist/collection/components/pds-datepicker/stories/pds-datepicker.stories.js +195 -0
  145. package/dist/collection/components/pds-datepicker/value.js +238 -0
  146. package/dist/collection/components/pds-divider/pds-divider.js +1 -1
  147. package/dist/collection/components/pds-drawer/pds-drawer-content/pds-drawer-content.js +1 -1
  148. package/dist/collection/components/pds-drawer/pds-drawer-footer/pds-drawer-footer.js +1 -1
  149. package/dist/collection/components/pds-drawer/pds-drawer-header/pds-drawer-header.js +1 -1
  150. package/dist/collection/components/pds-drawer/pds-drawer.css +4 -0
  151. package/dist/collection/components/pds-drawer/pds-drawer.js +9 -2
  152. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu-item/pds-dropdown-menu-item.js +1 -1
  153. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu-separator/pds-dropdown-menu-separator.js +1 -1
  154. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu.js +9 -2
  155. package/dist/collection/components/pds-filters/pds-filter/pds-filter.js +1 -1
  156. package/dist/collection/components/pds-filters/pds-filters.js +1 -1
  157. package/dist/collection/components/pds-image/pds-image.js +2 -2
  158. package/dist/collection/components/pds-input/pds-input.js +6 -3
  159. package/dist/collection/components/pds-link/pds-link.js +2 -2
  160. package/dist/collection/components/pds-loader/pds-loader.js +1 -1
  161. package/dist/collection/components/pds-modal/pds-modal-content/pds-modal-content.js +2 -2
  162. package/dist/collection/components/pds-modal/pds-modal-footer/pds-modal-footer.js +1 -1
  163. package/dist/collection/components/pds-modal/pds-modal-header/pds-modal-header.js +1 -1
  164. package/dist/collection/components/pds-modal/pds-modal.css +20 -0
  165. package/dist/collection/components/pds-modal/pds-modal.js +55 -10
  166. package/dist/collection/components/pds-modal/stories/pds-modal.stories.js +14 -0
  167. package/dist/collection/components/pds-modal/test/mock-pds-modal.js +1 -1
  168. package/dist/collection/components/pds-multiselect/pds-multiselect.css +19 -1
  169. package/dist/collection/components/pds-multiselect/pds-multiselect.js +390 -47
  170. package/dist/collection/components/pds-multiselect/stories/pds-multiselect.stories.js +136 -1
  171. package/dist/collection/components/pds-popover/pds-popover.js +29 -11
  172. package/dist/collection/components/pds-progress/pds-progress.js +3 -1
  173. package/dist/collection/components/pds-property/pds-property.js +1 -1
  174. package/dist/collection/components/pds-radio/pds-radio.css +1 -2
  175. package/dist/collection/components/pds-radio/pds-radio.js +3 -3
  176. package/dist/collection/components/pds-radio-group/pds-radio-group.js +3 -3
  177. package/dist/collection/components/pds-row/pds-row.js +1 -1
  178. package/dist/collection/components/pds-select/pds-select.js +1 -1
  179. package/dist/collection/components/pds-sortable/pds-sortable-item/pds-sortable-item.js +1 -1
  180. package/dist/collection/components/pds-sortable/pds-sortable.js +1 -1
  181. package/dist/collection/components/pds-switch/pds-switch.css +3 -0
  182. package/dist/collection/components/pds-switch/pds-switch.js +20 -4
  183. package/dist/collection/components/pds-table/pds-table-body/pds-table-body.js +1 -1
  184. package/dist/collection/components/pds-table/pds-table-cell/pds-table-cell.js +2 -2
  185. package/dist/collection/components/pds-table/pds-table-head/pds-table-head.js +1 -1
  186. package/dist/collection/components/pds-table/pds-table-head-cell/pds-table-head-cell.js +2 -2
  187. package/dist/collection/components/pds-table/pds-table-row/pds-table-row.js +1 -1
  188. package/dist/collection/components/pds-tabs/pds-tabpanel/pds-tabpanel.js +1 -1
  189. package/dist/collection/components/pds-tabs/pds-tabs.js +1 -1
  190. package/dist/collection/components/pds-text/pds-text.js +1 -1
  191. package/dist/collection/components/pds-textarea/pds-textarea.js +11 -6
  192. package/dist/collection/components/pds-toast/pds-toast.js +3 -3
  193. package/dist/collection/components/pds-tooltip/pds-tooltip.js +2 -2
  194. package/dist/collection/utils/closest.js +8 -0
  195. package/dist/collection/utils/utils.js +49 -6
  196. package/dist/docs.json +2055 -84
  197. package/dist/esm/closest-DHPzKMVS.js +17 -0
  198. package/dist/esm/{floating-ui.dom-DDtwbwIl.js → floating-ui.dom-BPqJgIq6.js} +79 -1
  199. package/dist/esm/{i18n-format-BKsJZTyn.js → i18n-format-BJgZHCWe.js} +1 -1
  200. package/dist/esm/{index-CTLu1TM4.js → index-CB8Yh0Nt.js} +4 -1
  201. package/dist/esm/{index-DdPaBxPo.js → index-DxI-61GK.js} +6 -2
  202. package/dist/esm/loader.js +3 -3
  203. package/dist/esm/mock-pds-modal.entry.js +3 -3
  204. package/dist/esm/pds-accordion.entry.js +2 -2
  205. package/dist/esm/pds-alert.entry.js +1 -1
  206. package/dist/esm/pds-avatar.entry.js +2 -2
  207. package/dist/esm/pds-box.entry.js +2 -2
  208. package/dist/esm/pds-button.entry.js +54 -5
  209. package/dist/esm/pds-checkbox.entry.js +14 -6
  210. package/dist/esm/pds-chip.entry.js +34 -9
  211. package/dist/esm/pds-combobox.entry.js +1225 -117
  212. package/dist/esm/pds-container.entry.js +2 -2
  213. package/dist/esm/pds-copytext.entry.js +5 -5
  214. package/dist/esm/pds-datepicker.entry.js +1960 -0
  215. package/dist/esm/pds-divider.entry.js +2 -2
  216. package/dist/esm/pds-drawer-content.entry.js +2 -2
  217. package/dist/esm/pds-drawer-footer.entry.js +2 -2
  218. package/dist/esm/pds-drawer-header.entry.js +2 -2
  219. package/dist/esm/pds-drawer.entry.js +11 -4
  220. package/dist/esm/pds-dropdown-menu-item.entry.js +2 -2
  221. package/dist/esm/pds-dropdown-menu-separator.entry.js +2 -2
  222. package/dist/esm/pds-dropdown-menu.entry.js +11 -4
  223. package/dist/esm/pds-filter.entry.js +3 -3
  224. package/dist/esm/pds-filters.entry.js +2 -2
  225. package/dist/esm/pds-icon.entry.js +2 -2
  226. package/dist/esm/pds-image.entry.js +3 -3
  227. package/dist/esm/pds-input.entry.js +8 -5
  228. package/dist/esm/pds-link.entry.js +5 -5
  229. package/dist/esm/pds-loader.entry.js +2 -2
  230. package/dist/esm/pds-modal-content.entry.js +3 -3
  231. package/dist/esm/pds-modal-footer.entry.js +2 -2
  232. package/dist/esm/pds-modal-header.entry.js +2 -2
  233. package/dist/esm/pds-modal.entry.js +42 -8
  234. package/dist/esm/pds-multiselect.entry.js +264 -51
  235. package/dist/esm/pds-popover.entry.js +30 -12
  236. package/dist/esm/pds-progress.entry.js +4 -2
  237. package/dist/esm/pds-property.entry.js +2 -2
  238. package/dist/esm/pds-radio-group.entry.js +5 -5
  239. package/dist/esm/pds-radio.entry.js +6 -6
  240. package/dist/esm/pds-row.entry.js +2 -2
  241. package/dist/esm/pds-select.entry.js +3 -3
  242. package/dist/esm/pds-sortable-item.entry.js +3 -3
  243. package/dist/esm/pds-sortable.entry.js +2 -2
  244. package/dist/esm/pds-switch.entry.js +14 -6
  245. package/dist/esm/pds-tab.entry.js +1 -1
  246. package/dist/esm/pds-table-body.entry.js +2 -2
  247. package/dist/esm/pds-table-cell.entry.js +5 -5
  248. package/dist/esm/pds-table-head-cell.entry.js +4 -4
  249. package/dist/esm/pds-table-head.entry.js +3 -3
  250. package/dist/esm/pds-table-row.entry.js +3 -3
  251. package/dist/esm/pds-table.entry.js +1 -1
  252. package/dist/esm/pds-tabpanel.entry.js +2 -2
  253. package/dist/esm/pds-tabs.entry.js +2 -2
  254. package/dist/esm/pds-text.entry.js +5 -5
  255. package/dist/esm/pds-textarea.entry.js +14 -9
  256. package/dist/esm/pds-toast.entry.js +4 -4
  257. package/dist/esm/pds-tooltip.entry.js +4 -4
  258. package/dist/esm/pine-core.js +3 -3
  259. package/dist/esm/{truncation-tooltip-CdtEBn_5.js → truncation-tooltip-_902ENWC.js} +1 -1
  260. package/dist/esm/{utils-tJa4KYNO.js → utils-OYK5-nwe.js} +50 -7
  261. package/dist/esm-es5/closest-DHPzKMVS.js +1 -0
  262. package/dist/esm-es5/floating-ui.dom-BPqJgIq6.js +1 -0
  263. package/dist/esm-es5/{i18n-format-BKsJZTyn.js → i18n-format-BJgZHCWe.js} +1 -1
  264. package/dist/esm-es5/index-CB8Yh0Nt.js +2 -0
  265. package/dist/esm-es5/index-DxI-61GK.js +1 -0
  266. package/dist/esm-es5/loader.js +1 -1
  267. package/dist/esm-es5/mock-pds-modal.entry.js +1 -1
  268. package/dist/esm-es5/pds-accordion.entry.js +1 -1
  269. package/dist/esm-es5/pds-alert.entry.js +1 -1
  270. package/dist/esm-es5/pds-avatar.entry.js +1 -1
  271. package/dist/esm-es5/pds-box.entry.js +1 -1
  272. package/dist/esm-es5/pds-button.entry.js +1 -1
  273. package/dist/esm-es5/pds-checkbox.entry.js +1 -1
  274. package/dist/esm-es5/pds-chip.entry.js +1 -1
  275. package/dist/esm-es5/pds-combobox.entry.js +2 -2
  276. package/dist/esm-es5/pds-container.entry.js +1 -1
  277. package/dist/esm-es5/pds-copytext.entry.js +1 -1
  278. package/dist/esm-es5/pds-datepicker.entry.js +1 -0
  279. package/dist/esm-es5/pds-divider.entry.js +1 -1
  280. package/dist/esm-es5/pds-drawer-content.entry.js +1 -1
  281. package/dist/esm-es5/pds-drawer-footer.entry.js +1 -1
  282. package/dist/esm-es5/pds-drawer-header.entry.js +1 -1
  283. package/dist/esm-es5/pds-drawer.entry.js +1 -1
  284. package/dist/esm-es5/pds-dropdown-menu-item.entry.js +1 -1
  285. package/dist/esm-es5/pds-dropdown-menu-separator.entry.js +1 -1
  286. package/dist/esm-es5/pds-dropdown-menu.entry.js +1 -1
  287. package/dist/esm-es5/pds-filter.entry.js +1 -1
  288. package/dist/esm-es5/pds-filters.entry.js +1 -1
  289. package/dist/esm-es5/pds-icon.entry.js +1 -1
  290. package/dist/esm-es5/pds-image.entry.js +1 -1
  291. package/dist/esm-es5/pds-input.entry.js +1 -1
  292. package/dist/esm-es5/pds-link.entry.js +1 -1
  293. package/dist/esm-es5/pds-loader.entry.js +1 -1
  294. package/dist/esm-es5/pds-modal-content.entry.js +1 -1
  295. package/dist/esm-es5/pds-modal-footer.entry.js +1 -1
  296. package/dist/esm-es5/pds-modal-header.entry.js +1 -1
  297. package/dist/esm-es5/pds-modal.entry.js +1 -1
  298. package/dist/esm-es5/pds-multiselect.entry.js +1 -1
  299. package/dist/esm-es5/pds-popover.entry.js +1 -1
  300. package/dist/esm-es5/pds-progress.entry.js +1 -1
  301. package/dist/esm-es5/pds-property.entry.js +1 -1
  302. package/dist/esm-es5/pds-radio-group.entry.js +1 -1
  303. package/dist/esm-es5/pds-radio.entry.js +1 -1
  304. package/dist/esm-es5/pds-row.entry.js +1 -1
  305. package/dist/esm-es5/pds-select.entry.js +1 -1
  306. package/dist/esm-es5/pds-sortable-item.entry.js +1 -1
  307. package/dist/esm-es5/pds-sortable.entry.js +2 -2
  308. package/dist/esm-es5/pds-switch.entry.js +1 -1
  309. package/dist/esm-es5/pds-tab.entry.js +1 -1
  310. package/dist/esm-es5/pds-table-body.entry.js +1 -1
  311. package/dist/esm-es5/pds-table-cell.entry.js +1 -1
  312. package/dist/esm-es5/pds-table-head-cell.entry.js +1 -1
  313. package/dist/esm-es5/pds-table-head.entry.js +1 -1
  314. package/dist/esm-es5/pds-table-row.entry.js +1 -1
  315. package/dist/esm-es5/pds-table.entry.js +1 -1
  316. package/dist/esm-es5/pds-tabpanel.entry.js +1 -1
  317. package/dist/esm-es5/pds-tabs.entry.js +1 -1
  318. package/dist/esm-es5/pds-text.entry.js +1 -1
  319. package/dist/esm-es5/pds-textarea.entry.js +1 -1
  320. package/dist/esm-es5/pds-toast.entry.js +1 -1
  321. package/dist/esm-es5/pds-tooltip.entry.js +1 -1
  322. package/dist/esm-es5/pine-core.js +1 -1
  323. package/dist/esm-es5/{truncation-tooltip-CdtEBn_5.js → truncation-tooltip-_902ENWC.js} +1 -1
  324. package/dist/esm-es5/utils-OYK5-nwe.js +1 -0
  325. package/dist/pine-core/p-0414092a.system.entry.js +1 -0
  326. package/dist/pine-core/p-04807201.entry.js +1 -0
  327. package/dist/pine-core/{p-56b34569.system.entry.js → p-07f0d168.system.entry.js} +1 -1
  328. package/dist/pine-core/p-08f5d19c.entry.js +1 -0
  329. package/dist/pine-core/{p-15d3a4bc.system.entry.js → p-0aaf8faa.system.entry.js} +1 -1
  330. package/dist/pine-core/p-0bad5955.entry.js +1 -0
  331. package/dist/pine-core/{p-4d388957.system.entry.js → p-0c3bf377.system.entry.js} +1 -1
  332. package/dist/pine-core/p-0ce79cd0.entry.js +1 -0
  333. package/dist/pine-core/{p-3464b456.system.entry.js → p-1057b8c3.system.entry.js} +1 -1
  334. package/dist/pine-core/{p-bf3afa16.system.entry.js → p-108c910a.system.entry.js} +1 -1
  335. package/dist/pine-core/{p-53a4852e.system.entry.js → p-166f61a6.system.entry.js} +1 -1
  336. package/dist/pine-core/{p-31e1722e.entry.js → p-16c8f5e3.entry.js} +1 -1
  337. package/dist/pine-core/p-16e258d1.entry.js +1 -0
  338. package/dist/pine-core/{p-591fbff5.entry.js → p-1be33c95.entry.js} +1 -1
  339. package/dist/pine-core/p-1e3a31da.entry.js +1 -0
  340. package/dist/pine-core/p-1f228db9.entry.js +1 -0
  341. package/dist/pine-core/p-257c6ef9.entry.js +1 -0
  342. package/dist/pine-core/p-26415245.system.entry.js +1 -0
  343. package/dist/pine-core/p-26e1e5bb.entry.js +1 -0
  344. package/dist/pine-core/p-27c06bf8.entry.js +2 -0
  345. package/dist/pine-core/p-31aee437.entry.js +1 -0
  346. package/dist/pine-core/p-330a76f6.system.entry.js +1 -0
  347. package/dist/pine-core/{p-3c4236a8.system.entry.js → p-33c63be6.system.entry.js} +1 -1
  348. package/dist/pine-core/{p-d46adf7d.entry.js → p-344609f9.entry.js} +2 -2
  349. package/dist/pine-core/{p-049681fc.entry.js → p-3a1711c1.entry.js} +1 -1
  350. package/dist/pine-core/p-3a9c58bd.system.entry.js +1 -0
  351. package/dist/pine-core/{p-e3a7d2db.entry.js → p-3ebd543f.entry.js} +1 -1
  352. package/dist/pine-core/p-3fbcb0c8.system.entry.js +1 -0
  353. package/dist/pine-core/{p-b923a70b.entry.js → p-427b4aeb.entry.js} +1 -1
  354. package/dist/pine-core/{p-5772f260.system.entry.js → p-42bd87ea.system.entry.js} +1 -1
  355. package/dist/pine-core/{p-1e2a9b5d.system.entry.js → p-43c691bf.system.entry.js} +1 -1
  356. package/dist/pine-core/p-4566d659.system.entry.js +1 -0
  357. package/dist/pine-core/p-4b8cf04a.system.entry.js +1 -0
  358. package/dist/pine-core/p-4d6f471c.entry.js +1 -0
  359. package/dist/pine-core/{p-3e0c6384.entry.js → p-4e7580c4.entry.js} +1 -1
  360. package/dist/pine-core/{p-0ab735aa.system.entry.js → p-4f4a2d2a.system.entry.js} +1 -1
  361. package/dist/pine-core/{p-7e2b934d.entry.js → p-5211aba7.entry.js} +1 -1
  362. package/dist/pine-core/{p-376f8286.entry.js → p-524b7d3f.entry.js} +1 -1
  363. package/dist/pine-core/{p-3f12afb1.entry.js → p-5613c64c.entry.js} +1 -1
  364. package/dist/pine-core/p-5bb24cb4.entry.js +1 -0
  365. package/dist/pine-core/{p-d7865297.entry.js → p-5bb80b3c.entry.js} +1 -1
  366. package/dist/pine-core/{p-e3cd4627.system.entry.js → p-5c6e0a26.system.entry.js} +1 -1
  367. package/dist/pine-core/{p-51dff5af.system.entry.js → p-5db57551.system.entry.js} +1 -1
  368. package/dist/pine-core/{p-f94667b5.entry.js → p-5eb00ba5.entry.js} +1 -1
  369. package/dist/pine-core/p-61756414.system.entry.js +1 -0
  370. package/dist/pine-core/{p-e5bbe8cc.entry.js → p-682e9e8f.entry.js} +1 -1
  371. package/dist/pine-core/p-6965db1d.entry.js +1 -0
  372. package/dist/pine-core/{p-5aea1082.entry.js → p-6b180ee2.entry.js} +1 -1
  373. package/dist/pine-core/{p-d21739eb.entry.js → p-6c53962f.entry.js} +1 -1
  374. package/dist/pine-core/p-6da6520b.system.entry.js +3 -0
  375. package/dist/pine-core/p-6e3bf862.system.entry.js +1 -0
  376. package/dist/pine-core/{p-053b0898.system.entry.js → p-71c9f866.system.entry.js} +1 -1
  377. package/dist/pine-core/{p-78fd593d.system.entry.js → p-7265b94c.system.entry.js} +1 -1
  378. package/dist/pine-core/{p-9187c26c.system.entry.js → p-74229b5e.system.entry.js} +1 -1
  379. package/dist/pine-core/p-7512c2d4.system.entry.js +1 -0
  380. package/dist/pine-core/p-76db406d.system.entry.js +1 -0
  381. package/dist/pine-core/{p-e1653d75.system.entry.js → p-79aa5f43.system.entry.js} +1 -1
  382. package/dist/pine-core/p-7bbcda0f.system.entry.js +1 -0
  383. package/dist/pine-core/p-7c8946e6.entry.js +1 -0
  384. package/dist/pine-core/p-7c98c90a.system.entry.js +1 -0
  385. package/dist/pine-core/p-81292c03.system.entry.js +1 -0
  386. package/dist/pine-core/p-81300046.system.entry.js +1 -0
  387. package/dist/pine-core/{p-a11a9c40.system.entry.js → p-8252cd84.system.entry.js} +1 -1
  388. package/dist/pine-core/{p-d0d2a886.system.entry.js → p-84ba0e6a.system.entry.js} +1 -1
  389. package/dist/pine-core/{p-c1f8a187.entry.js → p-86fc0bfa.entry.js} +1 -1
  390. package/dist/pine-core/p-90692bb6.entry.js +1 -0
  391. package/dist/pine-core/{p-2eb8d697.system.entry.js → p-90ea812c.system.entry.js} +1 -1
  392. package/dist/pine-core/p-948826db.entry.js +1 -0
  393. package/dist/pine-core/{p-714bfdbd.entry.js → p-9b66642d.entry.js} +1 -1
  394. package/dist/pine-core/p-BJgZHCWe.js +1 -0
  395. package/dist/pine-core/p-BPqJgIq6.js +1 -0
  396. package/dist/pine-core/p-BSRFZB4I.system.js +1 -0
  397. package/dist/pine-core/p-BZsj6eAn.system.js +1 -0
  398. package/dist/pine-core/p-CB8Yh0Nt.js +2 -0
  399. package/dist/pine-core/{p-B2qvvB3q.system.js → p-CBu9UrXr.system.js} +1 -1
  400. package/dist/pine-core/p-COtCa1Md.system.js +1 -0
  401. package/dist/pine-core/p-CTQZ2fkv.system.js +1 -0
  402. package/dist/pine-core/{p-B2nn1gSd.js → p-CnM3cCof.js} +1 -1
  403. package/dist/pine-core/p-CnO10OxH.system.js +1 -0
  404. package/dist/pine-core/p-D-akEPIU.system.js +2 -0
  405. package/dist/pine-core/p-DHPzKMVS.js +1 -0
  406. package/dist/pine-core/p-Dx8tEj0U.system.js +1 -0
  407. package/dist/pine-core/p-DxI-61GK.js +1 -0
  408. package/dist/pine-core/p-OYK5-nwe.js +1 -0
  409. package/dist/pine-core/p-a10af84b.system.entry.js +1 -0
  410. package/dist/pine-core/p-a21b530a.system.entry.js +1 -0
  411. package/dist/pine-core/p-a85535a4.system.entry.js +1 -0
  412. package/dist/pine-core/p-abc0fe2f.system.entry.js +1 -0
  413. package/dist/pine-core/p-ac6a5bf0.entry.js +1 -0
  414. package/dist/pine-core/{p-f7a5062f.entry.js → p-b0526f98.entry.js} +1 -1
  415. package/dist/pine-core/p-b130b716.entry.js +1 -0
  416. package/dist/pine-core/{p-09413b07.entry.js → p-b195d345.entry.js} +1 -1
  417. package/dist/pine-core/{p-e3600c53.entry.js → p-b4923acf.entry.js} +1 -1
  418. package/dist/pine-core/{p-e410b0d8.system.entry.js → p-b4e440e3.system.entry.js} +1 -1
  419. package/dist/pine-core/p-b8693a87.entry.js +1 -0
  420. package/dist/pine-core/{p-e95225cf.entry.js → p-b8ada1eb.entry.js} +1 -1
  421. package/dist/pine-core/{p-1614c000.system.entry.js → p-bb85bbb5.system.entry.js} +1 -1
  422. package/dist/pine-core/{p-fec298bb.system.entry.js → p-bbc539bc.system.entry.js} +2 -2
  423. package/dist/pine-core/{p-f21aea90.system.entry.js → p-bf833976.system.entry.js} +1 -1
  424. package/dist/pine-core/{p-c5ce6dea.entry.js → p-c23097af.entry.js} +1 -1
  425. package/dist/pine-core/p-c2dd84bd.system.entry.js +1 -0
  426. package/dist/pine-core/{p-869ed1c0.system.entry.js → p-c4520b04.system.entry.js} +1 -1
  427. package/dist/pine-core/{p-3605a5f5.entry.js → p-c4f68dfc.entry.js} +1 -1
  428. package/dist/pine-core/{p-d9ffb2d9.system.entry.js → p-c6fa6744.system.entry.js} +1 -1
  429. package/dist/pine-core/p-c7dba815.entry.js +1 -0
  430. package/dist/pine-core/p-cb6f55ff.entry.js +1 -0
  431. package/dist/pine-core/p-cfb8fa7c.system.entry.js +1 -0
  432. package/dist/pine-core/{p-86bc2a0c.entry.js → p-d0d7d1d4.entry.js} +1 -1
  433. package/dist/pine-core/{p-b109de2e.entry.js → p-d1112c8a.entry.js} +1 -1
  434. package/dist/pine-core/p-d4e91ad9.system.entry.js +1 -0
  435. package/dist/pine-core/{p-7b1e3e91.entry.js → p-da018dcb.entry.js} +1 -1
  436. package/dist/pine-core/{p-c09dab1a.system.entry.js → p-dc27abe6.system.entry.js} +1 -1
  437. package/dist/pine-core/{p-3eca0b00.entry.js → p-dc343b6d.entry.js} +1 -1
  438. package/dist/pine-core/p-dde753e7.system.entry.js +1 -0
  439. package/dist/pine-core/{p-f35596cd.entry.js → p-dea3a582.entry.js} +1 -1
  440. package/dist/pine-core/p-df29fd41.entry.js +1 -0
  441. package/dist/pine-core/{p-1f307e59.system.entry.js → p-e4e2eae2.system.entry.js} +1 -1
  442. package/dist/pine-core/{p-51b8490d.entry.js → p-e8668178.entry.js} +1 -1
  443. package/dist/pine-core/{p-5dd393cd.system.entry.js → p-eb28d21d.system.entry.js} +1 -1
  444. package/dist/pine-core/p-eb71a82d.system.entry.js +1 -0
  445. package/dist/pine-core/{p-8eef80d5.entry.js → p-ed63873c.entry.js} +1 -1
  446. package/dist/pine-core/{p-12182cab.entry.js → p-f4f6dc36.entry.js} +1 -1
  447. package/dist/pine-core/{p-be819bff.system.entry.js → p-f67a0ca8.system.entry.js} +1 -1
  448. package/dist/pine-core/p-fc455968.entry.js +1 -0
  449. package/dist/pine-core/{p-92ef1608.system.entry.js → p-fe3e2ebd.system.entry.js} +1 -1
  450. package/dist/pine-core/pine-core.css +1 -1
  451. package/dist/pine-core/pine-core.esm.js +1 -1
  452. package/dist/pine-core/pine-core.js +1 -1
  453. package/dist/types/components/pds-button/pds-button.d.ts +10 -0
  454. package/dist/types/components/pds-checkbox/pds-checkbox.d.ts +5 -0
  455. package/dist/types/components/pds-chip/pds-chip.d.ts +12 -0
  456. package/dist/types/components/pds-combobox/combobox-interface.d.ts +17 -0
  457. package/dist/types/components/pds-combobox/grouped-options-panel.d.ts +105 -0
  458. package/dist/types/components/pds-combobox/pds-combobox.d.ts +235 -3
  459. package/dist/types/components/pds-datepicker/calendar-portal.d.ts +43 -0
  460. package/dist/types/components/pds-datepicker/date-utils.d.ts +48 -0
  461. package/dist/types/components/pds-datepicker/datepicker-interface.d.ts +30 -0
  462. package/dist/types/components/pds-datepicker/pds-datepicker.d.ts +489 -0
  463. package/dist/types/components/pds-datepicker/presets.d.ts +23 -0
  464. package/dist/types/components/pds-datepicker/value.d.ts +47 -0
  465. package/dist/types/components/pds-dropdown-menu/pds-dropdown-menu.d.ts +1 -0
  466. package/dist/types/components/pds-modal/pds-modal.d.ts +9 -2
  467. package/dist/types/components/pds-multiselect/multiselect-interface.d.ts +19 -0
  468. package/dist/types/components/pds-multiselect/pds-multiselect.d.ts +56 -1
  469. package/dist/types/components/pds-popover/pds-popover.d.ts +1 -0
  470. package/dist/types/components/pds-switch/pds-switch.d.ts +5 -0
  471. package/dist/types/components.d.ts +702 -11
  472. package/dist/types/utils/closest.d.ts +2 -0
  473. package/dist/types/utils/types.d.ts +2 -1
  474. package/dist/types/utils/utils.d.ts +25 -2
  475. package/dist/vscode.html-data.json +497 -7
  476. package/hydrate/index.js +4059 -377
  477. package/hydrate/index.mjs +4059 -377
  478. package/package.json +2 -2
  479. package/dist/cjs/closest-BZXvuB_Y.js +0 -11
  480. package/dist/esm/closest-DnUJodVd.js +0 -9
  481. package/dist/esm-es5/closest-DnUJodVd.js +0 -1
  482. package/dist/esm-es5/floating-ui.dom-DDtwbwIl.js +0 -1
  483. package/dist/esm-es5/index-CTLu1TM4.js +0 -2
  484. package/dist/esm-es5/index-DdPaBxPo.js +0 -1
  485. package/dist/esm-es5/utils-tJa4KYNO.js +0 -1
  486. package/dist/pine-core/p--Llh7wKm.system.js +0 -1
  487. package/dist/pine-core/p-02a47ebb.entry.js +0 -1
  488. package/dist/pine-core/p-12ec8186.system.entry.js +0 -1
  489. package/dist/pine-core/p-17dc3f87.entry.js +0 -1
  490. package/dist/pine-core/p-18a59cdd.system.entry.js +0 -1
  491. package/dist/pine-core/p-2016c878.entry.js +0 -1
  492. package/dist/pine-core/p-222ff58e.system.entry.js +0 -1
  493. package/dist/pine-core/p-324a7ae4.system.entry.js +0 -1
  494. package/dist/pine-core/p-32f10aa9.entry.js +0 -1
  495. package/dist/pine-core/p-33c0f3c9.system.entry.js +0 -1
  496. package/dist/pine-core/p-34e38037.system.entry.js +0 -1
  497. package/dist/pine-core/p-3873f55b.entry.js +0 -1
  498. package/dist/pine-core/p-3aec9079.entry.js +0 -1
  499. package/dist/pine-core/p-3c229a00.system.entry.js +0 -1
  500. package/dist/pine-core/p-43338f77.system.entry.js +0 -1
  501. package/dist/pine-core/p-4661259e.system.entry.js +0 -1
  502. package/dist/pine-core/p-585ad368.system.entry.js +0 -1
  503. package/dist/pine-core/p-5d585aa8.entry.js +0 -1
  504. package/dist/pine-core/p-613a64f1.entry.js +0 -1
  505. package/dist/pine-core/p-630f33d6.system.entry.js +0 -1
  506. package/dist/pine-core/p-63686e2a.system.entry.js +0 -1
  507. package/dist/pine-core/p-6f7a7d55.entry.js +0 -1
  508. package/dist/pine-core/p-6fd863e5.system.entry.js +0 -1
  509. package/dist/pine-core/p-76286a68.system.entry.js +0 -1
  510. package/dist/pine-core/p-7691a875.system.entry.js +0 -3
  511. package/dist/pine-core/p-7e44106e.entry.js +0 -1
  512. package/dist/pine-core/p-84ade640.entry.js +0 -1
  513. package/dist/pine-core/p-8be301b6.entry.js +0 -1
  514. package/dist/pine-core/p-8cc270f0.system.entry.js +0 -1
  515. package/dist/pine-core/p-8cea0d7a.system.entry.js +0 -1
  516. package/dist/pine-core/p-957dbb0c.entry.js +0 -1
  517. package/dist/pine-core/p-BG6uGRQj.system.js +0 -1
  518. package/dist/pine-core/p-BKsJZTyn.js +0 -1
  519. package/dist/pine-core/p-BrLs99ML.system.js +0 -1
  520. package/dist/pine-core/p-CTLu1TM4.js +0 -2
  521. package/dist/pine-core/p-Cr7-rJtJ.system.js +0 -1
  522. package/dist/pine-core/p-DAIsVNlL.system.js +0 -2
  523. package/dist/pine-core/p-DDtwbwIl.js +0 -1
  524. package/dist/pine-core/p-DdPaBxPo.js +0 -1
  525. package/dist/pine-core/p-DnUJodVd.js +0 -1
  526. package/dist/pine-core/p-Dr-FzkdE.system.js +0 -1
  527. package/dist/pine-core/p-a4a58477.entry.js +0 -1
  528. package/dist/pine-core/p-a98bf89f.system.entry.js +0 -1
  529. package/dist/pine-core/p-ac0098fd.entry.js +0 -1
  530. package/dist/pine-core/p-b32e3174.system.entry.js +0 -1
  531. package/dist/pine-core/p-be4a6cc2.entry.js +0 -1
  532. package/dist/pine-core/p-c0b3f2c1.system.entry.js +0 -1
  533. package/dist/pine-core/p-c8bffb08.system.entry.js +0 -1
  534. package/dist/pine-core/p-cf24ed1e.system.entry.js +0 -1
  535. package/dist/pine-core/p-d12ef40d.entry.js +0 -1
  536. package/dist/pine-core/p-d280741f.entry.js +0 -1
  537. package/dist/pine-core/p-d415bb01.entry.js +0 -2
  538. package/dist/pine-core/p-d43db3ba.entry.js +0 -1
  539. package/dist/pine-core/p-db5703f2.entry.js +0 -1
  540. package/dist/pine-core/p-dd6d9807.system.entry.js +0 -1
  541. package/dist/pine-core/p-e0da17aa.system.entry.js +0 -1
  542. package/dist/pine-core/p-ed297f3e.entry.js +0 -1
  543. package/dist/pine-core/p-fbf5f315.entry.js +0 -1
  544. package/dist/pine-core/p-kYMYDfJY.system.js +0 -1
  545. package/dist/pine-core/p-tJa4KYNO.js +0 -1
@@ -1,8 +1,9 @@
1
1
  import { h, Host } from "@stencil/core";
2
2
  import { assignDescription, isSpecTest, messageId } from "../../utils/form";
3
3
  import { autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
4
- import { debounceEvent } from "../../utils/utils";
4
+ import { cancelDebouncedEvent, debounceEvent, flushDebouncedEvent } from "../../utils/utils";
5
5
  import DOMPurify from "dompurify";
6
+ import { FlyoutController, buildPanelEntries, findGroup, groupIconOf, groupLabelOf, isGroupDisabled, isOptionDisabled, panelColumn, parentOptgroupOf, } from "./grouped-options-panel";
6
7
  /**
7
8
  * @slot option - Option elements for the combobox dropdown
8
9
  * @slot trigger-content - Custom content for the button trigger when customTriggerContent is true
@@ -50,6 +51,35 @@ export class PdsCombobox {
50
51
  * @default 'host'
51
52
  */
52
53
  this.dropdownMount = 'host';
54
+ /**
55
+ * Accessible prefix for the row that returns to the group list when `group-display` is
56
+ * `drill-in`, read before the open group's label. Pass a translated string to localize it; defaults to English.
57
+ * @defaultValue 'Back'
58
+ */
59
+ this.drillInBackLabel = 'Back';
60
+ /**
61
+ * Maximum width of a group's flyout when `group-display` is `flyout`. Any valid CSS width value.
62
+ * @default '380px'
63
+ */
64
+ this.flyoutMaxWidth = '380px';
65
+ /**
66
+ * Minimum width of a group's flyout when `group-display` is `flyout`. Any valid CSS width value.
67
+ * The flyout is as wide as its options, between this and `flyout-max-width`.
68
+ * @default '280px'
69
+ */
70
+ this.flyoutMinWidth = '280px';
71
+ /**
72
+ * How option groups (`<optgroup>`, or the `group` field of `options`) are shown in the dropdown.
73
+ * `inline` lists each group's label above its options. `flyout` lists the groups, and opens a
74
+ * group's options in a panel beside it on hover or click; while searching, the matching options
75
+ * are listed under their group labels, and search also matches group labels. `flyout` groups
76
+ * by `<optgroup>` (or `group`) only: options under a `<pds-text>` heading are listed ungrouped.
77
+ * `drill-in` lists the groups too, but a click replaces the list with that group's options and a
78
+ * row back to the groups; use it where nothing can open beside the dropdown, such as a side
79
+ * panel. `flyout` falls back to `drill-in` when there is no room for a flyout on either side.
80
+ * @default 'inline'
81
+ */
82
+ this.groupDisplay = 'inline';
53
83
  /**
54
84
  * Visually hides the label text for instances where only the combobox should be displayed.
55
85
  * The visible `<label>` is omitted (same pattern as `pds-select`) so label spacing does not reserve layout;
@@ -63,6 +93,23 @@ export class PdsCombobox {
63
93
  * @default 'filter'
64
94
  */
65
95
  this.mode = 'filter';
96
+ /**
97
+ * Shows a search field at the top of the dropdown for the `button` and `chip` triggers, which
98
+ * can't be typed into. Search filters the options the same way typing in the `input` trigger
99
+ * does; in a grouped display it also matches group labels.
100
+ * @default false
101
+ */
102
+ this.panelSearch = false;
103
+ /**
104
+ * Accessible label for the search input inside the dropdown panel (`panel-search`). Pass a translated string to localize it; defaults to English.
105
+ * @defaultValue 'Search options'
106
+ */
107
+ this.searchOptionsLabel = 'Search options';
108
+ /**
109
+ * Placeholder text for the search input inside the dropdown panel (`panel-search`). Pass a translated string to localize it; defaults to English.
110
+ * @defaultValue 'Find...'
111
+ */
112
+ this.searchPlaceholder = 'Find...';
66
113
  /**
67
114
  * Determines the combobox trigger: 'input' (editable input), 'button' (button-like, non-editable), or 'chip' (chip-like, non-editable).
68
115
  * @default 'input'
@@ -135,6 +182,30 @@ export class PdsCombobox {
135
182
  * Internal state for dropdown open/close
136
183
  */
137
184
  this.isOpen = false;
185
+ /**
186
+ * The optgroup whose flyout is open, when `groupDisplay` is `flyout`.
187
+ */
188
+ this.openGroup = null;
189
+ /**
190
+ * Which way drill-in just moved, so the list slides that way: `in` to a group, `out` back to the
191
+ * groups. Cleared once the slide ends, so later renders don't animate.
192
+ */
193
+ this.drillDirection = null;
194
+ /**
195
+ * The group row highlighted in the grouped panel. It counts only while no option is
196
+ * highlighted (`highlightedIndex` is -1); see `highlightedGroupRow`.
197
+ */
198
+ this.highlightedGroup = null;
199
+ /** The keys, not the pointer, moved the highlight last; the focus ring shows only then. */
200
+ this.keyboardNavigating = false;
201
+ /**
202
+ * What's typed in the dropdown's own search field (`panelSearch`).
203
+ */
204
+ this.panelQuery = '';
205
+ /**
206
+ * `groupDisplay` is `flyout` but there's no room beside the dropdown, so groups drill in instead.
207
+ */
208
+ this.flyoutFallback = false;
138
209
  /**
139
210
  * Internal state for the currently selected option
140
211
  */
@@ -164,12 +235,39 @@ export class PdsCombobox {
164
235
  this.portalEl = null;
165
236
  this.bodyPortalAutoUpdateActive = false;
166
237
  this.isUpdatingFromSelection = false;
238
+ this.flyout = new FlyoutController(() => this.openGroup, group => {
239
+ this.openGroup = group;
240
+ });
241
+ /** The groups in the flyout panel's last render, so a row's index finds its optgroup. */
242
+ this.renderedGroups = [];
243
+ /** Room for a flyout was checked for this open. */
244
+ this.flyoutRoomChecked = false;
245
+ /** The highlighted option sits in a flyout that hasn't rendered yet; focus it after the next render. */
246
+ this.pendingOptionFocus = false;
247
+ /** Group rows' DOM ids, stable across renders so `aria-activedescendant` can point at a row. */
248
+ this.groupIds = new WeakMap();
249
+ this.groupIdCount = 0;
167
250
  /**
168
251
  * In filter mode, after choosing an option the input still shows the label and would
169
252
  * otherwise filter the list to matching substrings only. While this flag is true,
170
253
  * the list shows every option until the user types (then normal filtering applies).
171
254
  */
172
255
  this.expandFilterListWhileOpen = false;
256
+ this.onDrillAnimationEnd = (event) => {
257
+ var _a;
258
+ // Only the slide's own end: slotted consumer content can animate inside the list too.
259
+ if ((_a = event.animationName) === null || _a === void 0 ? void 0 : _a.startsWith('pds-combobox-drill'))
260
+ this.drillDirection = null;
261
+ };
262
+ /** `<label for>` only works on the input trigger; for a div trigger a click on the label focuses it. */
263
+ this.onLabelClick = () => {
264
+ var _a;
265
+ if (this.trigger !== 'input' && !this.disabled) {
266
+ (_a = this.triggerEl) === null || _a === void 0 ? void 0 : _a.focus();
267
+ }
268
+ };
269
+ /** The query behind the options loaded from `async-url`. */
270
+ this.lastFetchedQuery = '';
173
271
  this.handleInput = (e) => {
174
272
  const target = e.target;
175
273
  this.clearExpandFilterList();
@@ -206,6 +304,7 @@ export class PdsCombobox {
206
304
  this.handleKeyDown = (e) => {
207
305
  if (!this.isOpen && (e.key === 'ArrowDown' || e.key === 'ArrowUp' || (e.altKey && e.key === 'ArrowDown'))) {
208
306
  e.preventDefault();
307
+ this.keyboardNavigating = true;
209
308
  this.isOpen = true;
210
309
  this.prepareExpandFilterListOnOpen();
211
310
  this.filterOptions();
@@ -227,75 +326,53 @@ export class PdsCombobox {
227
326
  if (!this.isOpen) {
228
327
  return;
229
328
  }
329
+ if (this.handleGroupedPanelKey(e))
330
+ return;
331
+ // The input trigger's text keeps Space, Home and End while the grouped panel shows.
332
+ if (this.showsGroupedPanel && this.trigger === 'input' && (e.key === ' ' || e.key === 'Home' || e.key === 'End'))
333
+ return;
230
334
  // Get only the option elements (skip group labels) for navigation
231
335
  const selectableOptions = this.filteredItems.filter(item => item.tagName === 'OPTION');
232
336
  switch (e.key) {
233
337
  case 'ArrowDown':
234
338
  e.preventDefault();
235
- // If no option is highlighted and we have options, start at 0
236
- if (this.highlightedIndex < 0 && selectableOptions.length > 0) {
237
- this.highlightedIndex = 0;
238
- }
239
- else {
240
- this.highlightedIndex = Math.min(this.highlightedIndex + 1, selectableOptions.length - 1);
241
- }
242
- this.updateOptionFocus();
339
+ // If no option is highlighted, start at the first
340
+ this.highlightOptionAt(selectableOptions, this.highlightedIndex < 0 ? 0 : this.highlightedIndex + 1);
243
341
  break;
244
342
  case 'ArrowUp':
245
343
  e.preventDefault();
246
- // If no option is highlighted and we have options, start at last option
247
- if (this.highlightedIndex < 0 && selectableOptions.length > 0) {
248
- this.highlightedIndex = selectableOptions.length - 1;
249
- }
250
- else {
251
- this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
252
- }
253
- this.updateOptionFocus();
344
+ // If no option is highlighted, start at the last
345
+ this.highlightOptionAt(selectableOptions, this.highlightedIndex < 0 ? selectableOptions.length - 1 : this.highlightedIndex - 1);
254
346
  break;
255
347
  case 'Home':
256
348
  e.preventDefault();
257
- if (selectableOptions.length > 0) {
258
- this.highlightedIndex = 0;
259
- this.updateOptionFocus();
260
- }
349
+ this.highlightOptionAt(selectableOptions, 0);
261
350
  break;
262
351
  case 'End':
263
352
  e.preventDefault();
264
- if (selectableOptions.length > 0) {
265
- this.highlightedIndex = selectableOptions.length - 1;
266
- this.updateOptionFocus();
267
- }
353
+ this.highlightOptionAt(selectableOptions, selectableOptions.length - 1);
268
354
  break;
269
355
  case 'PageDown':
270
356
  e.preventDefault();
271
- if (selectableOptions.length > 0) {
272
- const nextIndex = Math.min(this.highlightedIndex + 10, selectableOptions.length - 1);
273
- this.highlightedIndex = nextIndex;
274
- this.updateOptionFocus();
275
- }
357
+ this.highlightOptionAt(selectableOptions, this.highlightedIndex + 10);
276
358
  break;
277
359
  case 'PageUp':
278
360
  e.preventDefault();
279
- if (selectableOptions.length > 0) {
280
- const prevIndex = Math.max(this.highlightedIndex - 10, 0);
281
- this.highlightedIndex = prevIndex;
282
- this.updateOptionFocus();
283
- }
361
+ this.highlightOptionAt(selectableOptions, this.highlightedIndex - 10);
284
362
  break;
285
363
  case 'Enter':
364
+ case ' ': {
286
365
  e.preventDefault();
287
- if (this.highlightedIndex >= 0 && this.highlightedIndex < selectableOptions.length) {
288
- this.handleOptionClick(selectableOptions[this.highlightedIndex]);
289
- this.restoreFocusToTrigger();
290
- }
291
- break;
292
- case ' ':
293
- e.preventDefault();
294
- if (this.highlightedIndex >= 0 && this.highlightedIndex < selectableOptions.length) {
295
- this.handleOptionClick(selectableOptions[this.highlightedIndex]);
366
+ // An option in a flyout the pointer has since closed isn't on screen; don't choose it.
367
+ if (this.showsGroupedPanel && this.activeDescendantId === undefined)
368
+ break;
369
+ const option = selectableOptions[this.highlightedIndex];
370
+ if (option !== undefined && !isOptionDisabled(option)) {
371
+ this.handleOptionClick(option);
296
372
  this.restoreFocusToTrigger();
297
373
  }
298
374
  break;
375
+ }
299
376
  case 'Escape':
300
377
  e.preventDefault();
301
378
  this.isOpen = false;
@@ -320,12 +397,53 @@ export class PdsCombobox {
320
397
  const idx = Number(event.currentTarget.getAttribute('data-option-index'));
321
398
  const selectableOptions = this.filteredItems.filter(item => item.tagName === 'OPTION');
322
399
  const option = selectableOptions[idx];
400
+ if (isOptionDisabled(option))
401
+ return;
323
402
  this.handleOptionClick(option);
403
+ // The search field closes with the panel; send focus back to the trigger instead of losing it.
404
+ // (Enter and Space already restore focus in the key handler.)
405
+ if (this.usesPanelSearch)
406
+ this.restoreFocusToTrigger();
324
407
  };
325
408
  // Event handler for mouse enter on option
326
409
  this.onOptionMouseEnter = (event) => {
327
410
  const idx = Number(event.currentTarget.getAttribute('data-option-index'));
328
411
  this.highlightedIndex = idx;
412
+ this.keyboardNavigating = false;
413
+ };
414
+ this.onGroupRowMouseEnter = (event) => {
415
+ const group = this.groupFromEvent(event);
416
+ if (group !== null)
417
+ this.flyout.enter(group);
418
+ };
419
+ this.onGroupRowMouseLeave = (event) => {
420
+ const group = this.groupFromEvent(event);
421
+ if (group !== null)
422
+ this.flyout.leave(group);
423
+ };
424
+ // The pointer highlights a group's row as it does an option, so the keys carry on from there.
425
+ this.onGroupRowTriggerMouseEnter = (event) => {
426
+ const group = this.groupFromEvent(event);
427
+ if (group !== null)
428
+ this.highlightGroupRowByPointer(group);
429
+ };
430
+ this.onGroupBackMouseEnter = () => {
431
+ if (this.openGroup !== null)
432
+ this.highlightGroupRowByPointer(this.openGroup);
433
+ };
434
+ this.onGroupRowClick = (event) => {
435
+ var _a;
436
+ const group = this.groupFromEvent(event);
437
+ if (group === null)
438
+ return;
439
+ this.flyout.open(group);
440
+ // Highlight the group's first option, so the arrow keys carry on from the group just opened
441
+ // rather than from wherever the highlight was before.
442
+ const first = (_a = this.renderedGroups.find(entry => entry.optgroup === group)) === null || _a === void 0 ? void 0 : _a.options[0];
443
+ if (first !== undefined) {
444
+ this.highlightedIndex = first.index;
445
+ this.updateAriaActiveDescendant();
446
+ }
329
447
  };
330
448
  // Prevent blur on mousedown
331
449
  this.onOptionMouseDown = (event) => {
@@ -342,10 +460,7 @@ export class PdsCombobox {
342
460
  if (this.isOpen) {
343
461
  this.prepareExpandFilterListOnOpen();
344
462
  this.filterOptions();
345
- // Trigger initial fetch if async and no options loaded yet
346
- if (this.asyncUrl && this.internalOptions.length === 0) {
347
- this.debouncedFetchAsyncOptions(this.displayText, 1);
348
- }
463
+ this.fetchAsyncOptionsOnOpen();
349
464
  // Set highlighted index and prepare for keyboard navigation
350
465
  this.setInitialHighlightedIndex();
351
466
  // For button trigger, prepare for arrow-key navigation mode
@@ -360,12 +475,17 @@ export class PdsCombobox {
360
475
  };
361
476
  // Handler for button trigger keyboard events
362
477
  this.onButtonTriggerKeyDown = (e) => {
478
+ // Escape closes an open group before the dropdown. Other keys reach the tree through handleKeyDown.
479
+ if (this.isOpen && e.key === 'Escape' && this.handleGroupedPanelKey(e))
480
+ return;
363
481
  if ((e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown' || e.key === 'ArrowUp') && !this.isOpen) {
364
482
  e.preventDefault();
365
483
  e.stopPropagation(); // Prevent the event from bubbling and triggering click
484
+ this.keyboardNavigating = true;
366
485
  this.isOpen = true;
367
486
  this.prepareExpandFilterListOnOpen();
368
487
  this.filterOptions();
488
+ this.fetchAsyncOptionsOnOpen();
369
489
  // Set highlighted index immediately
370
490
  this.setInitialHighlightedIndex();
371
491
  setTimeout(() => {
@@ -398,11 +518,13 @@ export class PdsCombobox {
398
518
  };
399
519
  // Close dropdown when focus leaves the combobox
400
520
  this.onComboboxFocusOut = (event) => {
401
- var _a, _b;
521
+ var _a, _b, _c;
402
522
  const relatedTarget = event.relatedTarget;
403
523
  const isRelatedTargetInListbox = relatedTarget &&
404
524
  (((_a = this.listboxEl) === null || _a === void 0 ? void 0 : _a.contains(relatedTarget)) || ((_b = this.portalEl) === null || _b === void 0 ? void 0 : _b.contains(relatedTarget)));
405
- const isRelatedTargetInCombobox = this.el.contains(relatedTarget);
525
+ // `contains` doesn't cross into the shadow root, where the trigger lives; with panel search,
526
+ // focus leaves the search field for the trigger whenever it's clicked to close.
527
+ const isRelatedTargetInCombobox = relatedTarget !== null && (this.el.contains(relatedTarget) || !!((_c = this.el.shadowRoot) === null || _c === void 0 ? void 0 : _c.contains(relatedTarget)));
406
528
  // Don't close if focus is moving to an option in the listbox or staying within the combobox
407
529
  if (!isRelatedTargetInCombobox && !isRelatedTargetInListbox) {
408
530
  this.isOpen = false;
@@ -425,11 +547,58 @@ export class PdsCombobox {
425
547
  // Load more when near bottom (within 50px)
426
548
  if (scrollBottom < 50) {
427
549
  this.pdsComboboxLoadOptions.emit({
428
- query: this.displayText,
550
+ query: this.activeQuery,
429
551
  page: this.currentPage + 1,
430
552
  });
431
- this.debouncedFetchAsyncOptions(this.displayText, this.currentPage + 1);
553
+ this.debouncedFetchAsyncOptions(this.activeQuery, this.currentPage + 1);
554
+ }
555
+ };
556
+ // A press on the panel's padding, captions or status rows keeps focus in the search field;
557
+ // otherwise it would blur the field, closing a portaled panel or stranding the keys.
558
+ this.onPanelMouseDown = (event) => {
559
+ if (event.target !== this.panelSearchEl)
560
+ event.preventDefault();
561
+ };
562
+ this.onPanelSearchInput = (event) => {
563
+ this.setPanelQuery(event.target.value);
564
+ };
565
+ // Space, Home and End edit the search text rather than moving through the options.
566
+ this.onPanelSearchKeyDown = (event) => {
567
+ if (event.key === ' ' || event.key === 'Home' || event.key === 'End')
568
+ return;
569
+ if (this.handlePanelTab(event))
570
+ return;
571
+ this.handleKeyDown(event);
572
+ };
573
+ /**
574
+ * Keys in the list while a drilled-in group hides the search field. Backspace, like ArrowLeft,
575
+ * steps back to the groups; a character starts a search of the whole list. Characters typed
576
+ * before the search field renders and takes focus still land on the list, so they add to the
577
+ * query rather than starting it again.
578
+ */
579
+ this.onPanelTreeKeyDown = (event) => {
580
+ if (this.handlePanelTab(event))
581
+ return;
582
+ if (event.key === 'Backspace') {
583
+ event.preventDefault();
584
+ if (!this.leaveDrilledInGroup() && this.panelQuery !== '')
585
+ this.setPanelQuery(this.panelQuery.slice(0, -1));
586
+ return;
587
+ }
588
+ // AltGr (Ctrl+Alt on Windows) and Option on macOS type characters too; Meta and Ctrl alone are
589
+ // shortcuts.
590
+ if (event.key.length === 1 && event.key !== ' ' && !event.metaKey && (!event.ctrlKey || event.altKey)) {
591
+ event.preventDefault();
592
+ this.flyout.reset();
593
+ // The query first, so leaving the group reads as search results, not a slide back.
594
+ this.setPanelQuery(this.panelQuery + event.key);
595
+ this.openGroup = null;
596
+ return;
432
597
  }
598
+ this.handleKeyDown(event);
599
+ };
600
+ this.onGroupBackClick = () => {
601
+ this.leaveDrilledInGroup();
433
602
  };
434
603
  }
435
604
  connectedCallback() {
@@ -466,15 +635,106 @@ export class PdsCombobox {
466
635
  this.deferredPortalTeardownId = undefined;
467
636
  }
468
637
  (_a = this.observer) === null || _a === void 0 ? void 0 : _a.disconnect();
638
+ cancelDebouncedEvent(this.pdsComboboxSearch);
469
639
  this.clearAsyncFetchState();
470
640
  this.removeBodyPortal();
641
+ this.flyout.reset();
471
642
  }
472
643
  componentDidRender() {
644
+ var _a;
473
645
  if (this.usesBodyPortal && this.isOpen) {
474
646
  this.syncBodyPortal();
475
647
  }
648
+ const row = (_a = this.listboxEl) === null || _a === void 0 ? void 0 : _a.querySelector('.pds-combobox__group-row--open');
649
+ const flyoutEl = row === null || row === void 0 ? void 0 : row.querySelector('.pds-combobox__flyout');
650
+ if (this.isOpen && this.listboxEl && row && flyoutEl) {
651
+ this.flyout.position(row, this.listboxEl, flyoutEl);
652
+ }
653
+ else {
654
+ this.flyout.stopPositioning();
655
+ }
656
+ // The row back is sticky over the group's options; scrolling an option into view stops below
657
+ // it. Set before any scroll below, and when it first applies, scroll the highlighted option
658
+ // again: the list keeps its offset on drilling in, and an earlier scroll had no padding.
659
+ if (this.panelTreeEl) {
660
+ const header = this.hidesPanelSearch ? this.panelTreeEl.querySelector('.pds-combobox__group-back-trigger') : null;
661
+ const padding = header ? `${header.offsetHeight}px` : '';
662
+ if (this.panelTreeEl.style.scrollPaddingBlockStart !== padding) {
663
+ this.panelTreeEl.style.scrollPaddingBlockStart = padding;
664
+ if (padding !== '' && this.highlightedIndex >= 0)
665
+ this.pendingOptionFocus = true;
666
+ }
667
+ }
668
+ if (this.pendingOptionFocus) {
669
+ this.pendingOptionFocus = false;
670
+ this.updateOptionFocus();
671
+ }
672
+ // Panel search keeps focus in the search field, or in the list while a group hides the field.
673
+ const panelFocusEl = this.hidesPanelSearch ? this.panelTreeEl : this.panelSearchEl;
674
+ if (this.isOpen && this.usesPanelSearch && panelFocusEl && panelFocusEl !== this.panelFocusEl) {
675
+ this.focusPanel(panelFocusEl);
676
+ }
677
+ }
678
+ focusPanel(el) {
679
+ var _a, _b;
680
+ this.panelFocusEl = el;
681
+ // Before the dropdown is positioned it sits in the page's flow; don't scroll to it.
682
+ el.focus({ preventScroll: true });
683
+ // A search started by typing in the list carries its first character; type on after it.
684
+ if (el === this.panelSearchEl && this.panelQuery !== '') {
685
+ (_b = (_a = this.panelSearchEl).setSelectionRange) === null || _b === void 0 ? void 0 : _b.call(_a, this.panelQuery.length, this.panelQuery.length);
686
+ }
687
+ this.updateAriaActiveDescendant();
688
+ }
689
+ /** Switching display mode leaves no flyout or hover timer from the last one. */
690
+ groupDisplayChanged() {
691
+ this.flyout.reset();
692
+ this.openGroup = null;
693
+ // The reset isn't a drill-out, so it doesn't slide either.
694
+ this.drillDirection = null;
695
+ }
696
+ openGroupChanged(next, previous) {
697
+ this.handPanelFocusToList();
698
+ // Only moves between the drill-in panel's views slide; search results don't.
699
+ if (!this.isOpen || this.effectiveGroupDisplay !== 'drill-in' || !this.showsGroupedPanel)
700
+ return;
701
+ if (next !== null && previous === null)
702
+ this.drillDirection = 'in';
703
+ else if (next === null && previous !== null)
704
+ this.drillDirection = 'out';
705
+ }
706
+ /** A flyout with no room beside the dropdown drills in, which can hide the focused search field too. */
707
+ flyoutFallbackChanged() {
708
+ this.handPanelFocusToList();
709
+ }
710
+ /**
711
+ * Drilling into a group hides the search field, so focus moves to the list before the field
712
+ * unmounts: focus lost with it would leave the combobox and close the dropdown.
713
+ */
714
+ handPanelFocusToList() {
715
+ if (this.isOpen && this.hidesPanelSearch && this.panelTreeEl)
716
+ this.focusPanel(this.panelTreeEl);
476
717
  }
477
718
  isOpenChanged(isOpen) {
719
+ if (!isOpen) {
720
+ this.drillDirection = null;
721
+ this.openGroup = null;
722
+ this.highlightedGroup = null;
723
+ this.keyboardNavigating = false;
724
+ this.flyout.reset();
725
+ if (this.panelQuery !== '') {
726
+ // The field reopens empty, so tell consumers who filter `options` from this event.
727
+ this.panelQuery = '';
728
+ this.pdsComboboxSearch.emit({ query: '' });
729
+ }
730
+ // And with `async-url`, start loading the unsearched list now, so it's ready by the reopen.
731
+ if (this.usesPanelSearch && this.asyncUrl && this.lastFetchedQuery !== '') {
732
+ this.debouncedFetchAsyncOptions('', 1);
733
+ }
734
+ this.panelFocusEl = undefined;
735
+ this.flyoutRoomChecked = false;
736
+ this.flyoutFallback = false;
737
+ }
478
738
  if (isOpen && this.deferredPortalTeardownId !== undefined) {
479
739
  clearTimeout(this.deferredPortalTeardownId);
480
740
  this.deferredPortalTeardownId = undefined;
@@ -507,17 +767,52 @@ export class PdsCombobox {
507
767
  get usesBodyPortal() {
508
768
  return this.dropdownMount === 'body';
509
769
  }
770
+ get labelId() {
771
+ return `${this.componentId}-label`;
772
+ }
773
+ /** One check for "the `<label>` is rendered", so the label and the reference to it can't disagree. */
774
+ get hasVisibleLabel() {
775
+ return Boolean(this.label) && !this.hideLabel;
776
+ }
777
+ /**
778
+ * The button and chip triggers are `div`s, which `<label for>` can't name, so they point at the
779
+ * visible label instead. With `hide-label` there is no visible label and `aria-label` names them.
780
+ * With panel search the trigger is a button, which has no value, so its own text (the selection
781
+ * or placeholder) joins the label in its name.
782
+ */
783
+ get triggerLabelledBy() {
784
+ if (!this.hasVisibleLabel)
785
+ return undefined;
786
+ return this.usesPanelSearch ? `${this.labelId} ${this.componentId}` : this.labelId;
787
+ }
788
+ /** The div triggers' `aria-label` when the label is hidden; with panel search it names the selection too. */
789
+ get triggerAriaLabel() {
790
+ if (!this.hideLabel)
791
+ return undefined;
792
+ const shown = this.selectedLabel || this.placeholder;
793
+ return this.usesPanelSearch && shown ? `${this.label}, ${shown}` : this.label;
794
+ }
510
795
  get listboxId() {
511
796
  return `${this.componentId}-listbox`;
512
797
  }
513
798
  optionDomId(optionIndex) {
514
799
  return `${this.componentId}-option-${optionIndex}`;
515
800
  }
801
+ groupDomId(optgroup) {
802
+ let id = this.groupIds.get(optgroup);
803
+ if (id === undefined) {
804
+ id = `${this.componentId}-group-${this.groupIdCount++}`;
805
+ this.groupIds.set(optgroup, id);
806
+ }
807
+ return id;
808
+ }
516
809
  setupDebounce() {
517
810
  const { pdsComboboxSearch, debounce, originalSearchEmitter } = this;
518
811
  this.pdsComboboxSearch = debounce === undefined
519
812
  ? originalSearchEmitter !== null && originalSearchEmitter !== void 0 ? originalSearchEmitter : pdsComboboxSearch
520
813
  : debounceEvent(pdsComboboxSearch, debounce);
814
+ // After the swap, so an emit made while flushing goes to the new emitter, not the old one.
815
+ flushDebouncedEvent(pdsComboboxSearch);
521
816
  }
522
817
  optionsChanged() {
523
818
  if (this.options) {
@@ -664,6 +959,8 @@ export class PdsCombobox {
664
959
  });
665
960
  if (page === 1) {
666
961
  this.internalOptions = formattedResults;
962
+ // Only once its results have loaded: a failed or aborted fetch leaves the last query's.
963
+ this.lastFetchedQuery = query;
667
964
  }
668
965
  else {
669
966
  this.internalOptions = [...this.internalOptions, ...formattedResults];
@@ -688,26 +985,76 @@ export class PdsCombobox {
688
985
  }
689
986
  }
690
987
  updateOptionsFromAsync() {
988
+ var _a;
691
989
  if (!this.asyncUrl && !this.options)
692
990
  return;
693
991
  // Clear existing slot-based options when using async
694
992
  this.optionEls = [];
695
993
  this.allItems = [];
696
- // Create option elements from async data
994
+ // Create option elements from async data. Grouped options are nested in an optgroup, as
995
+ // slotted markup would be, so grouping, filtering and labels work the same either way. Each
996
+ // group is listed where it first appears with all of its options, even when the data
997
+ // interleaves groups, so an option never renders under another group's label. Ungrouped
998
+ // options come first, so none reads as part of the group above it.
697
999
  const asyncOptions = this.options || this.internalOptions;
1000
+ const groups = new Map();
1001
+ const ungrouped = [];
1002
+ const groupEntries = [];
698
1003
  asyncOptions.forEach(opt => {
699
1004
  const optionEl = document.createElement('option');
700
1005
  optionEl.value = String(opt.id);
701
1006
  optionEl.textContent = opt.text;
702
- this.optionEls.push(optionEl);
703
- this.allItems.push(optionEl);
1007
+ if (opt.disabled === true)
1008
+ optionEl.setAttribute('disabled', '');
1009
+ // Only strings group an option or name an icon; anything else from JS is ignored, not coerced.
1010
+ if (typeof opt.group !== 'string' || opt.group === '') {
1011
+ ungrouped.push(optionEl);
1012
+ return;
1013
+ }
1014
+ let optgroup = groups.get(opt.group);
1015
+ if (!optgroup) {
1016
+ optgroup = document.createElement('optgroup');
1017
+ optgroup.label = opt.group;
1018
+ groups.set(opt.group, optgroup);
1019
+ groupEntries.push(optgroup);
1020
+ }
1021
+ if (!optgroup.hasAttribute('data-icon') && typeof opt.groupIcon === 'string' && opt.groupIcon !== '') {
1022
+ optgroup.setAttribute('data-icon', opt.groupIcon);
1023
+ }
1024
+ optgroup.appendChild(optionEl);
704
1025
  });
705
- // Re-apply preselection if a value is set but no option has been selected yet.
706
- // This handles the case where `value` is set before async options have loaded.
707
- if (this.value && !this.selectedOption) {
1026
+ [...ungrouped, ...groupEntries].forEach(entry => {
1027
+ if (entry.tagName === 'OPTGROUP') {
1028
+ this.allItems.push(entry);
1029
+ Array.from(entry.children).forEach(child => {
1030
+ this.optionEls.push(child);
1031
+ this.allItems.push(child);
1032
+ });
1033
+ }
1034
+ else {
1035
+ this.optionEls.push(entry);
1036
+ this.allItems.push(entry);
1037
+ }
1038
+ });
1039
+ // A rebuild replaces every optgroup. Follow the open group to its new element by label, so the
1040
+ // flyout stays open across reloads and pagination, or close it if the group is gone. The hover
1041
+ // timers still hold the old element, so they're cleared either way. filterOptions, below,
1042
+ // resets the highlight.
1043
+ if (this.openGroup !== null && !groupEntries.includes(this.openGroup)) {
1044
+ this.flyout.reset();
1045
+ this.openGroup = (_a = groups.get(this.openGroup.label)) !== null && _a !== void 0 ? _a : null;
1046
+ }
1047
+ // Re-apply the selection to the rebuilt options: when `value` is set before async options have
1048
+ // loaded, and after any rebuild, which detaches the previously selected option element.
1049
+ if (this.value && (!this.selectedOption || !this.optionEls.includes(this.selectedOption))) {
708
1050
  const matchingOption = this.optionEls.find(opt => opt.value === this.value);
709
1051
  if (matchingOption) {
1052
+ // Relinking a selection keeps the same value, so it keeps any text being typed too, which
1053
+ // the selectedOption watcher would otherwise reset to the label.
1054
+ const typed = this.selectedOption ? this.displayText : undefined;
710
1055
  this.setSelectedOption(matchingOption);
1056
+ if (typed !== undefined)
1057
+ this.displayText = typed;
711
1058
  }
712
1059
  }
713
1060
  this.filterOptions();
@@ -758,11 +1105,12 @@ export class PdsCombobox {
758
1105
  }
759
1106
  }
760
1107
  // For chip triggers, ensure we always have a selected option
761
- if (!initialSelected && this.trigger === 'chip' && this.optionEls.length > 0) {
762
- initialSelected = this.optionEls[0]; // Select first option as default
1108
+ const firstEnabled = this.optionEls.find(opt => !isOptionDisabled(opt));
1109
+ if (initialSelected === null && this.trigger === 'chip' && firstEnabled !== undefined) {
1110
+ initialSelected = firstEnabled;
763
1111
  this.value = initialSelected.value;
764
1112
  this.displayText = this.getOptionLabel(initialSelected);
765
- console.warn('PDS Combobox: Chip triggers should always have a selected option. Automatically selected the first option.');
1113
+ console.warn('PDS Combobox: Chip triggers should always have a selected option. Automatically selected the first enabled option.');
766
1114
  }
767
1115
  // Update selection if we found a different selected option or if we don't have one
768
1116
  if (initialSelected && initialSelected !== this.selectedOption) {
@@ -850,16 +1198,26 @@ export class PdsCombobox {
850
1198
  }
851
1199
  }
852
1200
  filterOptions() {
1201
+ // A new list replaces the rows a slide was animating, so its end event may never come.
1202
+ this.drillDirection = null;
853
1203
  // Ensure allItems includes optionEls if not already populated (for edge cases)
854
1204
  if (this.allItems.length === 0 && this.optionEls.length > 0) {
855
1205
  this.allItems = [...this.optionEls];
856
1206
  }
857
- if (this.mode === 'select-only' || this.expandFilterListWhileOpen) {
1207
+ const searchesPanel = this.usesPanelSearch;
1208
+ if ((this.mode === 'select-only' && !searchesPanel) || this.expandFilterListWhileOpen || (searchesPanel && this.panelQuery === '')) {
858
1209
  this.filteredItems = [...this.allItems];
859
1210
  }
860
1211
  else {
861
- const val = this.displayText.toLowerCase();
1212
+ const val = (searchesPanel ? this.panelQuery : this.displayText).toLowerCase();
862
1213
  const filteredOptions = this.optionEls.filter(option => {
1214
+ // Outside inline mode a group's label is part of what search matches, so "tag" finds
1215
+ // every option in the Tag group.
1216
+ if (this.groupDisplay !== 'inline') {
1217
+ const groupLabel = groupLabelOf(option);
1218
+ if (groupLabel !== null && groupLabel.toLowerCase().includes(val))
1219
+ return true;
1220
+ }
863
1221
  // For layout options, search both text content and data-search-text attribute
864
1222
  if (this.isOptionLayout(option)) {
865
1223
  const searchText = option.getAttribute('data-search-text') || option.textContent || '';
@@ -894,6 +1252,7 @@ export class PdsCombobox {
894
1252
  });
895
1253
  }
896
1254
  this.highlightedIndex = -1;
1255
+ this.highlightedGroup = null;
897
1256
  }
898
1257
  clearExpandFilterList() {
899
1258
  this.expandFilterListWhileOpen = false;
@@ -923,7 +1282,8 @@ export class PdsCombobox {
923
1282
  this.listboxEl.style.width = this.dropdownWidth;
924
1283
  if (this.maxHeight) {
925
1284
  this.listboxEl.style.maxHeight = this.maxHeight;
926
- this.listboxEl.style.overflowY = 'auto';
1285
+ // With panel search, the options list scrolls under a fixed search field.
1286
+ this.listboxEl.style.overflowY = this.usesPanelSearch ? 'hidden' : 'auto';
927
1287
  }
928
1288
  this.listboxEl.offsetHeight;
929
1289
  return computePosition(this.triggerEl, this.listboxEl, {
@@ -940,8 +1300,30 @@ export class PdsCombobox {
940
1300
  position: strategy,
941
1301
  zIndex,
942
1302
  });
1303
+ this.checkFlyoutRoom();
943
1304
  });
944
1305
  }
1306
+ /**
1307
+ * Once per open, after the dropdown is placed: with no room for a flyout on either side of it,
1308
+ * `flyout` drills in instead. `flyout-min-width` is resolved through CSS, so any length works.
1309
+ */
1310
+ checkFlyoutRoom() {
1311
+ if (!this.isOpen || this.groupDisplay !== 'flyout' || this.flyoutRoomChecked || !this.listboxEl)
1312
+ return;
1313
+ this.flyoutRoomChecked = true;
1314
+ const probe = document.createElement('div');
1315
+ // Fixed, like the flyout, so a percentage resolves against the viewport as the flyout's does.
1316
+ probe.style.cssText = 'position:fixed;visibility:hidden;width:0;';
1317
+ probe.style.minWidth = this.flyoutMinWidth;
1318
+ this.listboxEl.appendChild(probe);
1319
+ const needed = parseFloat(getComputedStyle(probe).minWidth);
1320
+ probe.remove();
1321
+ const rect = this.listboxEl.getBoundingClientRect();
1322
+ const room = Math.max(window.innerWidth - rect.right, rect.left);
1323
+ const fallback = Number.isFinite(needed) && needed > 0 && rect.width > 0 && room < needed;
1324
+ if (fallback !== this.flyoutFallback)
1325
+ this.flyoutFallback = fallback;
1326
+ }
945
1327
  openDropdownPositioning() {
946
1328
  if (!this.triggerEl || !this.listboxEl) {
947
1329
  return;
@@ -1044,6 +1426,130 @@ export class PdsCombobox {
1044
1426
  this.listboxEl = undefined;
1045
1427
  }
1046
1428
  }
1429
+ /**
1430
+ * The tree keys in the grouped panel. Up, Down, Home, End, PageUp and PageDown move within one
1431
+ * level; Right, Enter or Space opens a group at its first option; Left, or Escape, closes the
1432
+ * open group back to its row. Returns false for keys the listbox handling covers, such as
1433
+ * Enter on an option.
1434
+ */
1435
+ handleGroupedPanelKey(e) {
1436
+ if (!this.showsGroupedPanel)
1437
+ return false;
1438
+ this.keyboardNavigating = true;
1439
+ // In the input trigger, Left and Right still move the caret through typed text, and Space,
1440
+ // Home and End edit it, as they do in the panel's search field.
1441
+ if (this.trigger === 'input') {
1442
+ if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && this.displayText !== '')
1443
+ return false;
1444
+ if (e.key === ' ' || e.key === 'Home' || e.key === 'End')
1445
+ return false;
1446
+ }
1447
+ const entries = buildPanelEntries(this.filteredItems);
1448
+ const drilledIn = this.effectiveGroupDisplay === 'drill-in' && this.openGroup !== null;
1449
+ const current = this.currentPanelItem();
1450
+ const column = panelColumn(entries, this.openGroup, current, drilledIn);
1451
+ if (column.length === 0)
1452
+ return false;
1453
+ const position = current === null ? -1 : column.indexOf(current);
1454
+ const moveTo = (to) => {
1455
+ e.preventDefault();
1456
+ this.highlightPanelItem(column[Math.max(0, Math.min(column.length - 1, to))]);
1457
+ return true;
1458
+ };
1459
+ switch (e.key) {
1460
+ case 'ArrowDown':
1461
+ return moveTo(position < 0 ? 0 : position + 1);
1462
+ case 'ArrowUp':
1463
+ return moveTo(position < 0 ? column.length - 1 : position - 1);
1464
+ case 'Home':
1465
+ return moveTo(0);
1466
+ case 'End':
1467
+ return moveTo(column.length - 1);
1468
+ case 'PageDown':
1469
+ return moveTo(position < 0 ? 0 : position + 10);
1470
+ case 'PageUp':
1471
+ return moveTo(position < 0 ? column.length - 1 : position - 10);
1472
+ case 'ArrowRight':
1473
+ case 'Enter':
1474
+ case ' ': {
1475
+ if (current === null || typeof current === 'number')
1476
+ return false;
1477
+ e.preventDefault();
1478
+ if (!drilledIn || current !== this.openGroup) {
1479
+ this.expandGroup(entries, current);
1480
+ }
1481
+ else if (e.key === 'ArrowRight') {
1482
+ // On the row back of a drilled-in group: down into its options.
1483
+ if (column.length > 1)
1484
+ this.highlightPanelItem(column[1]);
1485
+ }
1486
+ else {
1487
+ this.leaveDrilledInGroup();
1488
+ }
1489
+ return true;
1490
+ }
1491
+ case 'ArrowLeft':
1492
+ case 'Escape': {
1493
+ // Escape with no group open closes the panel, through the listbox handling.
1494
+ if (this.openGroup === null)
1495
+ return false;
1496
+ e.preventDefault();
1497
+ this.collapseToGroup(this.openGroup);
1498
+ return true;
1499
+ }
1500
+ }
1501
+ return false;
1502
+ }
1503
+ /**
1504
+ * Where the keyboard is in the grouped panel. An option highlighted in a group that has since
1505
+ * closed (the pointer left its flyout) counts as its group's row, so the keys carry on from
1506
+ * what's showing rather than from an option that isn't.
1507
+ */
1508
+ currentPanelItem() {
1509
+ if (this.highlightedIndex < 0)
1510
+ return this.highlightedGroup;
1511
+ const selectable = this.filteredItems.filter(item => item.tagName === 'OPTION');
1512
+ const group = parentOptgroupOf(selectable[this.highlightedIndex]);
1513
+ return group !== null && group !== this.openGroup ? group : this.highlightedIndex;
1514
+ }
1515
+ /**
1516
+ * Highlights the option at `index`, kept within the list. Disabled options can be highlighted,
1517
+ * so assistive tech announces them as unavailable, but not chosen.
1518
+ */
1519
+ highlightOptionAt(options, index) {
1520
+ if (options.length === 0)
1521
+ return;
1522
+ this.highlightedIndex = Math.max(0, Math.min(options.length - 1, index));
1523
+ this.updateOptionFocus();
1524
+ }
1525
+ /** Highlights a group row or an option, opening or closing flyouts to match. */
1526
+ highlightPanelItem(item) {
1527
+ if (typeof item === 'number') {
1528
+ this.highlightedIndex = item;
1529
+ }
1530
+ else {
1531
+ this.highlightedIndex = -1;
1532
+ this.highlightedGroup = item;
1533
+ }
1534
+ this.updateOptionFocus();
1535
+ }
1536
+ expandGroup(entries, optgroup) {
1537
+ var _a;
1538
+ this.flyout.open(optgroup);
1539
+ const first = (_a = findGroup(entries, optgroup)) === null || _a === void 0 ? void 0 : _a.options[0];
1540
+ if (first !== undefined)
1541
+ this.highlightPanelItem(first.index);
1542
+ // Its options render after this change; scroll to the first one then.
1543
+ this.pendingOptionFocus = true;
1544
+ }
1545
+ /** Closes the open group and highlights its row, as a tree's Left does from a child. */
1546
+ collapseToGroup(optgroup) {
1547
+ this.flyout.reset();
1548
+ this.openGroup = null;
1549
+ this.highlightPanelItem(optgroup);
1550
+ // The row renders after this change (in drill-in it replaces the row back); scroll to it then.
1551
+ this.pendingOptionFocus = true;
1552
+ }
1047
1553
  /**
1048
1554
  * Sets focus on the native input element.
1049
1555
  */
@@ -1057,8 +1563,9 @@ export class PdsCombobox {
1057
1563
  }
1058
1564
  }
1059
1565
  /**
1060
- * Sets the initial highlighted index when dropdown opens.
1061
- * If there's a selected option, highlight it. Otherwise, highlight the first option.
1566
+ * Sets the initial highlighted index when dropdown opens. Highlights the selected option unless
1567
+ * it's disabled. Otherwise highlights the first enabled option, or, in the grouped panel, the
1568
+ * first row with something to choose, or nothing when every option is disabled.
1062
1569
  */
1063
1570
  setInitialHighlightedIndex() {
1064
1571
  const selectableOptions = this.filteredItems.filter(item => item.tagName === 'OPTION');
@@ -1066,16 +1573,33 @@ export class PdsCombobox {
1066
1573
  this.highlightedIndex = -1;
1067
1574
  return;
1068
1575
  }
1069
- // If there's a selected option, find its index in the filtered options
1070
- if (this.selectedOption) {
1576
+ // If there's a selected option, find its index in the filtered options. A disabled one can be
1577
+ // the value, as in a native select, but it isn't highlighted.
1578
+ if (this.selectedOption && !isOptionDisabled(this.selectedOption)) {
1071
1579
  const selectedIndex = selectableOptions.findIndex(option => option === this.selectedOption);
1072
1580
  if (selectedIndex >= 0) {
1073
1581
  this.highlightedIndex = selectedIndex;
1582
+ // In the grouped panel, open on the selection's group, however the dropdown was opened, and
1583
+ // scroll the selection into view once its group has rendered.
1584
+ if (this.showsGroupedPanel) {
1585
+ this.openGroup = parentOptgroupOf(this.selectedOption);
1586
+ // Opening on the selection isn't drilling in, so it doesn't slide.
1587
+ this.drillDirection = null;
1588
+ this.pendingOptionFocus = true;
1589
+ }
1074
1590
  return;
1075
1591
  }
1076
1592
  }
1077
- // No selected option or selected option not in filtered results, highlight first option
1078
- this.highlightedIndex = 0;
1593
+ // In the grouped panel, the first row with something to choose: a group stays closed until
1594
+ // it's opened.
1595
+ if (this.showsGroupedPanel) {
1596
+ const first = buildPanelEntries(this.filteredItems).find(entry => entry.kind === 'group' ? !isGroupDisabled(entry.group) : !isOptionDisabled(entry.option));
1597
+ this.highlightedIndex = (first === null || first === void 0 ? void 0 : first.kind) === 'option' ? first.index : -1;
1598
+ this.highlightedGroup = (first === null || first === void 0 ? void 0 : first.kind) === 'group' ? first.group.optgroup : null;
1599
+ return;
1600
+ }
1601
+ // No selected option or selected option not in filtered results, highlight the first enabled option
1602
+ this.highlightedIndex = selectableOptions.findIndex(option => !isOptionDisabled(option));
1079
1603
  }
1080
1604
  /**
1081
1605
  * Focus management helper - moves focus to the first option when dropdown opens
@@ -1111,19 +1635,9 @@ export class PdsCombobox {
1111
1635
  const selectableOptions = this.filteredItems.filter(item => item.tagName === 'OPTION');
1112
1636
  if (selectableOptions.length > 0) {
1113
1637
  this.setInitialHighlightedIndex();
1114
- if (this.listboxEl) {
1115
- const optionElements = this.listboxEl.querySelectorAll('[role="option"]');
1116
- const highlightedOption = optionElements[this.highlightedIndex];
1117
- if (highlightedOption) {
1118
- optionElements.forEach(option => {
1119
- option.setAttribute('tabindex', '-1');
1120
- });
1121
- highlightedOption.setAttribute('tabindex', '0');
1122
- highlightedOption.focus();
1123
- highlightedOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
1124
- }
1125
- }
1126
- this.updateAriaActiveDescendant();
1638
+ // By index, through the same path as arrowing: it opens the option's flyout group first
1639
+ // when it isn't rendered, and highlights it once it is.
1640
+ this.updateOptionFocus();
1127
1641
  }
1128
1642
  }
1129
1643
  /**
@@ -1131,14 +1645,58 @@ export class PdsCombobox {
1131
1645
  * Note: We don't actually focus the option to prevent focusout events that close the dropdown
1132
1646
  */
1133
1647
  updateOptionFocus() {
1134
- if (!this.listboxEl || this.highlightedIndex < 0)
1648
+ var _a;
1649
+ if (this.listboxEl === undefined || this.listboxEl === null)
1135
1650
  return;
1651
+ // A row highlighted outright; an option highlighted in a closed group opens it below instead.
1652
+ const groupRow = this.showsGroupedPanel && this.highlightedIndex < 0 ? this.highlightedGroup : null;
1653
+ if (groupRow !== null) {
1654
+ // Arrowing onto another row closes a flyout that hover left open.
1655
+ if (this.effectiveGroupDisplay === 'flyout' && this.openGroup !== null && this.openGroup !== groupRow) {
1656
+ this.flyout.reset();
1657
+ this.openGroup = null;
1658
+ }
1659
+ // Matched by id, not a selector: componentId is consumer text and may not be selector-safe.
1660
+ const id = this.groupDomId(groupRow);
1661
+ (_a = Array.from(this.listboxEl.querySelectorAll('[role="treeitem"]'))
1662
+ .find(row => row.id === id)) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'nearest' });
1663
+ this.updateAriaActiveDescendant();
1664
+ return;
1665
+ }
1666
+ if (this.highlightedIndex < 0)
1667
+ return;
1668
+ // In the flyout panel only the open group's options are rendered: open the highlighted
1669
+ // option's group, and finish once it has rendered. Through the controller, so a hover timer
1670
+ // left running by the pointer can't close or switch the group the keys just moved into.
1671
+ if (this.showsGroupedPanel) {
1672
+ const selectable = this.filteredItems.filter(item => item.tagName === 'OPTION');
1673
+ const group = parentOptgroupOf(selectable[this.highlightedIndex]);
1674
+ const changed = group !== this.openGroup;
1675
+ if (group !== null) {
1676
+ this.flyout.open(group);
1677
+ }
1678
+ else {
1679
+ this.flyout.reset();
1680
+ this.openGroup = null;
1681
+ }
1682
+ if (changed) {
1683
+ this.pendingOptionFocus = true;
1684
+ return;
1685
+ }
1686
+ }
1136
1687
  const optionElements = this.listboxEl.querySelectorAll('[role="option"]');
1137
- const currentOption = optionElements[this.highlightedIndex];
1688
+ // By index, not position: the flyout panel renders only some options.
1689
+ const currentOption = this.listboxEl.querySelector(`[data-option-index="${this.highlightedIndex}"]`);
1138
1690
  if (currentOption) {
1139
1691
  // Check if any option currently has focus OR if we're in arrow-key navigation mode
1140
1692
  const hasOptionFocus = Array.from(optionElements).some(el => el === document.activeElement);
1141
- const shouldFocusOption = !this.usesBodyPortal && (hasOptionFocus || this.isArrowKeyNavigationMode);
1693
+ // The panel's search field keeps focus and points at the option with aria-activedescendant,
1694
+ // and so does the trigger over the grouped panel, whose group rows can't take focus and
1695
+ // whose options are hidden while a flyout is placed.
1696
+ const shouldFocusOption = !this.usesBodyPortal &&
1697
+ !this.usesPanelSearch &&
1698
+ !this.showsGroupedPanel &&
1699
+ (hasOptionFocus || this.isArrowKeyNavigationMode);
1142
1700
  if (shouldFocusOption) {
1143
1701
  optionElements.forEach(option => {
1144
1702
  option.setAttribute('tabindex', '-1');
@@ -1146,21 +1704,64 @@ export class PdsCombobox {
1146
1704
  currentOption.setAttribute('tabindex', '0');
1147
1705
  currentOption.focus();
1148
1706
  }
1149
- // Always scroll the option into view
1150
- currentOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
1707
+ // Always scroll the option into view; in a flyout, by scrolling the flyout alone.
1708
+ const flyout = currentOption.closest('.pds-combobox__flyout');
1709
+ if (flyout) {
1710
+ this.flyout.reveal(flyout, currentOption);
1711
+ }
1712
+ else {
1713
+ currentOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
1714
+ }
1151
1715
  }
1152
1716
  // Always update aria-activedescendant on the trigger element
1153
1717
  this.updateAriaActiveDescendant();
1154
1718
  }
1719
+ /** The highlighted group row in the grouped panel, or null while an option is highlighted. */
1720
+ get highlightedGroupRow() {
1721
+ if (!this.showsGroupedPanel)
1722
+ return null;
1723
+ if (this.highlightedIndex < 0)
1724
+ return this.highlightedGroup;
1725
+ // An option left highlighted in a flyout that has since closed shows as its group's row.
1726
+ const current = this.currentPanelItem();
1727
+ return typeof current === 'number' ? null : current;
1728
+ }
1155
1729
  /**
1156
- * Updates aria-activedescendant on the trigger element
1730
+ * The id of the highlighted option or group row, only while it's rendered. In the flyout panel
1731
+ * an option in a closed group isn't in the DOM, and drilled into a group the other groups' rows
1732
+ * aren't; `aria-activedescendant` must not point at a missing element.
1157
1733
  */
1734
+ get activeDescendantId() {
1735
+ if (!this.isOpen)
1736
+ return undefined;
1737
+ const groupRow = this.highlightedGroupRow;
1738
+ if (groupRow !== null) {
1739
+ const drilledIntoAnother = this.effectiveGroupDisplay === 'drill-in' && this.openGroup !== null && this.openGroup !== groupRow;
1740
+ return drilledIntoAnother ? undefined : this.groupDomId(groupRow);
1741
+ }
1742
+ if (this.highlightedIndex < 0)
1743
+ return undefined;
1744
+ if (this.showsGroupedPanel) {
1745
+ const selectable = this.filteredItems.filter(item => item.tagName === 'OPTION');
1746
+ const group = parentOptgroupOf(selectable[this.highlightedIndex]);
1747
+ if (group !== null && group !== this.openGroup)
1748
+ return undefined;
1749
+ }
1750
+ return this.optionDomId(this.highlightedIndex);
1751
+ }
1752
+ /** Points the focused element's aria-activedescendant at the highlighted option, or clears it. */
1158
1753
  updateAriaActiveDescendant() {
1159
- if (this.triggerEl && this.highlightedIndex >= 0) {
1160
- this.triggerEl.setAttribute('aria-activedescendant', this.optionDomId(this.highlightedIndex));
1754
+ // With panel search, the element with focus owns it, not the trigger: the search field, or
1755
+ // the list while a drilled-in group hides the field.
1756
+ const target = this.usesPanelSearch ? (this.hidesPanelSearch ? this.panelTreeEl : this.panelSearchEl) : this.triggerEl;
1757
+ if (!target)
1758
+ return;
1759
+ const id = this.activeDescendantId;
1760
+ if (id !== undefined) {
1761
+ target.setAttribute('aria-activedescendant', id);
1161
1762
  }
1162
- else if (this.triggerEl) {
1163
- this.triggerEl.removeAttribute('aria-activedescendant');
1763
+ else {
1764
+ target.removeAttribute('aria-activedescendant');
1164
1765
  }
1165
1766
  }
1166
1767
  /**
@@ -1196,6 +1797,17 @@ export class PdsCombobox {
1196
1797
  this.pdsComboboxChange.emit({ value: '' });
1197
1798
  }
1198
1799
  }
1800
+ // Group rows in the flyout panel carry their group's index, as options carry theirs.
1801
+ groupFromEvent(event) {
1802
+ var _a, _b;
1803
+ const index = Number(event.currentTarget.getAttribute('data-group-index'));
1804
+ return (_b = (_a = this.renderedGroups[index]) === null || _a === void 0 ? void 0 : _a.optgroup) !== null && _b !== void 0 ? _b : null;
1805
+ }
1806
+ highlightGroupRowByPointer(group) {
1807
+ this.highlightedIndex = -1;
1808
+ this.highlightedGroup = group;
1809
+ this.keyboardNavigating = false;
1810
+ }
1199
1811
  // Get the label of the selected option
1200
1812
  get selectedLabel() {
1201
1813
  return this.displayText || '';
@@ -1308,7 +1920,24 @@ export class PdsCombobox {
1308
1920
  // Fallback: Use component props
1309
1921
  return this.chipDot;
1310
1922
  }
1923
+ /**
1924
+ * With `async-url`, fetches when the button trigger opens: the first page if nothing has loaded.
1925
+ * The panel's search field opens empty, so results left from the last search are refetched for
1926
+ * an empty query.
1927
+ */
1928
+ fetchAsyncOptionsOnOpen() {
1929
+ if (!this.asyncUrl)
1930
+ return;
1931
+ if (this.usesPanelSearch && (this.internalOptions.length === 0 || this.lastFetchedQuery !== '')) {
1932
+ this.debouncedFetchAsyncOptions('', 1);
1933
+ }
1934
+ else if (this.internalOptions.length === 0) {
1935
+ this.debouncedFetchAsyncOptions(this.displayText, 1);
1936
+ }
1937
+ }
1311
1938
  handleOptionClick(option) {
1939
+ if (isOptionDisabled(option))
1940
+ return;
1312
1941
  // Update reactive state - single source of truth
1313
1942
  // The @Watch('selectedOption') will handle displayText, value, and form internals
1314
1943
  this.setSelectedOption(option);
@@ -1322,33 +1951,242 @@ export class PdsCombobox {
1322
1951
  }
1323
1952
  const hasSlottedEmpty = !this.usesBodyPortal && !!this.el.querySelector('[slot="empty"]');
1324
1953
  const hasSlottedLoading = !this.usesBodyPortal && !!this.el.querySelector('[slot="loading"]');
1325
- let optionIndex = 0;
1326
1954
  const selectableOptions = this.filteredItems.filter(item => item.tagName === 'OPTION');
1327
- return (h("ul", { class: "pds-combobox__listbox", role: "listbox", id: this.listboxId, "aria-label": this.label || 'Options', "aria-multiselectable": "false", ref: el => (this.listboxEl = el), onScroll: this.handleScroll }, this.loading && (h("li", { class: "pds-combobox__loading", role: "presentation" }, hasSlottedLoading ? (h("slot", { name: "loading" })) : (h("pds-loader", { size: "small" })))), !this.loading && this.filteredItems.length === 0 && (h("li", { class: "pds-combobox__empty", role: "presentation" }, hasSlottedEmpty ? (h("slot", { name: "empty" })) : (h("span", null, "No options found")))), this.filteredItems.map((item, itemIdx) => {
1955
+ const withSearch = this.usesPanelSearch;
1956
+ // While a drilled-in group hides the search field, the list takes focus in its place.
1957
+ const listTakesFocus = this.hidesPanelSearch;
1958
+ const list = (h("ul", { key: "panel-list", class: {
1959
+ 'pds-combobox__listbox': !withSearch,
1960
+ 'pds-combobox__listbox-options': withSearch,
1961
+ 'pds-combobox__listbox-options--drilled-in': listTakesFocus,
1962
+ 'pds-combobox__listbox--grouped': this.groupDisplay !== 'inline',
1963
+ 'pds-combobox__listbox--drill-in': this.drillDirection === 'in',
1964
+ 'pds-combobox__listbox--drill-out': this.drillDirection === 'out',
1965
+ }, onAnimationEnd: this.onDrillAnimationEnd, role: this.showsGroupedPanel ? 'tree' : 'listbox', id: this.listboxId, "aria-label": this.label || 'Options', "aria-multiselectable": "false",
1966
+ // Focusable but out of the tab order with panel search, so it can take focus when a
1967
+ // drilled-in group hides the field. Dropping it while focused would blur the list and close.
1968
+ tabIndex: withSearch ? -1 : undefined, "aria-activedescendant": listTakesFocus ? this.activeDescendantId : undefined, onKeyDown: withSearch ? this.onPanelTreeKeyDown : undefined, ref: el => {
1969
+ if (withSearch)
1970
+ this.panelTreeEl = el;
1971
+ else
1972
+ this.listboxEl = el;
1973
+ }, onScroll: this.handleScroll }, this.loading && (h("li", { class: "pds-combobox__loading", role: "presentation" }, hasSlottedLoading ? (h("slot", { name: "loading" })) : (h("pds-loader", { size: "small" })))), !this.loading && this.filteredItems.length === 0 && (h("li", { class: "pds-combobox__empty", role: "presentation" }, hasSlottedEmpty ? (h("slot", { name: "empty" })) : (h("span", null, "No options found")))), this.showsGroupedPanel ? this.renderGroupedPanel() : this.renderListboxItems(selectableOptions.length), this.hasMore && !this.loading && (h("li", { class: "pds-combobox__load-more", role: "presentation" }, h("pds-loader", { size: "small" })))));
1974
+ if (!withSearch)
1975
+ return list;
1976
+ // The search field sits above the list; the wrapper is what's positioned and portaled.
1977
+ return (h("div", { class: "pds-combobox__listbox pds-combobox__listbox--with-search", ref: el => (this.listboxEl = el), onMouseDown: this.onPanelMouseDown,
1978
+ // Portaled, the panel sits outside .pds-combobox, so its focusout wouldn't reach that handler.
1979
+ onFocusout: this.usesBodyPortal ? this.onComboboxFocusOut : undefined }, !listTakesFocus && this.renderPanelSearch(), list));
1980
+ }
1981
+ /**
1982
+ * The listbox's items. Each optgroup's options sit in a `group` named for it, as in
1983
+ * `pds-multiselect`, so assistive tech announces the group on entering it; a `pds-text` label
1984
+ * stays a caption above the options that follow it.
1985
+ */
1986
+ renderListboxItems(setSize) {
1987
+ const rendered = [];
1988
+ let optionIndex = 0;
1989
+ let group = null;
1990
+ const closeGroup = () => {
1991
+ if (group === null)
1992
+ return;
1993
+ const { optgroup, key, options, members } = group;
1994
+ const groupIcon = groupIconOf(optgroup);
1995
+ // As in the grouped displays: disabled when there's nothing left in it to choose.
1996
+ const disabled = members.length > 0 && members.every(member => isOptionDisabled(member));
1997
+ rendered.push(h("li", { key: key, class: { 'pds-combobox__group': true, 'pds-combobox__group--disabled': disabled }, role: "group", "aria-label": optgroup.label, "aria-disabled": disabled ? 'true' : undefined }, h("div", { class: { 'pds-combobox__group-label': true, 'pds-combobox__group-label--with-icon': groupIcon !== null }, "aria-hidden": "true" }, groupIcon !== null && (
1998
+ // `name`, not `icon`: `icon` also loads URLs and data: SVGs, unsanitized.
1999
+ h("pds-icon", { name: groupIcon, size: "small", "aria-hidden": "true", class: "pds-combobox__group-icon" })), optgroup.label), h("ul", { class: "pds-combobox__group-list", role: "presentation" }, options)));
2000
+ group = null;
2001
+ };
2002
+ this.filteredItems.forEach((item, itemIdx) => {
1328
2003
  if (item.tagName === 'OPTGROUP') {
1329
- const optgroup = item;
1330
- return (h("li", { key: `optgroup-${itemIdx}`, class: "pds-combobox__group-label", role: "presentation", "aria-label": optgroup.label }, optgroup.label));
2004
+ closeGroup();
2005
+ group = { optgroup: item, key: `optgroup-${itemIdx}`, options: [], members: [] };
1331
2006
  }
1332
2007
  else if (item.tagName === 'PDS-TEXT') {
1333
- const pdsText = item;
1334
- return (h("li", { key: `pds-text-${itemIdx}`, class: "pds-combobox__group-label", role: "presentation" }, pdsText.textContent));
2008
+ closeGroup();
2009
+ rendered.push(h("li", { key: `pds-text-${itemIdx}`, class: "pds-combobox__group-label", role: "presentation" }, item.textContent));
1335
2010
  }
1336
2011
  else if (item.tagName === 'OPTION') {
1337
2012
  const option = item;
1338
- const isSelected = this.isOptionSelected(option);
1339
- const isHighlighted = this.highlightedIndex === optionIndex;
1340
- const isLayout = this.isOptionLayout(option);
1341
- const isChip = this.isOptionChip(option);
1342
- const currentOptionIndex = optionIndex++;
1343
- return (h("li", { key: option.value, id: this.optionDomId(currentOptionIndex), role: "option", "aria-selected": isSelected ? 'true' : 'false', "aria-setsize": selectableOptions.length, "aria-posinset": currentOptionIndex + 1, "aria-label": isLayout || isChip ? option.getAttribute('aria-label') || this.getOptionLabel(option) : undefined, tabindex: this.usesBodyPortal ? '-1' : isHighlighted ? '0' : '-1', class: {
1344
- 'pds-combobox__option': true,
1345
- 'pds-combobox__option--highlighted': isHighlighted,
1346
- 'pds-combobox__option--layout': isLayout,
1347
- 'pds-combobox__option--chip': isChip,
1348
- }, "data-option-index": currentOptionIndex, onMouseDown: this.onOptionMouseDown, onClick: this.onOptionClick, onMouseEnter: this.onOptionMouseEnter, onKeyDown: this.onOptionKeyDown }, isLayout ? (h("pds-box", { class: "pds-combobox__option-layout-wrapper", innerHTML: this.getOptionLayoutContent(option) })) : isChip ? (this.renderOptionChip(option)) : (this.getOptionLabel(option)), isSelected && h("pds-icon", { icon: "check", size: "regular", class: "pds-combobox__option-check" })));
2013
+ if (group !== null && parentOptgroupOf(option) !== group.optgroup)
2014
+ closeGroup();
2015
+ const rendering = this.renderOption(option, optionIndex, { posinset: optionIndex + 1, setsize: setSize });
2016
+ optionIndex++;
2017
+ if (group !== null) {
2018
+ group.options.push(rendering);
2019
+ group.members.push(option);
2020
+ }
2021
+ else {
2022
+ rendered.push(rendering);
2023
+ }
1349
2024
  }
1350
- return null;
1351
- }), this.hasMore && !this.loading && (h("li", { class: "pds-combobox__load-more", role: "presentation" }, h("pds-loader", { size: "small" })))));
2025
+ });
2026
+ closeGroup();
2027
+ return rendered;
2028
+ }
2029
+ /** `panelSearch` applies to the triggers that can't be typed into. */
2030
+ get usesPanelSearch() {
2031
+ return this.panelSearch && this.trigger !== 'input';
2032
+ }
2033
+ /** `groupDisplay`, with `flyout` drilling in when there's no room beside the dropdown. */
2034
+ get effectiveGroupDisplay() {
2035
+ return this.groupDisplay === 'flyout' && this.flyoutFallback ? 'drill-in' : this.groupDisplay;
2036
+ }
2037
+ /**
2038
+ * The popup's role, for the triggers' `aria-haspopup`: a `tree` while the grouped panel shows
2039
+ * groups, and a `listbox` of options otherwise, search results included.
2040
+ */
2041
+ get popupRole() {
2042
+ return this.showsGroupedPanel ? 'tree' : 'listbox';
2043
+ }
2044
+ /**
2045
+ * The input trigger's `aria-haspopup`. While closed it names what opening shows: the full list,
2046
+ * so the grouped panel in a grouped display, even though the input still holds the selection's
2047
+ * label.
2048
+ */
2049
+ get inputPopupRole() {
2050
+ if (this.isOpen)
2051
+ return this.popupRole;
2052
+ return this.effectiveGroupDisplay === 'inline' ? 'listbox' : 'tree';
2053
+ }
2054
+ /**
2055
+ * The button and chip triggers' `aria-haspopup`. With panel search the trigger opens the panel,
2056
+ * which starts as a tree in a grouped display, so typing in the panel doesn't change it.
2057
+ */
2058
+ get triggerPopupRole() {
2059
+ if (!this.usesPanelSearch)
2060
+ return this.popupRole;
2061
+ return this.groupDisplay === 'inline' ? 'listbox' : 'tree';
2062
+ }
2063
+ /**
2064
+ * Drilled into a group with panel search: the group's row back takes the search field's place,
2065
+ * since one group's options don't need searching. Typing searches the whole list again.
2066
+ */
2067
+ get hidesPanelSearch() {
2068
+ return this.usesPanelSearch && this.showsGroupedPanel && this.effectiveGroupDisplay === 'drill-in' && this.openGroup !== null;
2069
+ }
2070
+ /** The grouped panel is showing groups rather than search results. */
2071
+ get showsGroupedPanel() {
2072
+ if (this.effectiveGroupDisplay === 'inline')
2073
+ return false;
2074
+ const searching = this.usesPanelSearch
2075
+ ? this.panelQuery !== ''
2076
+ : this.mode === 'filter' && !this.expandFilterListWhileOpen && this.displayText !== '';
2077
+ return !searching;
2078
+ }
2079
+ setPanelQuery(query) {
2080
+ this.panelQuery = query;
2081
+ this.clearExpandFilterList();
2082
+ this.filterOptions();
2083
+ this.pdsComboboxSearch.emit({ query: this.panelQuery });
2084
+ if (this.asyncUrl !== undefined && this.asyncUrl !== '') {
2085
+ this.debouncedFetchAsyncOptions(this.panelQuery, 1);
2086
+ }
2087
+ // The list changes size, so place the dropdown again, as typing in the input trigger does.
2088
+ setTimeout(() => this.openDropdownPositioning(), 0);
2089
+ }
2090
+ /** What's being searched: the panel's field, or the input trigger's text. */
2091
+ get activeQuery() {
2092
+ return this.usesPanelSearch ? this.panelQuery : this.displayText;
2093
+ }
2094
+ // Tab closes the dropdown and lands on the trigger, as Escape does: the focused field or list is
2095
+ // about to unmount, so the browser can't tab on from it. The next Tab continues from the trigger.
2096
+ handlePanelTab(event) {
2097
+ if (event.key !== 'Tab')
2098
+ return false;
2099
+ event.preventDefault();
2100
+ this.handleKeyDown(event);
2101
+ this.restoreFocusToTrigger();
2102
+ return true;
2103
+ }
2104
+ /** Returns from a drilled-in group to the group list; false when not drilled in. */
2105
+ leaveDrilledInGroup() {
2106
+ if (!this.showsGroupedPanel || this.effectiveGroupDisplay !== 'drill-in' || this.openGroup === null)
2107
+ return false;
2108
+ this.collapseToGroup(this.openGroup);
2109
+ return true;
2110
+ }
2111
+ renderPanelSearch() {
2112
+ return (h("div", { key: "panel-search", class: "pds-combobox__panel-search" }, h("pds-icon", { name: "search", size: "small", "aria-hidden": "true" }), h("input", { ref: el => (this.panelSearchEl = el), type: "text", class: "pds-combobox__panel-search-input", placeholder: this.searchPlaceholder, value: this.panelQuery, role: "combobox", "aria-label": this.searchOptionsLabel, "aria-haspopup": this.popupRole, "aria-controls": this.listboxId, "aria-autocomplete": "list", "aria-expanded": "true", "aria-activedescendant": this.activeDescendantId, autocomplete: "off", onInput: this.onPanelSearchInput, onKeyDown: this.onPanelSearchKeyDown })));
2113
+ }
2114
+ /**
2115
+ * An option, as a listbox `option` or, given a tree `level`, a `treeitem`. `posinset` and
2116
+ * `setsize` place it among its siblings.
2117
+ */
2118
+ renderOption(option, index, { posinset, setsize, level }) {
2119
+ const isSelected = this.isOptionSelected(option);
2120
+ const isHighlighted = this.highlightedIndex === index;
2121
+ const isLayout = this.isOptionLayout(option);
2122
+ const isChip = this.isOptionChip(option);
2123
+ const isDisabled = isOptionDisabled(option);
2124
+ const tree = level !== undefined;
2125
+ return (h("li", { key: option.value, id: this.optionDomId(index), role: tree ? 'treeitem' : 'option', "aria-selected": isSelected ? 'true' : tree ? undefined : 'false', "aria-disabled": isDisabled ? 'true' : undefined, "aria-level": level, "aria-setsize": setsize, "aria-posinset": posinset, "aria-label": isLayout || isChip ? option.getAttribute('aria-label') || this.getOptionLabel(option) : undefined, tabindex: this.usesBodyPortal || this.usesPanelSearch || tree ? '-1' : isHighlighted ? '0' : '-1', class: {
2126
+ 'pds-combobox__option': true,
2127
+ 'pds-combobox__option--highlighted': isHighlighted,
2128
+ 'pds-combobox__option--layout': isLayout,
2129
+ 'pds-combobox__option--chip': isChip,
2130
+ 'pds-combobox__option--disabled': isDisabled,
2131
+ }, "data-option-index": index, onMouseDown: this.onOptionMouseDown, onClick: this.onOptionClick, onMouseEnter: this.onOptionMouseEnter, onKeyDown: this.onOptionKeyDown }, isLayout ? (h("pds-box", { class: "pds-combobox__option-layout-wrapper", innerHTML: this.getOptionLayoutContent(option) })) : isChip ? (this.renderOptionChip(option)) : (this.getOptionLabel(option)), isSelected && h("pds-icon", { icon: "check", size: "regular", class: "pds-combobox__option-check" })));
2132
+ }
2133
+ /**
2134
+ * `flyout`: one row per group, its options in a flyout beside it. `drill-in`: the same rows, and
2135
+ * an opened group replaces them with its row, drawn as a row back, above its options. Either way
2136
+ * it's a tree: the rows and ungrouped options are level 1, a group's options level 2.
2137
+ */
2138
+ renderGroupedPanel() {
2139
+ const entries = buildPanelEntries(this.filteredItems);
2140
+ const setsize = entries.length;
2141
+ if (this.effectiveGroupDisplay === 'drill-in' && this.openGroup !== null) {
2142
+ const position = entries.findIndex(entry => entry.kind === 'group' && entry.group.optgroup === this.openGroup);
2143
+ const open = entries[position];
2144
+ if ((open === null || open === void 0 ? void 0 : open.kind) === 'group')
2145
+ return this.renderGroupBackRow(open.group, { posinset: position + 1, setsize });
2146
+ }
2147
+ this.renderedGroups = [];
2148
+ return entries.map((entry, position) => {
2149
+ const placement = { posinset: position + 1, setsize };
2150
+ if (entry.kind === 'option')
2151
+ return this.renderOption(entry.option, entry.index, Object.assign(Object.assign({}, placement), { level: 1 }));
2152
+ this.renderedGroups.push(entry.group);
2153
+ return this.renderGroupRow(entry.group, this.renderedGroups.length - 1, placement);
2154
+ });
2155
+ }
2156
+ /** A group's options, as its children in the tree. */
2157
+ renderGroupOptions(group) {
2158
+ return group.options.map(({ option, index }, position) => this.renderOption(option, index, { posinset: position + 1, setsize: group.options.length, level: 2 }));
2159
+ }
2160
+ /**
2161
+ * Drilled into a group: its treeitem, expanded. Its row reads as a way back ("Back: Tag") and
2162
+ * leads its options.
2163
+ */
2164
+ renderGroupBackRow(group, { posinset, setsize }) {
2165
+ const id = this.groupDomId(group.optgroup);
2166
+ const disabled = isGroupDisabled(group);
2167
+ return (h("li", { key: "group-back", id: id, class: {
2168
+ 'pds-combobox__group-back': true,
2169
+ 'pds-combobox__group-back--disabled': disabled,
2170
+ 'pds-combobox__group-back--highlighted': this.highlightedGroupRow === group.optgroup,
2171
+ 'pds-combobox__group-back--keyboard': this.keyboardNavigating && this.highlightedGroupRow === group.optgroup,
2172
+ }, role: "treeitem", "aria-expanded": "true", "aria-disabled": disabled ? 'true' : undefined, "aria-level": 1, "aria-posinset": posinset, "aria-setsize": setsize, "aria-labelledby": `${id}-label` }, h("div", { class: "pds-combobox__group-row-trigger pds-combobox__group-back-trigger", onMouseDown: this.onOptionMouseDown, onMouseEnter: this.onGroupBackMouseEnter, onClick: this.onGroupBackClick }, h("span", { class: "pds-combobox__group-row-label", id: `${id}-label` }, h("pds-icon", { name: "caret-left", size: "regular", "aria-hidden": "true", class: "pds-combobox__group-row-caret" }), h("span", { class: "visually-hidden" }, this.drillInBackLabel, ": "), group.label)), h("ul", { class: "pds-combobox__drill-in-options", role: "group", "aria-label": group.label }, this.renderGroupOptions(group))));
2173
+ }
2174
+ renderGroupRow(group, groupIndex, { posinset, setsize }) {
2175
+ const flyout = this.effectiveGroupDisplay === 'flyout';
2176
+ const isOpen = flyout && this.openGroup === group.optgroup;
2177
+ const hasSelection = group.options.some(({ option }) => this.isOptionSelected(option));
2178
+ const disabled = isGroupDisabled(group);
2179
+ const id = this.groupDomId(group.optgroup);
2180
+ return (h("li", { key: `group-${groupIndex}`, id: id, class: {
2181
+ 'pds-combobox__group-row': true,
2182
+ 'pds-combobox__group-row--open': isOpen,
2183
+ 'pds-combobox__group-row--has-selection': hasSelection,
2184
+ 'pds-combobox__group-row--highlighted': this.highlightedGroupRow === group.optgroup,
2185
+ 'pds-combobox__group-row--keyboard': this.keyboardNavigating && this.highlightedGroupRow === group.optgroup,
2186
+ 'pds-combobox__group-row--disabled': disabled,
2187
+ }, role: "treeitem", "aria-expanded": isOpen ? 'true' : 'false', "aria-disabled": disabled ? 'true' : undefined, "aria-level": 1, "aria-posinset": posinset, "aria-setsize": setsize, "aria-labelledby": `${id}-label`, "data-group-index": groupIndex, onMouseEnter: flyout ? this.onGroupRowMouseEnter : undefined, onMouseLeave: flyout ? this.onGroupRowMouseLeave : undefined }, h("div", { class: "pds-combobox__group-row-trigger", "data-group-index": groupIndex, onMouseDown: this.onOptionMouseDown, onMouseEnter: this.onGroupRowTriggerMouseEnter, onClick: this.onGroupRowClick }, h("span", { class: "pds-combobox__group-row-label", id: `${id}-label` }, group.icon !== null && (
2188
+ // `name`, not `icon`: `icon` also loads URLs and data: SVGs, unsanitized.
2189
+ h("pds-icon", { name: group.icon, size: "regular", "aria-hidden": "true", class: "pds-combobox__group-icon" })), group.label), h("pds-icon", { name: "caret-right", size: "regular", "aria-hidden": "true", class: "pds-combobox__group-row-caret" })), isOpen && (h("ul", { class: "pds-combobox__flyout", role: "group", "aria-label": group.label, style: { minWidth: this.flyoutMinWidth, maxWidth: this.flyoutMaxWidth } }, this.renderGroupOptions(group)))));
1352
2190
  }
1353
2191
  // Helper method to render the caret icon
1354
2192
  renderCaretIcon() {
@@ -1485,13 +2323,13 @@ export class PdsCombobox {
1485
2323
  ]
1486
2324
  .filter(Boolean)
1487
2325
  .join(' ');
1488
- return (h(Host, { key: 'a39df3c86a29028344943bd3a2828d5e99106d77', "aria-disabled": this.disabled ? 'true' : null }, h("div", { key: '32cdb8ce31f9d7c9eac8f995cd233fe6f4d9dfc3', class: "pds-combobox", tabIndex: -1, onFocusout: this.onComboboxFocusOut, part: "combobox" }, this.label && !this.hideLabel && (h("label", { key: '64ed7fe37082e2f895daae0090ae3dfac472e3c3', htmlFor: this.componentId, class: "pds-combobox__label" }, this.label)), this.trigger === 'input' ? (h("div", { class: "pds-combobox__input-wrapper", style: { width: this.triggerWidth } }, h("input", { ref: el => {
2326
+ return (h(Host, { key: 'd709320411c4d1706133376ea716756c3e1c1471', "aria-disabled": this.disabled ? 'true' : null }, h("div", { key: '455a02fa764524c95fe0bbfd024eba7134b3ac34', class: "pds-combobox", tabIndex: -1, onFocusout: this.onComboboxFocusOut, part: "combobox" }, this.hasVisibleLabel && (h("label", { key: '0d243157491934d823156841e97bfd369e12c2d5', htmlFor: this.trigger === 'input' ? this.componentId : undefined, id: this.labelId, class: "pds-combobox__label", onClick: this.onLabelClick }, this.label)), this.trigger === 'input' ? (h("div", { class: "pds-combobox__input-wrapper", style: { width: this.triggerWidth } }, h("input", { ref: el => {
1489
2327
  this.inputEl = el;
1490
2328
  this.triggerEl = el;
1491
2329
  }, class: {
1492
2330
  'pds-combobox__input': true,
1493
2331
  'pds-combobox__input--invalid': this.isInvalid(),
1494
- }, type: "text", role: "combobox", "aria-autocomplete": "list", "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.isOpen && this.highlightedIndex >= 0 ? this.optionDomId(this.highlightedIndex) : undefined, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.hideLabel ? this.label : undefined, id: this.componentId, value: this.displayText, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput, onClick: this.handleInputClick, onKeyDown: this.handleKeyDown, autocomplete: "off", part: "input" }), h("pds-icon", { icon: "enlarge", "aria-hidden": "true", class: "pds-combobox__input-icon" }))) : this.trigger === 'chip' ? (h("div", { class: this.getChipTriggerClass(), style: { width: this.triggerWidth }, role: "combobox", "aria-haspopup": "listbox", "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.isOpen && this.highlightedIndex >= 0 ? this.optionDomId(this.highlightedIndex) : undefined, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.hideLabel ? this.label : undefined, id: this.componentId, tabIndex: this.disabled ? -1 : 0, onClick: this.onButtonTriggerClick, "data-layout": this.customTriggerContent, onKeyDown: this.onButtonTriggerKeyDown, onKeyUp: this.onButtonTriggerKeyUp, ref: el => (this.triggerEl = el), part: "chip-trigger" }, this.renderChipTriggerContent())) : (h("div", { class: triggerClass, style: { width: this.triggerWidth }, role: "combobox", "aria-haspopup": "listbox", "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.isOpen && this.highlightedIndex >= 0 ? this.optionDomId(this.highlightedIndex) : undefined, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.hideLabel ? this.label : undefined, id: this.componentId, tabIndex: this.disabled ? -1 : 0, onClick: this.onButtonTriggerClick, "data-layout": this.customTriggerContent, onKeyDown: this.onButtonTriggerKeyDown, onKeyUp: this.onButtonTriggerKeyUp, ref: el => (this.triggerEl = el), part: "button-trigger" }, this.renderButtonTriggerContent())), this.renderMessages(), h("div", { key: '48041c41a39987f2f4093bd048d6c495ea69514b', style: { display: 'none' } }, h("slot", { key: '5f4d95fefbd3dbdb33ecb25af593c9429df0e10b', onSlotchange: () => this.updateOptions() })), this.renderDropdown())));
2332
+ }, type: "text", role: "combobox", "aria-autocomplete": "list", "aria-haspopup": this.inputPopupRole === 'tree' ? 'tree' : undefined, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.hideLabel ? this.label : undefined, id: this.componentId, value: this.displayText, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput, onClick: this.handleInputClick, onKeyDown: this.handleKeyDown, autocomplete: "off", part: "input" }), h("pds-icon", { icon: "enlarge", "aria-hidden": "true", class: "pds-combobox__input-icon" }))) : this.trigger === 'chip' ? (h("div", { class: this.getChipTriggerClass(), style: { width: this.triggerWidth }, role: this.usesPanelSearch ? 'button' : 'combobox', "aria-haspopup": this.triggerPopupRole, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.triggerAriaLabel, "aria-labelledby": this.triggerLabelledBy, id: this.componentId, tabIndex: this.disabled ? -1 : 0, onClick: this.onButtonTriggerClick, "data-layout": this.customTriggerContent, onKeyDown: this.onButtonTriggerKeyDown, onKeyUp: this.onButtonTriggerKeyUp, ref: el => (this.triggerEl = el), part: "chip-trigger" }, this.renderChipTriggerContent())) : (h("div", { class: triggerClass, style: { width: this.triggerWidth }, role: this.usesPanelSearch ? 'button' : 'combobox', "aria-haspopup": this.triggerPopupRole, "aria-controls": this.listboxId, "aria-describedby": assignDescription(this.componentId, this.isInvalid(), this.helperMessage, this.errorMessage), "aria-invalid": this.isInvalid() ? 'true' : undefined, "aria-activedescendant": this.usesPanelSearch ? undefined : this.activeDescendantId, "aria-expanded": this.isOpen ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.triggerAriaLabel, "aria-labelledby": this.triggerLabelledBy, id: this.componentId, tabIndex: this.disabled ? -1 : 0, onClick: this.onButtonTriggerClick, "data-layout": this.customTriggerContent, onKeyDown: this.onButtonTriggerKeyDown, onKeyUp: this.onButtonTriggerKeyUp, ref: el => (this.triggerEl = el), part: "button-trigger" }, this.renderButtonTriggerContent())), this.renderMessages(), h("div", { key: 'fd1eb34561f56df05e2f44c7acecde0e56b5e714', style: { display: 'none' } }, h("slot", { key: '68e11626a00175d0926c54086ae815be157e6bf0', onSlotchange: () => this.updateOptions() })), this.renderDropdown(), this.groupDisplay !== 'inline' && (h("span", { key: '6f3c60c3400d411abbed08ddc6e596db097bb097', class: "visually-hidden", role: "status", "aria-live": "polite" }, this.isOpen && this.effectiveGroupDisplay === 'drill-in' && this.openGroup !== null ? this.openGroup.label : '')))));
1495
2333
  }
1496
2334
  static get is() { return "pds-combobox"; }
1497
2335
  static get encapsulation() { return "shadow"; }
@@ -1700,6 +2538,98 @@ export class PdsCombobox {
1700
2538
  "attribute": "dropdown-mount",
1701
2539
  "defaultValue": "'host'"
1702
2540
  },
2541
+ "drillInBackLabel": {
2542
+ "type": "string",
2543
+ "mutable": false,
2544
+ "complexType": {
2545
+ "original": "string",
2546
+ "resolved": "string",
2547
+ "references": {}
2548
+ },
2549
+ "required": false,
2550
+ "optional": false,
2551
+ "docs": {
2552
+ "tags": [{
2553
+ "name": "defaultValue",
2554
+ "text": "'Back'"
2555
+ }],
2556
+ "text": "Accessible prefix for the row that returns to the group list when `group-display` is\n`drill-in`, read before the open group's label. Pass a translated string to localize it; defaults to English."
2557
+ },
2558
+ "getter": false,
2559
+ "setter": false,
2560
+ "reflect": false,
2561
+ "attribute": "drill-in-back-label",
2562
+ "defaultValue": "'Back'"
2563
+ },
2564
+ "flyoutMaxWidth": {
2565
+ "type": "string",
2566
+ "mutable": false,
2567
+ "complexType": {
2568
+ "original": "string",
2569
+ "resolved": "string",
2570
+ "references": {}
2571
+ },
2572
+ "required": false,
2573
+ "optional": false,
2574
+ "docs": {
2575
+ "tags": [{
2576
+ "name": "default",
2577
+ "text": "'380px'"
2578
+ }],
2579
+ "text": "Maximum width of a group's flyout when `group-display` is `flyout`. Any valid CSS width value."
2580
+ },
2581
+ "getter": false,
2582
+ "setter": false,
2583
+ "reflect": false,
2584
+ "attribute": "flyout-max-width",
2585
+ "defaultValue": "'380px'"
2586
+ },
2587
+ "flyoutMinWidth": {
2588
+ "type": "string",
2589
+ "mutable": false,
2590
+ "complexType": {
2591
+ "original": "string",
2592
+ "resolved": "string",
2593
+ "references": {}
2594
+ },
2595
+ "required": false,
2596
+ "optional": false,
2597
+ "docs": {
2598
+ "tags": [{
2599
+ "name": "default",
2600
+ "text": "'280px'"
2601
+ }],
2602
+ "text": "Minimum width of a group's flyout when `group-display` is `flyout`. Any valid CSS width value.\nThe flyout is as wide as its options, between this and `flyout-max-width`."
2603
+ },
2604
+ "getter": false,
2605
+ "setter": false,
2606
+ "reflect": false,
2607
+ "attribute": "flyout-min-width",
2608
+ "defaultValue": "'280px'"
2609
+ },
2610
+ "groupDisplay": {
2611
+ "type": "string",
2612
+ "mutable": false,
2613
+ "complexType": {
2614
+ "original": "'inline' | 'flyout' | 'drill-in'",
2615
+ "resolved": "\"drill-in\" | \"flyout\" | \"inline\"",
2616
+ "references": {}
2617
+ },
2618
+ "required": false,
2619
+ "optional": false,
2620
+ "docs": {
2621
+ "tags": [{
2622
+ "name": "default",
2623
+ "text": "'inline'"
2624
+ }],
2625
+ "text": "How option groups (`<optgroup>`, or the `group` field of `options`) are shown in the dropdown.\n`inline` lists each group's label above its options. `flyout` lists the groups, and opens a\ngroup's options in a panel beside it on hover or click; while searching, the matching options\nare listed under their group labels, and search also matches group labels. `flyout` groups\nby `<optgroup>` (or `group`) only: options under a `<pds-text>` heading are listed ungrouped.\n`drill-in` lists the groups too, but a click replaces the list with that group's options and a\nrow back to the groups; use it where nothing can open beside the dropdown, such as a side\npanel. `flyout` falls back to `drill-in` when there is no room for a flyout on either side."
2626
+ },
2627
+ "getter": false,
2628
+ "setter": false,
2629
+ "reflect": false,
2630
+ "attribute": "group-display",
2631
+ "defaultValue": "'inline'"
2632
+ },
1703
2633
  "helperMessage": {
1704
2634
  "type": "string",
1705
2635
  "mutable": false,
@@ -1819,6 +2749,29 @@ export class PdsCombobox {
1819
2749
  "attribute": "mode",
1820
2750
  "defaultValue": "'filter'"
1821
2751
  },
2752
+ "panelSearch": {
2753
+ "type": "boolean",
2754
+ "mutable": false,
2755
+ "complexType": {
2756
+ "original": "boolean",
2757
+ "resolved": "boolean",
2758
+ "references": {}
2759
+ },
2760
+ "required": false,
2761
+ "optional": false,
2762
+ "docs": {
2763
+ "tags": [{
2764
+ "name": "default",
2765
+ "text": "false"
2766
+ }],
2767
+ "text": "Shows a search field at the top of the dropdown for the `button` and `chip` triggers, which\ncan't be typed into. Search filters the options the same way typing in the `input` trigger\ndoes; in a grouped display it also matches group labels."
2768
+ },
2769
+ "getter": false,
2770
+ "setter": false,
2771
+ "reflect": false,
2772
+ "attribute": "panel-search",
2773
+ "defaultValue": "false"
2774
+ },
1822
2775
  "placeholder": {
1823
2776
  "type": "string",
1824
2777
  "mutable": false,
@@ -1838,6 +2791,52 @@ export class PdsCombobox {
1838
2791
  "reflect": false,
1839
2792
  "attribute": "placeholder"
1840
2793
  },
2794
+ "searchOptionsLabel": {
2795
+ "type": "string",
2796
+ "mutable": false,
2797
+ "complexType": {
2798
+ "original": "string",
2799
+ "resolved": "string",
2800
+ "references": {}
2801
+ },
2802
+ "required": false,
2803
+ "optional": false,
2804
+ "docs": {
2805
+ "tags": [{
2806
+ "name": "defaultValue",
2807
+ "text": "'Search options'"
2808
+ }],
2809
+ "text": "Accessible label for the search input inside the dropdown panel (`panel-search`). Pass a translated string to localize it; defaults to English."
2810
+ },
2811
+ "getter": false,
2812
+ "setter": false,
2813
+ "reflect": false,
2814
+ "attribute": "search-options-label",
2815
+ "defaultValue": "'Search options'"
2816
+ },
2817
+ "searchPlaceholder": {
2818
+ "type": "string",
2819
+ "mutable": false,
2820
+ "complexType": {
2821
+ "original": "string",
2822
+ "resolved": "string",
2823
+ "references": {}
2824
+ },
2825
+ "required": false,
2826
+ "optional": false,
2827
+ "docs": {
2828
+ "tags": [{
2829
+ "name": "defaultValue",
2830
+ "text": "'Find...'"
2831
+ }],
2832
+ "text": "Placeholder text for the search input inside the dropdown panel (`panel-search`). Pass a translated string to localize it; defaults to English."
2833
+ },
2834
+ "getter": false,
2835
+ "setter": false,
2836
+ "reflect": false,
2837
+ "attribute": "search-placeholder",
2838
+ "defaultValue": "'Find...'"
2839
+ },
1841
2840
  "trigger": {
1842
2841
  "type": "string",
1843
2842
  "mutable": false,
@@ -1935,7 +2934,7 @@ export class PdsCombobox {
1935
2934
  "mutable": false,
1936
2935
  "complexType": {
1937
2936
  "original": "ChipSentimentType",
1938
- "resolved": "\"accent\" | \"brand\" | \"danger\" | \"info\" | \"neutral\" | \"success\" | \"warning\"",
2937
+ "resolved": "\"accent\" | \"brand\" | \"danger\" | \"info\" | \"neutral\" | \"solid\" | \"subtle\" | \"success\" | \"warning\"",
1939
2938
  "references": {
1940
2939
  "ChipSentimentType": {
1941
2940
  "location": "import",
@@ -2200,6 +3199,12 @@ export class PdsCombobox {
2200
3199
  "filteredItems": {},
2201
3200
  "highlightedIndex": {},
2202
3201
  "isOpen": {},
3202
+ "openGroup": {},
3203
+ "drillDirection": {},
3204
+ "highlightedGroup": {},
3205
+ "keyboardNavigating": {},
3206
+ "panelQuery": {},
3207
+ "flyoutFallback": {},
2203
3208
  "selectedOption": {},
2204
3209
  "selectedOptionLayoutContent": {},
2205
3210
  "selectedOptionChipProps": {},
@@ -2328,6 +3333,15 @@ export class PdsCombobox {
2328
3333
  static get elementRef() { return "el"; }
2329
3334
  static get watchers() {
2330
3335
  return [{
3336
+ "propName": "groupDisplay",
3337
+ "methodName": "groupDisplayChanged"
3338
+ }, {
3339
+ "propName": "openGroup",
3340
+ "methodName": "openGroupChanged"
3341
+ }, {
3342
+ "propName": "flyoutFallback",
3343
+ "methodName": "flyoutFallbackChanged"
3344
+ }, {
2331
3345
  "propName": "isOpen",
2332
3346
  "methodName": "isOpenChanged"
2333
3347
  }, {