@ds-mo/ui 14.15.0 → 14.16.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 (245) hide show
  1. package/dist/.package-ready.json +1 -1
  2. package/dist/agent-patterns.json +3 -3
  3. package/dist/agent.json +161 -24
  4. package/dist/angular/ds-button-unfilled.d.ts +1 -1
  5. package/dist/angular/ds-button-unfilled.js +3 -3
  6. package/dist/angular/ds-filter-menu.d.ts +1 -1
  7. package/dist/angular/ds-filter-menu.js +3 -3
  8. package/dist/angular/ds-select.d.ts +1 -1
  9. package/dist/angular/ds-select.js +3 -3
  10. package/dist/angular/ds-table-sort.d.ts +16 -0
  11. package/dist/angular/ds-table-sort.js +43 -0
  12. package/dist/angular/index.d.ts +1 -1
  13. package/dist/angular/index.js +1 -0
  14. package/dist/angular/proxies.d.ts +1 -0
  15. package/dist/angular/proxies.js +1 -0
  16. package/dist/components/ds-agent-activity.js +1 -1
  17. package/dist/components/ds-agent-questionnaire.js +1 -1
  18. package/dist/components/ds-agent-response.js +1 -1
  19. package/dist/components/ds-agent-source-list.js +1 -1
  20. package/dist/components/ds-agent-tool-call.js +1 -1
  21. package/dist/components/ds-attachment-list.js +1 -1
  22. package/dist/components/ds-badge.js +1 -1
  23. package/dist/components/ds-banner.js +1 -1
  24. package/dist/components/ds-banner.js.map +1 -1
  25. package/dist/components/ds-bar-action.js +1 -1
  26. package/dist/components/ds-bar-action.js.map +1 -1
  27. package/dist/components/ds-bar-nav.js +1 -1
  28. package/dist/components/ds-bar-title.js +1 -1
  29. package/dist/components/ds-bar-workflow.js +1 -1
  30. package/dist/components/ds-bar-workflow.js.map +1 -1
  31. package/dist/components/ds-breadcrumb.js +1 -1
  32. package/dist/components/ds-button-filled.js +1 -1
  33. package/dist/components/ds-button-unfilled.js +1 -1
  34. package/dist/components/ds-card-chart.js +1 -1
  35. package/dist/components/ds-card-chart.js.map +1 -1
  36. package/dist/components/ds-card-overview.js +1 -1
  37. package/dist/components/ds-card-overview.js.map +1 -1
  38. package/dist/components/ds-card-setting.js +1 -1
  39. package/dist/components/ds-card-setting.js.map +1 -1
  40. package/dist/components/ds-chart-legend.js +1 -1
  41. package/dist/components/ds-chart-legend.js.map +1 -1
  42. package/dist/components/ds-chart.js +1 -1
  43. package/dist/components/ds-chart.js.map +1 -1
  44. package/dist/components/ds-checkbox.js +1 -1
  45. package/dist/components/ds-chip.js +1 -1
  46. package/dist/components/ds-chip.js.map +1 -1
  47. package/dist/components/ds-code-block.js +1 -1
  48. package/dist/components/ds-conversation-list-item.js +1 -1
  49. package/dist/components/ds-conversation-list-item.js.map +1 -1
  50. package/dist/components/ds-conversation-list-section.js +1 -1
  51. package/dist/components/ds-empty-state.js +1 -1
  52. package/dist/components/ds-field.js +1 -1
  53. package/dist/components/ds-filter-menu.js +1 -1
  54. package/dist/components/ds-filter-menu.js.map +1 -1
  55. package/dist/components/ds-input.js +1 -1
  56. package/dist/components/ds-loader.js +1 -1
  57. package/dist/components/ds-markdown.js +1 -1
  58. package/dist/components/ds-menu.js +1 -1
  59. package/dist/components/ds-message-actions.js +1 -1
  60. package/dist/components/ds-message-actions.js.map +1 -1
  61. package/dist/components/ds-message-composer.js +1 -1
  62. package/dist/components/ds-message-composer.js.map +1 -1
  63. package/dist/components/ds-message-scroller.js +1 -1
  64. package/dist/components/ds-message-scroller.js.map +1 -1
  65. package/dist/components/ds-message.js +1 -1
  66. package/dist/components/ds-mobile-bar-nav.js +1 -1
  67. package/dist/components/ds-mobile-header.js +1 -1
  68. package/dist/components/ds-mobile-section-switcher.js +1 -1
  69. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  70. package/dist/components/ds-modal.js +1 -1
  71. package/dist/components/ds-pagination.js +1 -1
  72. package/dist/components/ds-panel-nav.js +1 -1
  73. package/dist/components/ds-panel-sub-nav.js +1 -1
  74. package/dist/components/ds-panel-sub-nav.js.map +1 -1
  75. package/dist/components/ds-panel-tool-header.js +1 -1
  76. package/dist/components/ds-panel-tool-search.js +1 -1
  77. package/dist/components/ds-panel-tool-search.js.map +1 -1
  78. package/dist/components/ds-panel-tools.js +1 -1
  79. package/dist/components/ds-paper-texture.js +1 -1
  80. package/dist/components/ds-radio.js +1 -1
  81. package/dist/components/ds-scroll-overlay.js +1 -1
  82. package/dist/components/ds-scroll-overlay.js.map +1 -1
  83. package/dist/components/ds-select.js +1 -1
  84. package/dist/components/ds-shell-app.js +1 -1
  85. package/dist/components/ds-shell-app.js.map +1 -1
  86. package/dist/components/ds-shell-page.js +1 -1
  87. package/dist/components/ds-shell-tools.js +1 -1
  88. package/dist/components/ds-skeleton.js +1 -1
  89. package/dist/components/ds-slider.js +1 -1
  90. package/dist/components/ds-slider.js.map +1 -1
  91. package/dist/components/ds-swatch-picker.js +1 -1
  92. package/dist/components/ds-switch.js +1 -1
  93. package/dist/components/ds-tab-group.js +1 -1
  94. package/dist/components/ds-table-saved-views.js +1 -1
  95. package/dist/components/ds-table-saved-views.js.map +1 -1
  96. package/dist/components/ds-table-sort.d.ts +11 -0
  97. package/dist/components/ds-table-sort.js +2 -0
  98. package/dist/components/ds-table-sort.js.map +1 -0
  99. package/dist/components/ds-table-toolbar.js +1 -1
  100. package/dist/components/ds-table-toolbar.js.map +1 -1
  101. package/dist/components/ds-table.js +1 -1
  102. package/dist/components/ds-table.js.map +1 -1
  103. package/dist/components/ds-tag.js +1 -1
  104. package/dist/components/ds-text.js +1 -1
  105. package/dist/components/ds-toast.js +1 -1
  106. package/dist/components/ds-toast.js.map +1 -1
  107. package/dist/components/ds-tooltip-chart.js +1 -1
  108. package/dist/components/ds-tooltip.js +1 -1
  109. package/dist/components/ds-typing-indicator.js +1 -1
  110. package/dist/components/index.js +1 -1
  111. package/dist/components/index.js.map +1 -1
  112. package/dist/components/{p-CjgnFGd6.js → p-0IbKxhqM.js} +2 -2
  113. package/dist/components/{p-CjgnFGd6.js.map → p-0IbKxhqM.js.map} +1 -1
  114. package/dist/components/p-23u3lIr1.js +2 -0
  115. package/dist/components/{p-DLkNcCY0.js.map → p-23u3lIr1.js.map} +1 -1
  116. package/dist/components/{p-DfyfrfHL.js → p-3dYwA4i_.js} +2 -2
  117. package/dist/components/{p-DfyfrfHL.js.map → p-3dYwA4i_.js.map} +1 -1
  118. package/dist/components/{p-C1q-tq3U.js → p-7tZx4ukz.js} +2 -2
  119. package/dist/components/{p-C5TrNd31.js → p-B0PEg5RF.js} +2 -2
  120. package/dist/components/{p-C5TrNd31.js.map → p-B0PEg5RF.js.map} +1 -1
  121. package/dist/components/{p-DZFilC--.js → p-BGXqFq2C.js} +2 -2
  122. package/dist/components/{p-Rfpd5mWO.js → p-BHDkJmrC.js} +2 -2
  123. package/dist/components/{p-Rfpd5mWO.js.map → p-BHDkJmrC.js.map} +1 -1
  124. package/dist/components/{p-BXPQmp7q.js → p-BK-JjdoN.js} +2 -2
  125. package/dist/components/p-BUhShHfb.js +2 -0
  126. package/dist/components/{p-URdgCwEs.js.map → p-BUhShHfb.js.map} +1 -1
  127. package/dist/components/{p-CKY989vF.js → p-BUrbVZJU.js} +2 -2
  128. package/dist/components/{p-CKY989vF.js.map → p-BUrbVZJU.js.map} +1 -1
  129. package/dist/components/{p-V2ywPfsR.js → p-BVlfQ8xZ.js} +2 -2
  130. package/dist/components/{p-V2ywPfsR.js.map → p-BVlfQ8xZ.js.map} +1 -1
  131. package/dist/components/p-BdnQv7Zl.js +2 -0
  132. package/dist/components/p-BdnQv7Zl.js.map +1 -0
  133. package/dist/components/{p-Btxwp4lJ.js → p-BhYWdbEp.js} +2 -2
  134. package/dist/components/{p-Btxwp4lJ.js.map → p-BhYWdbEp.js.map} +1 -1
  135. package/dist/components/{p-B80NfEqc.js → p-C0X2zKbB.js} +2 -2
  136. package/dist/components/{p-Cyw9odJX.js → p-C4p2i59V.js} +2 -2
  137. package/dist/components/{p-Cyw9odJX.js.map → p-C4p2i59V.js.map} +1 -1
  138. package/dist/components/p-C91izpHV.js +2 -0
  139. package/dist/components/{p-CnWC0QvG.js.map → p-C91izpHV.js.map} +1 -1
  140. package/dist/components/p-COVcyUAA.js +2 -0
  141. package/dist/components/p-COVcyUAA.js.map +1 -0
  142. package/dist/components/{p-D1Nqlrrl.js → p-C_TOgX82.js} +2 -2
  143. package/dist/components/{p-D1Nqlrrl.js.map → p-C_TOgX82.js.map} +1 -1
  144. package/dist/components/{p-Ct-m0I6U.js → p-CaAZ-D08.js} +2 -2
  145. package/dist/components/p-Cr2VT5zv.js +2 -0
  146. package/dist/components/{p-M9DPfXbP.js.map → p-Cr2VT5zv.js.map} +1 -1
  147. package/dist/components/{p-Dsn1RCS5.js → p-Ct2GVdSI.js} +2 -2
  148. package/dist/components/{p-Dsn1RCS5.js.map → p-Ct2GVdSI.js.map} +1 -1
  149. package/dist/components/p-CwkcFMMe.js +2 -0
  150. package/dist/components/{p-CWDmHPJC.js.map → p-CwkcFMMe.js.map} +1 -1
  151. package/dist/components/p-CxBE3l74.js +2 -0
  152. package/dist/components/{p-CKJzHclc.js.map → p-CxBE3l74.js.map} +1 -1
  153. package/dist/components/{p-PzI11fs8.js → p-CzcWZiH-.js} +2 -2
  154. package/dist/components/{p-PzI11fs8.js.map → p-CzcWZiH-.js.map} +1 -1
  155. package/dist/components/{p-BagXfn1G.js → p-D1YhYulL.js} +2 -2
  156. package/dist/components/{p-BzObtSlo.js → p-D2z2DgtX.js} +2 -2
  157. package/dist/components/{p-BzObtSlo.js.map → p-D2z2DgtX.js.map} +1 -1
  158. package/dist/components/{p-DcPVSNDr.js → p-D8Qd6gkf.js} +2 -2
  159. package/dist/components/{p-DcPVSNDr.js.map → p-D8Qd6gkf.js.map} +1 -1
  160. package/dist/components/p-DAV32g35.js +2 -0
  161. package/dist/components/p-DAV32g35.js.map +1 -0
  162. package/dist/components/{p-BHjw0x3L.js → p-DAqSoyd4.js} +2 -2
  163. package/dist/components/{p-BHjw0x3L.js.map → p-DAqSoyd4.js.map} +1 -1
  164. package/dist/components/{p-Bf_D3crn.js → p-DNJTN_s0.js} +2 -2
  165. package/dist/components/{p-DL_5VENG.js → p-DQqOzDEq.js} +2 -2
  166. package/dist/components/{p-C5eosu67.js → p-DRBTr6TS.js} +2 -2
  167. package/dist/components/{p-C5eosu67.js.map → p-DRBTr6TS.js.map} +1 -1
  168. package/dist/components/{p-D-kWb8cF.js → p-DSIOINBK.js} +2 -2
  169. package/dist/components/{p-QdI17bl-.js → p-Dg9qiMDa.js} +2 -2
  170. package/dist/components/{p-DTMxwYQj.js → p-EWI6ccs-.js} +2 -2
  171. package/dist/components/p-JPTkZ3Ji.js +2 -0
  172. package/dist/components/p-JPTkZ3Ji.js.map +1 -0
  173. package/dist/components/p-MxptXwlW.js +2 -0
  174. package/dist/components/{p-C2n8A8NA.js.map → p-MxptXwlW.js.map} +1 -1
  175. package/dist/components/{p-B7Hf86NF.js → p-PD1Vfg0t.js} +2 -2
  176. package/dist/components/{p-B7Hf86NF.js.map → p-PD1Vfg0t.js.map} +1 -1
  177. package/dist/components/{p-BxqBiVbH.js → p-U8JIZzI2.js} +2 -2
  178. package/dist/components/{p-BxqBiVbH.js.map → p-U8JIZzI2.js.map} +1 -1
  179. package/dist/components/{p-Ck_VRQYi.js → p-WGZWqh-B.js} +2 -2
  180. package/dist/components/{p-Ck_VRQYi.js.map → p-WGZWqh-B.js.map} +1 -1
  181. package/dist/components/{p-vWK5faWZ.js → p-a6qP60zb.js} +2 -2
  182. package/dist/components/p-vzxD117T.js +2 -0
  183. package/dist/components/{p-BzLnSwG9.js.map → p-vzxD117T.js.map} +1 -1
  184. package/dist/components/{p-jMCRaxQW.js → p-y4jhIkRc.js} +2 -2
  185. package/dist/components/{p-jMCRaxQW.js.map → p-y4jhIkRc.js.map} +1 -1
  186. package/dist/docs/components.json +326 -11
  187. package/dist/mcp-data/registry/button-unfilled.json +26 -7
  188. package/dist/mcp-data/registry/filter-menu.json +25 -6
  189. package/dist/mcp-data/registry/loader.json +1 -1
  190. package/dist/mcp-data/registry/registry.json +163 -23
  191. package/dist/mcp-data/registry/select.json +22 -3
  192. package/dist/mcp-data/registry/shell-app.json +3 -3
  193. package/dist/mcp-data/registry/table-saved-views.json +4 -4
  194. package/dist/mcp-data/registry/table-sort.json +229 -0
  195. package/dist/mcp-data/registry/table-toolbar.json +19 -12
  196. package/dist/mcp-data/registry/table.json +16 -9
  197. package/dist/react/components.d.ts +1 -0
  198. package/dist/react/components.js +1 -0
  199. package/dist/react/ds-table-sort.d.ts +12 -0
  200. package/dist/react/ds-table-sort.js +12 -0
  201. package/dist/types/components/ButtonUnfilled/ButtonUnfilled.d.ts +16 -1
  202. package/dist/types/components/FilterMenu/FilterMenu.d.ts +11 -0
  203. package/dist/types/components/Select/Select.d.ts +11 -0
  204. package/dist/types/components/Table/Table.d.ts +5 -0
  205. package/dist/types/components/Table/table-column-customizer.d.ts +2 -0
  206. package/dist/types/components/Table/table-pagination-shortcut.d.ts +16 -0
  207. package/dist/types/components/TableSort/TableSort.d.ts +24 -0
  208. package/dist/types/components/TableSort/table-sort-menu.d.ts +15 -0
  209. package/dist/types/components/TableToolbar/TableToolbar.d.ts +6 -0
  210. package/dist/types/components.d.ts +102 -6
  211. package/dist/types/utils/table-caption-compact.d.ts +6 -0
  212. package/dist/vue/components.d.ts +1 -0
  213. package/dist/vue/components.js +1 -0
  214. package/dist/vue/ds-button-unfilled.js +1 -0
  215. package/dist/vue/ds-filter-menu.js +1 -0
  216. package/dist/vue/ds-select.js +1 -0
  217. package/dist/vue/ds-table-sort.d.ts +3 -0
  218. package/dist/vue/ds-table-sort.js +13 -0
  219. package/package.json +1 -1
  220. package/dist/components/p-BzLnSwG9.js +0 -2
  221. package/dist/components/p-C2n8A8NA.js +0 -2
  222. package/dist/components/p-C7rzVotG.js +0 -2
  223. package/dist/components/p-C7rzVotG.js.map +0 -1
  224. package/dist/components/p-CKJzHclc.js +0 -2
  225. package/dist/components/p-CWDmHPJC.js +0 -2
  226. package/dist/components/p-Ca-I2UnB.js +0 -2
  227. package/dist/components/p-Ca-I2UnB.js.map +0 -1
  228. package/dist/components/p-CnWC0QvG.js +0 -2
  229. package/dist/components/p-CooNucm0.js +0 -2
  230. package/dist/components/p-CooNucm0.js.map +0 -1
  231. package/dist/components/p-DLkNcCY0.js +0 -2
  232. package/dist/components/p-M9DPfXbP.js +0 -2
  233. package/dist/components/p-URdgCwEs.js +0 -2
  234. /package/dist/components/{p-C1q-tq3U.js.map → p-7tZx4ukz.js.map} +0 -0
  235. /package/dist/components/{p-DZFilC--.js.map → p-BGXqFq2C.js.map} +0 -0
  236. /package/dist/components/{p-BXPQmp7q.js.map → p-BK-JjdoN.js.map} +0 -0
  237. /package/dist/components/{p-B80NfEqc.js.map → p-C0X2zKbB.js.map} +0 -0
  238. /package/dist/components/{p-Ct-m0I6U.js.map → p-CaAZ-D08.js.map} +0 -0
  239. /package/dist/components/{p-BagXfn1G.js.map → p-D1YhYulL.js.map} +0 -0
  240. /package/dist/components/{p-Bf_D3crn.js.map → p-DNJTN_s0.js.map} +0 -0
  241. /package/dist/components/{p-DL_5VENG.js.map → p-DQqOzDEq.js.map} +0 -0
  242. /package/dist/components/{p-D-kWb8cF.js.map → p-DSIOINBK.js.map} +0 -0
  243. /package/dist/components/{p-QdI17bl-.js.map → p-Dg9qiMDa.js.map} +0 -0
  244. /package/dist/components/{p-DTMxwYQj.js.map → p-EWI6ccs-.js.map} +0 -0
  245. /package/dist/components/{p-vWK5faWZ.js.map → p-a6qP60zb.js.map} +0 -0
@@ -43,8 +43,8 @@
43
43
  "The default view is a non-editable template. Application-owned custom views appear in a separate section with Rename and Remove actions. New view opens the naming dialog. When an editable selected view is dirty, that view row shows Save and negative Discard supporting actions while the footer remains dedicated to New view. The application decides which table state to capture, restore, and persist."
44
44
  ],
45
45
  "responsiveBehavior": [
46
- "The control remains a single toolbar item and relies on Select, Menu, and Modal collision and viewport behavior. Its choice popup keeps the token-backed 200px menu-width-xs minimum.",
47
- "Place it in the leading region of ds-table-toolbar so narrow table headers preserve the expected control order."
46
+ "The control remains a single toolbar item and relies on Select, Menu, and Modal collision and viewport behavior. Its trigger is capped at the compact menu-width token so long view names truncate while the dirty dot stays visible. Its choice popup keeps that same token as the minimum width.",
47
+ "Place it in the start region of ds-table-toolbar so the middle region can keep left-side and right-side controls on one spanning cluster."
48
48
  ],
49
49
  "frameworkCaveats": {
50
50
  "customElements": [
@@ -371,12 +371,12 @@
371
371
  "files": [
372
372
  {
373
373
  "path": "src/wc/components/TableSavedViews/TableSavedViews.css",
374
- "content": ":host {\n --ds-choice-popup-min-inline-size: var(--dimension-menu-width-xs);\n\n display: inline-block;\n min-inline-size: 0;\n}\n\nds-select {\n display: block;\n}\n\n.table-saved-views__dialog-actions {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: var(--dimension-space-100);\n}\n",
374
+ "content": ":host {\n --ds-choice-popup-min-inline-size: var(--dimension-menu-width-xs);\n\n display: inline-block;\n min-inline-size: 0;\n max-inline-size: var(--dimension-menu-width-xs);\n}\n\nds-select {\n display: block;\n min-inline-size: 0;\n max-inline-size: 100%;\n}\n\n.table-saved-views__dialog-actions {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: var(--dimension-space-100);\n}\n",
375
375
  "type": "registry:ui"
376
376
  },
377
377
  {
378
378
  "path": "src/wc/components/TableSavedViews/TableSavedViews.tsx",
379
- "content": "import { Component, Element, Event, EventEmitter, h, Host, Prop, State } from '@stencil/core';\nimport type { MenuItemData } from '../Menu/menu-types';\nimport type {\n SelectOptionActionDetail,\n SelectOptionSubtextActionDetail,\n SelectValue,\n} from '../Select/Select';\nimport type {\n TableSavedView,\n TableSavedViewChangeDetail,\n TableSavedViewCreateDetail,\n TableSavedViewDiscardDetail,\n TableSavedViewRemoveDetail,\n TableSavedViewRenameDetail,\n TableSavedViewSaveDetail,\n} from './table-saved-views-types';\n\ntype SavedViewDialogMode = 'create' | 'rename';\n\nlet tableSavedViewsId = 0;\n\n@Component({\n tag: 'ds-table-saved-views',\n styleUrl: 'TableSavedViews.css',\n scoped: true,\n})\nexport class TableSavedViews {\n @Element() el!: HTMLElement;\n\n /** Application-owned custom saved views. Assign arrays through the JavaScript property. */\n @Prop() views: TableSavedView[] = [];\n /** ID of the controlled active view, including the default view ID. */\n @Prop() value: string = '__default__';\n /** Whether the current table state differs from the selected custom view. Ignored for the default view. */\n @Prop() dirty: boolean = false;\n /** ID used for the built-in default view. */\n @Prop() defaultViewId: string = '__default__';\n /** Label used for the built-in default view. */\n @Prop() defaultViewLabel: string = 'Default';\n /** Accessible name for the saved-views select. */\n @Prop() label: string = 'Saved views';\n /** Trigger label shown while the default view is selected. */\n @Prop() triggerLabel: string = 'Views';\n /** Footer action label that opens the create-view dialog. */\n @Prop() createLabel: string = 'New view';\n\n /** Emitted when a view selection is requested. */\n @Event() dsViewChange!: EventEmitter<TableSavedViewChangeDetail>;\n /** Emitted after a valid create-view name is submitted. */\n @Event() dsViewCreate!: EventEmitter<TableSavedViewCreateDetail>;\n /** Emitted after a valid renamed view name is submitted. */\n @Event() dsViewRename!: EventEmitter<TableSavedViewRenameDetail>;\n /** Emitted when a custom view removal is requested. */\n @Event() dsViewRemove!: EventEmitter<TableSavedViewRemoveDetail>;\n /** Emitted when changes to the active custom view should replace its stored payload. */\n @Event() dsViewSave!: EventEmitter<TableSavedViewSaveDetail>;\n /** Emitted when the active custom view's stored payload should be restored. */\n @Event() dsViewDiscard!: EventEmitter<TableSavedViewDiscardDetail>;\n\n @State() private actionViewId: string | null = null;\n @State() private actionAnchorId: string | undefined;\n @State() private actionMenuOpen = false;\n @State() private actionMenuInitialFocusVisible = false;\n @State() private dialogOpen = false;\n @State() private dialogMode: SavedViewDialogMode = 'create';\n @State() private dialogViewId: string | null = null;\n @State() private nameDraft = '';\n @State() private nameError = '';\n\n private readonly componentId = `ds-table-saved-views-${++tableSavedViewsId}`;\n private readonly selectId = `${this.componentId}-select`;\n private readonly actionMenuId = `${this.componentId}-actions`;\n private readonly fieldId = `${this.componentId}-name-field`;\n private readonly inputId = `${this.componentId}-name-input`;\n private readonly actionItems: MenuItemData[] = [\n { label: 'Rename', value: 'rename' },\n { label: 'Remove', value: 'remove', isDestructive: true },\n ];\n\n private get selectedCustomView(): TableSavedView | undefined {\n return this.views.find(view => view.id === this.value);\n }\n\n private get dialogView(): TableSavedView | undefined {\n return this.views.find(view => view.id === this.dialogViewId);\n }\n\n private get sections() {\n const custom = this.views.map(view => ({\n label: view.label,\n value: view.id,\n subtextActions:\n this.dirty && view.id === this.selectedCustomView?.id\n ? [\n { label: 'Save', value: 'save' },\n { label: 'Discard', value: 'discard', tone: 'negative' as const },\n ]\n : undefined,\n action: {\n label: `Options for ${view.label}`,\n controls: this.actionMenuId,\n expanded: this.actionViewId === view.id && this.actionMenuOpen,\n },\n }));\n const templates = {\n header: 'Templates',\n options: [{ label: this.defaultViewLabel, value: this.defaultViewId }],\n divider: custom.length > 0,\n };\n return custom.length > 0 ? [templates, { header: 'Custom', options: custom }] : [templates];\n }\n\n private handleViewChange(value: SelectValue) {\n if (typeof value === 'string') this.dsViewChange.emit({ viewId: value });\n }\n\n private handleSubtextAction(detail: SelectOptionSubtextActionDetail) {\n const view = this.selectedCustomView;\n if (!view || !this.dirty || detail.value !== view.id) return;\n if (detail.actionValue === 'save') this.dsViewSave.emit({ viewId: view.id });\n if (detail.actionValue === 'discard') this.dsViewDiscard.emit({ viewId: view.id });\n }\n\n private handleOptionAction(detail: SelectOptionActionDetail) {\n if (!this.views.some(view => view.id === detail.value)) return;\n this.actionMenuInitialFocusVisible = detail.originalEvent.detail === 0;\n if (this.actionViewId === detail.value && this.actionMenuOpen) {\n this.actionMenuOpen = false;\n return;\n }\n this.actionViewId = detail.value;\n this.actionAnchorId = detail.anchorId;\n this.actionMenuOpen = true;\n }\n\n private handleActionSelect(event: CustomEvent<MenuItemData>) {\n const viewId = this.actionViewId;\n this.actionMenuOpen = false;\n if (!viewId) return;\n if (event.detail.value === 'rename') this.openDialog('rename', viewId);\n if (event.detail.value === 'remove') this.dsViewRemove.emit({ viewId });\n }\n\n private openDialog(mode: SavedViewDialogMode, viewId: string | null = null) {\n const view = mode === 'rename' ? this.views.find(item => item.id === viewId) : undefined;\n if (mode === 'rename' && !view) return;\n this.dialogMode = mode;\n this.dialogViewId = view?.id ?? null;\n this.nameDraft = view?.label ?? '';\n this.nameError = '';\n this.dialogOpen = true;\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n const input = this.el.querySelector<HTMLElement & { setFocus?: () => Promise<void> }>(\n `#${this.inputId}`\n );\n void input?.setFocus?.();\n });\n });\n }\n\n private validateName(): string {\n const name = this.nameDraft.trim();\n if (!name) return 'View name is required.';\n const duplicate = this.views.some(\n view =>\n view.id !== this.dialogViewId &&\n view.label.trim().toLocaleLowerCase() === name.toLocaleLowerCase()\n );\n if (\n duplicate ||\n this.defaultViewLabel.trim().toLocaleLowerCase() === name.toLocaleLowerCase()\n ) {\n return 'A view with this name already exists.';\n }\n return '';\n }\n\n private submitDialog() {\n const error = this.validateName();\n if (error) {\n this.nameError = error;\n return;\n }\n const name = this.nameDraft.trim();\n if (this.dialogMode === 'rename' && this.dialogView) {\n this.dsViewRename.emit({ viewId: this.dialogView.id, name });\n } else {\n this.dsViewCreate.emit({ name });\n }\n this.dialogOpen = false;\n }\n\n private finishDialogClose() {\n if (this.dialogOpen) return;\n this.dialogViewId = null;\n this.nameDraft = '';\n this.nameError = '';\n const select = this.el.querySelector<HTMLElement & { setFocus?: () => Promise<void> }>(\n `#${this.selectId}`\n );\n void select?.setFocus?.();\n }\n\n render() {\n return (\n <Host>\n <ds-select\n id={this.selectId}\n size=\"md\"\n placeholder={this.label}\n aria-label={this.label}\n sections={this.sections}\n value={this.value}\n triggerLabel={this.selectedCustomView ? undefined : this.triggerLabel}\n triggerLabelPlaceholder={!this.selectedCustomView}\n dot={this.dirty && Boolean(this.selectedCustomView)}\n footerActionLabel={this.createLabel}\n activeFill={false}\n allowClear={false}\n onDsChange={(event: CustomEvent<SelectValue>) => this.handleViewChange(event.detail)}\n onDsFooterAction={() => this.openDialog('create')}\n onDsOptionSubtextAction={(event: CustomEvent<SelectOptionSubtextActionDetail>) =>\n this.handleSubtextAction(event.detail)\n }\n onDsOptionAction={(event: CustomEvent<SelectOptionActionDetail>) =>\n this.handleOptionAction(event.detail)\n }\n />\n\n <ds-menu\n id={this.actionMenuId}\n anchorId={this.actionAnchorId}\n menuLabel=\"Saved view options\"\n side=\"bottom\"\n align=\"end\"\n menuWidth=\"max-content\"\n minWidth=\"0\"\n items={this.actionItems}\n open={this.actionMenuOpen}\n initialFocusVisible={this.actionMenuInitialFocusVisible}\n onDsClose={() => (this.actionMenuOpen = false)}\n onDsAfterClose={() => {\n if (this.actionMenuOpen) return;\n this.actionViewId = null;\n this.actionAnchorId = undefined;\n }}\n onDsSelect={(event: CustomEvent<MenuItemData>) => this.handleActionSelect(event)}\n />\n\n <ds-modal\n open={this.dialogOpen}\n heading={this.dialogMode === 'rename' ? 'Rename view' : 'Save as new view'}\n modalWidth=\"sm\"\n onDsClose={() => (this.dialogOpen = false)}\n onDsAfterClose={() => this.finishDialogClose()}\n >\n <ds-field\n label=\"Name\"\n fieldId={this.fieldId}\n error={this.nameError.length > 0}\n errorMessage={this.nameError}\n >\n <ds-input\n id={this.inputId}\n size=\"md\"\n placeholder=\"Enter a unique name\"\n value={this.nameDraft}\n autoFocus={true}\n onDsChange={(event: CustomEvent<string>) => {\n this.nameDraft = event.detail;\n if (this.nameError) this.nameError = '';\n }}\n onKeyDown={(event: KeyboardEvent) => {\n if (event.key === 'Enter') this.submitDialog();\n }}\n />\n </ds-field>\n\n <div slot=\"footer\" class=\"table-saved-views__dialog-actions\">\n <ds-button-filled\n label={this.dialogMode === 'rename' ? 'Rename' : 'Save'}\n size=\"md\"\n onDsClick={() => this.submitDialog()}\n />\n <ds-button-unfilled\n label=\"Cancel\"\n size=\"md\"\n onDsClick={() => (this.dialogOpen = false)}\n />\n </div>\n </ds-modal>\n </Host>\n );\n }\n}\n",
379
+ "content": "import { Component, Element, Event, EventEmitter, h, Host, Prop, State } from '@stencil/core';\nimport type { MenuItemData } from '../Menu/menu-types';\nimport type {\n SelectOptionActionDetail,\n SelectOptionSubtextActionDetail,\n SelectValue,\n} from '../Select/Select';\nimport type {\n TableSavedView,\n TableSavedViewChangeDetail,\n TableSavedViewCreateDetail,\n TableSavedViewDiscardDetail,\n TableSavedViewRemoveDetail,\n TableSavedViewRenameDetail,\n TableSavedViewSaveDetail,\n} from './table-saved-views-types';\n\ntype SavedViewDialogMode = 'create' | 'rename';\n\nlet tableSavedViewsId = 0;\n\n@Component({\n tag: 'ds-table-saved-views',\n styleUrl: 'TableSavedViews.css',\n scoped: true,\n})\nexport class TableSavedViews {\n @Element() el!: HTMLElement;\n\n /** Application-owned custom saved views. Assign arrays through the JavaScript property. */\n @Prop() views: TableSavedView[] = [];\n /** ID of the controlled active view, including the default view ID. */\n @Prop() value: string = '__default__';\n /** Whether the current table state differs from the selected custom view. Ignored for the default view. */\n @Prop() dirty: boolean = false;\n /** ID used for the built-in default view. */\n @Prop() defaultViewId: string = '__default__';\n /** Label used for the built-in default view. */\n @Prop() defaultViewLabel: string = 'Default';\n /** Accessible name for the saved-views select. */\n @Prop() label: string = 'Saved views';\n /** Trigger label shown while the default view is selected. */\n @Prop() triggerLabel: string = 'Views';\n /** Footer action label that opens the create-view dialog. */\n @Prop() createLabel: string = 'New view';\n\n /** Emitted when a view selection is requested. */\n @Event() dsViewChange!: EventEmitter<TableSavedViewChangeDetail>;\n /** Emitted after a valid create-view name is submitted. */\n @Event() dsViewCreate!: EventEmitter<TableSavedViewCreateDetail>;\n /** Emitted after a valid renamed view name is submitted. */\n @Event() dsViewRename!: EventEmitter<TableSavedViewRenameDetail>;\n /** Emitted when a custom view removal is requested. */\n @Event() dsViewRemove!: EventEmitter<TableSavedViewRemoveDetail>;\n /** Emitted when changes to the active custom view should replace its stored payload. */\n @Event() dsViewSave!: EventEmitter<TableSavedViewSaveDetail>;\n /** Emitted when the active custom view's stored payload should be restored. */\n @Event() dsViewDiscard!: EventEmitter<TableSavedViewDiscardDetail>;\n\n @State() private actionViewId: string | null = null;\n @State() private actionAnchorId: string | undefined;\n @State() private actionMenuOpen = false;\n @State() private actionMenuInitialFocusVisible = false;\n @State() private dialogOpen = false;\n @State() private dialogMode: SavedViewDialogMode = 'create';\n @State() private dialogViewId: string | null = null;\n @State() private nameDraft = '';\n @State() private nameError = '';\n\n private readonly componentId = `ds-table-saved-views-${++tableSavedViewsId}`;\n private readonly selectId = `${this.componentId}-select`;\n private readonly actionMenuId = `${this.componentId}-actions`;\n private readonly fieldId = `${this.componentId}-name-field`;\n private readonly inputId = `${this.componentId}-name-input`;\n private readonly actionItems: MenuItemData[] = [\n { label: 'Rename', value: 'rename' },\n { label: 'Remove', value: 'remove', isDestructive: true },\n ];\n\n private get selectedCustomView(): TableSavedView | undefined {\n return this.views.find(view => view.id === this.value);\n }\n\n private get dialogView(): TableSavedView | undefined {\n return this.views.find(view => view.id === this.dialogViewId);\n }\n\n private get sections() {\n const custom = this.views.map(view => ({\n label: view.label,\n value: view.id,\n subtextActions:\n this.dirty && view.id === this.selectedCustomView?.id\n ? [\n { label: 'Save', value: 'save' },\n { label: 'Discard', value: 'discard', tone: 'negative' as const },\n ]\n : undefined,\n action: {\n label: `Options for ${view.label}`,\n controls: this.actionMenuId,\n expanded: this.actionViewId === view.id && this.actionMenuOpen,\n },\n }));\n const templates = {\n header: 'Templates',\n options: [{ label: this.defaultViewLabel, value: this.defaultViewId }],\n divider: custom.length > 0,\n };\n return custom.length > 0 ? [templates, { header: 'Custom', options: custom }] : [templates];\n }\n\n private handleViewChange(value: SelectValue) {\n if (typeof value === 'string') this.dsViewChange.emit({ viewId: value });\n }\n\n private handleSubtextAction(detail: SelectOptionSubtextActionDetail) {\n const view = this.selectedCustomView;\n if (!view || !this.dirty || detail.value !== view.id) return;\n if (detail.actionValue === 'save') this.dsViewSave.emit({ viewId: view.id });\n if (detail.actionValue === 'discard') this.dsViewDiscard.emit({ viewId: view.id });\n }\n\n private handleOptionAction(detail: SelectOptionActionDetail) {\n if (!this.views.some(view => view.id === detail.value)) return;\n this.actionMenuInitialFocusVisible = detail.originalEvent.detail === 0;\n if (this.actionViewId === detail.value && this.actionMenuOpen) {\n this.actionMenuOpen = false;\n return;\n }\n this.actionViewId = detail.value;\n this.actionAnchorId = detail.anchorId;\n this.actionMenuOpen = true;\n }\n\n private handleActionSelect(event: CustomEvent<MenuItemData>) {\n const viewId = this.actionViewId;\n this.actionMenuOpen = false;\n if (!viewId) return;\n if (event.detail.value === 'rename') this.openDialog('rename', viewId);\n if (event.detail.value === 'remove') this.dsViewRemove.emit({ viewId });\n }\n\n private openDialog(mode: SavedViewDialogMode, viewId: string | null = null) {\n const view = mode === 'rename' ? this.views.find(item => item.id === viewId) : undefined;\n if (mode === 'rename' && !view) return;\n this.dialogMode = mode;\n this.dialogViewId = view?.id ?? null;\n this.nameDraft = view?.label ?? '';\n this.nameError = '';\n this.dialogOpen = true;\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n const input = this.el.querySelector<HTMLElement & { setFocus?: () => Promise<void> }>(\n `#${this.inputId}`\n );\n void input?.setFocus?.();\n });\n });\n }\n\n private validateName(): string {\n const name = this.nameDraft.trim();\n if (!name) return 'View name is required.';\n const duplicate = this.views.some(\n view =>\n view.id !== this.dialogViewId &&\n view.label.trim().toLocaleLowerCase() === name.toLocaleLowerCase()\n );\n if (\n duplicate ||\n this.defaultViewLabel.trim().toLocaleLowerCase() === name.toLocaleLowerCase()\n ) {\n return 'A view with this name already exists.';\n }\n return '';\n }\n\n private submitDialog() {\n const error = this.validateName();\n if (error) {\n this.nameError = error;\n return;\n }\n const name = this.nameDraft.trim();\n if (this.dialogMode === 'rename' && this.dialogView) {\n this.dsViewRename.emit({ viewId: this.dialogView.id, name });\n } else {\n this.dsViewCreate.emit({ name });\n }\n this.dialogOpen = false;\n }\n\n private finishDialogClose() {\n if (this.dialogOpen) return;\n this.dialogViewId = null;\n this.nameDraft = '';\n this.nameError = '';\n const select = this.el.querySelector<HTMLElement & { setFocus?: () => Promise<void> }>(\n `#${this.selectId}`\n );\n void select?.setFocus?.();\n }\n\n render() {\n return (\n <Host>\n <ds-select\n id={this.selectId}\n size=\"md\"\n width=\"fill\"\n placeholder={this.label}\n aria-label={this.label}\n sections={this.sections}\n value={this.value}\n triggerLabel={this.selectedCustomView ? undefined : this.triggerLabel}\n triggerLabelPlaceholder={!this.selectedCustomView}\n dot={this.dirty && Boolean(this.selectedCustomView)}\n footerActionLabel={this.createLabel}\n activeFill={false}\n allowClear={false}\n onDsChange={(event: CustomEvent<SelectValue>) => this.handleViewChange(event.detail)}\n onDsFooterAction={() => this.openDialog('create')}\n onDsOptionSubtextAction={(event: CustomEvent<SelectOptionSubtextActionDetail>) =>\n this.handleSubtextAction(event.detail)\n }\n onDsOptionAction={(event: CustomEvent<SelectOptionActionDetail>) =>\n this.handleOptionAction(event.detail)\n }\n />\n\n <ds-menu\n id={this.actionMenuId}\n anchorId={this.actionAnchorId}\n menuLabel=\"Saved view options\"\n side=\"bottom\"\n align=\"end\"\n menuWidth=\"max-content\"\n minWidth=\"0\"\n items={this.actionItems}\n open={this.actionMenuOpen}\n initialFocusVisible={this.actionMenuInitialFocusVisible}\n onDsClose={() => (this.actionMenuOpen = false)}\n onDsAfterClose={() => {\n if (this.actionMenuOpen) return;\n this.actionViewId = null;\n this.actionAnchorId = undefined;\n }}\n onDsSelect={(event: CustomEvent<MenuItemData>) => this.handleActionSelect(event)}\n />\n\n <ds-modal\n open={this.dialogOpen}\n heading={this.dialogMode === 'rename' ? 'Rename view' : 'Save as new view'}\n modalWidth=\"sm\"\n onDsClose={() => (this.dialogOpen = false)}\n onDsAfterClose={() => this.finishDialogClose()}\n >\n <ds-field\n label=\"Name\"\n fieldId={this.fieldId}\n error={this.nameError.length > 0}\n errorMessage={this.nameError}\n >\n <ds-input\n id={this.inputId}\n size=\"md\"\n placeholder=\"Enter a unique name\"\n value={this.nameDraft}\n autoFocus={true}\n onDsChange={(event: CustomEvent<string>) => {\n this.nameDraft = event.detail;\n if (this.nameError) this.nameError = '';\n }}\n onKeyDown={(event: KeyboardEvent) => {\n if (event.key === 'Enter') this.submitDialog();\n }}\n />\n </ds-field>\n\n <div slot=\"footer\" class=\"table-saved-views__dialog-actions\">\n <ds-button-filled\n label={this.dialogMode === 'rename' ? 'Rename' : 'Save'}\n size=\"md\"\n onDsClick={() => this.submitDialog()}\n />\n <ds-button-unfilled\n label=\"Cancel\"\n size=\"md\"\n onDsClick={() => (this.dialogOpen = false)}\n />\n </div>\n </ds-modal>\n </Host>\n );\n }\n}\n",
380
380
  "type": "registry:ui"
381
381
  },
382
382
  {
@@ -0,0 +1,229 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "table-sort",
4
+ "title": "TableSort",
5
+ "description": "Toolbar companion for the table's one controlled member-row sort, with a two-section menu for the sort field and direction that mirrors header sorting.",
6
+ "type": "registry:ui",
7
+ "meta": {
8
+ "library": "@ds-mo/ui",
9
+ "distribution": "npm",
10
+ "storybook": "https://zainadeel.github.io/compomo/?path=/story/table-sort",
11
+ "source": "src/wc/components/TableSort/TableSort.tsx",
12
+ "intentStatus": "complete",
13
+ "intent": {
14
+ "audience": "general",
15
+ "status": "stable",
16
+ "summary": "Toolbar companion for the table's one controlled member-row sort, with a two-section menu for the sort field and direction that mirrors header sorting.",
17
+ "useWhen": [
18
+ "A table toolbar needs an explicit Sort control alongside Filter and Group.",
19
+ "Sortable columns or compound header segments should be choosable without relying only on column-header clicks."
20
+ ],
21
+ "avoidWhen": [
22
+ "The table has no sortable columns; the control hides itself.",
23
+ "The application expects the control to sort rows internally or persist preferences.",
24
+ "The popup should submit a form value; use Select."
25
+ ],
26
+ "alternatives": [
27
+ {
28
+ "when": "The choice is one submitted form value.",
29
+ "component": "component:ds-select",
30
+ "reason": "Select owns form-value and listbox semantics, not table sort state."
31
+ }
32
+ ],
33
+ "commonlyComposedWith": [
34
+ "component:ds-table",
35
+ "component:ds-table-toolbar",
36
+ "component:ds-filter-menu",
37
+ "component:ds-select",
38
+ "component:ds-button-unfilled",
39
+ "component:ds-menu"
40
+ ],
41
+ "patterns": [
42
+ "pattern:menu-trigger"
43
+ ],
44
+ "accessibility": [
45
+ "The trigger is an icon-label menu button named Sort table unless a product aria-label is supplied. Compact caption widths keep that accessible name after the visible label is omitted.",
46
+ "The menu has two sections, Data and Direction. Each section presents one selected row through isSelected; the menu is not a single radio group because field and direction are independent.",
47
+ "Choosing a row applies immediately and keeps the menu open so the other section can be changed. Escape and outside activation close the menu and restore trigger focus."
48
+ ],
49
+ "states": [
50
+ "columns and sort are controlled. dsSortChange reports the next TableSortState and never mutates those inputs. The same sort object is what column headers emit through the table.",
51
+ "Selecting a different data field keeps the current direction, or starts ascending when no sort exists. Selecting a direction applies it to the current field, or to the first sortable field when no sort exists. Selecting the already-active field or direction is a no-op and does not toggle the way a header click does.",
52
+ "The trigger stays Sort in resting secondary chrome. Field and direction selection live in the menu and on column headers, so the trigger never uses active primary emphasis.",
53
+ "Compound sortable headers contribute one Data row per segment. Action columns never appear."
54
+ ],
55
+ "responsiveBehavior": [
56
+ "Place the control in the trailing region of ds-table-toolbar with Filter and Group.",
57
+ "Inside a named ds-table container narrower than 900px, the trigger omits its label and chevron and becomes icon-only. The icon keeps the resting secondary foreground."
58
+ ],
59
+ "frameworkCaveats": {
60
+ "customElements": [
61
+ "Assign columns and sort as JavaScript properties. Replace sort from both dsSortChange on this control and dsSortChange on the table."
62
+ ],
63
+ "react": [
64
+ "Keep one sort state and pass it to both ds-table and ds-table-sort; update it from either event."
65
+ ],
66
+ "angular": [
67
+ "Import the generated DsTableSort adapter, bind columns and sort, and handle dsSortChange with the same owner that handles table header sorting."
68
+ ]
69
+ },
70
+ "references": [
71
+ {
72
+ "label": "Storybook review states",
73
+ "path": "src/wc/components/TableSort/TableSort.stories.ts"
74
+ },
75
+ {
76
+ "label": "Toolbar composition owner",
77
+ "path": "src/wc/components/TableToolbar/TableToolbar.agent.json"
78
+ }
79
+ ]
80
+ },
81
+ "api": {
82
+ "props": {
83
+ "ariaLabel": {
84
+ "type": "string | null",
85
+ "resolvedType": "null | string",
86
+ "attribute": "aria-label",
87
+ "default": "null",
88
+ "required": false,
89
+ "mutable": false,
90
+ "description": "Accessible name for the trigger and menu."
91
+ },
92
+ "columns": {
93
+ "type": "TableColumn[]",
94
+ "resolvedType": "TableColumn[]",
95
+ "default": "[]",
96
+ "required": false,
97
+ "mutable": false,
98
+ "description": "Catalog used to derive sortable fields, including compound header segments."
99
+ },
100
+ "sort": {
101
+ "type": "TableSortState | null",
102
+ "resolvedType": "TableSortState | null",
103
+ "default": "null",
104
+ "required": false,
105
+ "mutable": false,
106
+ "description": "Controlled table sort. Header sorting and this menu share the same value."
107
+ }
108
+ },
109
+ "events": [
110
+ {
111
+ "name": "dsSortChange",
112
+ "detail": "TableSortChangeDetail",
113
+ "bubbles": false,
114
+ "cancelable": true,
115
+ "composed": true
116
+ }
117
+ ],
118
+ "methods": [
119
+ {
120
+ "name": "setFocus",
121
+ "signature": "setFocus() => Promise<void>"
122
+ }
123
+ ],
124
+ "slots": []
125
+ },
126
+ "props": {
127
+ "ariaLabel": {
128
+ "type": "string | null",
129
+ "resolvedType": "null | string",
130
+ "attribute": "aria-label",
131
+ "default": "null",
132
+ "required": false,
133
+ "mutable": false,
134
+ "description": "Accessible name for the trigger and menu."
135
+ },
136
+ "columns": {
137
+ "type": "TableColumn[]",
138
+ "resolvedType": "TableColumn[]",
139
+ "default": "[]",
140
+ "required": false,
141
+ "mutable": false,
142
+ "description": "Catalog used to derive sortable fields, including compound header segments."
143
+ },
144
+ "sort": {
145
+ "type": "TableSortState | null",
146
+ "resolvedType": "TableSortState | null",
147
+ "default": "null",
148
+ "required": false,
149
+ "mutable": false,
150
+ "description": "Controlled table sort. Header sorting and this menu share the same value."
151
+ }
152
+ },
153
+ "events": [
154
+ {
155
+ "name": "dsSortChange",
156
+ "detail": "TableSortChangeDetail",
157
+ "bubbles": false,
158
+ "cancelable": true,
159
+ "composed": true
160
+ }
161
+ ],
162
+ "methods": [
163
+ {
164
+ "name": "setFocus",
165
+ "signature": "setFocus() => Promise<void>"
166
+ }
167
+ ],
168
+ "slots": [],
169
+ "exports": {
170
+ "customElement": "ds-table-sort",
171
+ "react": "DsTableSort",
172
+ "vue": "DsTableSort",
173
+ "angular": "DsTableSort"
174
+ },
175
+ "consumption": {
176
+ "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
177
+ "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
178
+ "customElements": {
179
+ "import": "import '@ds-mo/ui/dist/components/ds-table-sort.js';",
180
+ "example": "<ds-table-sort></ds-table-sort>"
181
+ },
182
+ "react": {
183
+ "import": "import { DsTableSort } from '@ds-mo/ui/react';",
184
+ "example": "<DsTableSort />"
185
+ },
186
+ "vue": {
187
+ "import": "import { DsTableSort } from '@ds-mo/ui/vue';",
188
+ "example": "<DsTableSort />"
189
+ },
190
+ "angular": {
191
+ "import": "import { DsTableSort } from '@ds-mo/ui/angular/ds-table-sort';",
192
+ "example": "<ds-table-sort></ds-table-sort>"
193
+ },
194
+ "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: columns, sort.",
195
+ "peerDependencies": {
196
+ "required": [
197
+ "@ds-mo/tokens ^6.5.0",
198
+ "@ds-mo/icons ^7.0.0"
199
+ ],
200
+ "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
201
+ }
202
+ }
203
+ },
204
+ "dependencies": [
205
+ "@ds-mo/ui",
206
+ "@ds-mo/tokens"
207
+ ],
208
+ "registryDependencies": [
209
+ "button-unfilled",
210
+ "menu"
211
+ ],
212
+ "files": [
213
+ {
214
+ "path": "src/wc/components/TableSort/TableSort.css",
215
+ "content": ":host {\n display: inline-flex;\n min-inline-size: 0;\n max-inline-size: var(--dimension-menu-width-xs);\n}\n\n:host([hidden]) {\n display: none;\n}\n",
216
+ "type": "registry:ui"
217
+ },
218
+ {
219
+ "path": "src/wc/components/TableSort/TableSort.tsx",
220
+ "content": "import { Component, Element, Event, EventEmitter, h, Host, Method, Prop, State } from '@stencil/core';\nimport type { MenuItemData } from '../Menu/menu-types';\nimport type { TableColumn, TableSortChangeDetail, TableSortState } from '../Table/table-types';\nimport {\n nextTableSortStateFromMenuItem,\n tableSortFields,\n tableSortMenuSections,\n tableSortStatesEqual,\n} from './table-sort-menu';\n\nlet tableSortSeq = 0;\n\n@Component({\n tag: 'ds-table-sort',\n styleUrl: 'TableSort.css',\n scoped: true,\n})\nexport class TableSort {\n @Element() private el!: HTMLElement;\n\n /** Catalog used to derive sortable fields, including compound header segments. */\n @Prop() columns: TableColumn[] = [];\n /** Controlled table sort. Header sorting and this menu share the same value. */\n @Prop() sort: TableSortState | null = null;\n /** Accessible name for the trigger and menu. */\n @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n\n @Event({ bubbles: false }) dsSortChange!: EventEmitter<TableSortChangeDetail>;\n\n @State() private menuOpen = false;\n @State() private initialFocusVisible = false;\n\n private readonly componentId = `ds-table-sort-${++tableSortSeq}`;\n private readonly triggerId = `${this.componentId}-trigger`;\n private readonly menuId = `${this.componentId}-menu`;\n\n @Method()\n async setFocus() {\n const trigger = this.el.querySelector<HTMLElement & { setFocus?: () => Promise<void> }>(\n `#${CSS.escape(this.triggerId)}`\n );\n await trigger?.setFocus?.();\n }\n\n render() {\n const fields = tableSortFields(this.columns);\n const name = this.ariaLabel?.trim() || 'Sort table';\n\n return (\n <Host hidden={fields.length === 0 ? true : undefined}>\n {fields.length === 0 ? null : (\n <ds-button-unfilled\n id={this.triggerId}\n variant=\"icon-label\"\n size=\"md\"\n icon=\"ArrowsVertical\"\n label=\"Sort\"\n aria-label={name}\n hasMenu={true}\n collapseLabel={true}\n expanded={this.menuOpen}\n controls={this.menuId}\n onDsClick={(event: CustomEvent<MouseEvent>) => {\n this.toggle(event.detail.detail === 0);\n }}\n />\n )}\n {fields.length === 0 ? null : (\n <ds-menu\n id={this.menuId}\n open={this.menuOpen}\n anchorId={this.triggerId}\n align=\"end\"\n side=\"bottom\"\n menuLabel={name}\n selectionMode=\"none\"\n initialFocusVisible={this.initialFocusVisible}\n sections={tableSortMenuSections(this.columns, this.sort)}\n onDsClose={() => this.close()}\n onDsSelect={event => this.handleSelect(event.detail)}\n />\n )}\n </Host>\n );\n }\n\n private toggle(fromKeyboard = false): void {\n if (this.menuOpen) this.close();\n else this.open(fromKeyboard);\n }\n\n private open(fromKeyboard = false): void {\n if (this.menuOpen) return;\n this.initialFocusVisible = fromKeyboard;\n this.menuOpen = true;\n }\n\n private close(): void {\n this.menuOpen = false;\n }\n\n private handleSelect(item: MenuItemData): void {\n const next = nextTableSortStateFromMenuItem(this.columns, this.sort, item);\n if (tableSortStatesEqual(this.sort, next)) return;\n this.dsSortChange.emit({ sort: next });\n }\n}\n",
221
+ "type": "registry:ui"
222
+ },
223
+ {
224
+ "path": "src/wc/components/TableSort/table-sort-menu.ts",
225
+ "content": "import type { MenuItemData, MenuSection } from '../Menu/menu-types';\nimport { isTableActionColumn } from '../Table/table-column-customizer';\nimport type { TableColumn, TableSortState } from '../Table/table-types';\n\nexport const TABLE_SORT_DIRECTION_ASC = 'direction:asc';\nexport const TABLE_SORT_DIRECTION_DESC = 'direction:desc';\n\nexport interface TableSortField {\n id: string;\n label: string;\n}\n\n/** Sortable data fields from the catalog, including compound header segments. */\nexport function tableSortFields(columns: readonly TableColumn[]): TableSortField[] {\n const fields: TableSortField[] = [];\n const seen = new Set<string>();\n\n for (const column of columns) {\n if (isTableActionColumn(column) || !column.sortable) continue;\n const segments = column.headerSegments?.filter(segment => segment.sortKey.trim()) ?? [];\n if (segments.length > 0) {\n for (const segment of segments) {\n if (seen.has(segment.sortKey)) continue;\n seen.add(segment.sortKey);\n fields.push({\n id: segment.sortKey,\n label: segment.label.trim() || segment.sortKey,\n });\n }\n continue;\n }\n if (seen.has(column.id)) continue;\n seen.add(column.id);\n fields.push({\n id: column.id,\n label: column.header.trim() || column.headerLabel?.trim() || column.id,\n });\n }\n\n return fields;\n}\n\nexport function tableSortMenuSections(\n columns: readonly TableColumn[],\n sort: TableSortState | null | undefined\n): MenuSection[] {\n const fields = tableSortFields(columns);\n return [\n {\n header: 'Data',\n items: fields.map(field => ({\n label: field.label,\n value: field.id,\n isSelected: sort?.columnId === field.id,\n })),\n },\n {\n header: 'Direction',\n items: [\n {\n label: 'Ascending',\n value: TABLE_SORT_DIRECTION_ASC,\n icon: 'ArrowUp',\n isSelected: sort?.direction === 'asc',\n },\n {\n label: 'Descending',\n value: TABLE_SORT_DIRECTION_DESC,\n icon: 'ArrowDown',\n isSelected: sort?.direction === 'desc',\n },\n ],\n },\n ];\n}\n\nexport function tableSortStatesEqual(\n left: TableSortState | null | undefined,\n right: TableSortState | null | undefined\n): boolean {\n if (left === right) return true;\n if (!left || !right) return false;\n return left.columnId === right.columnId && left.direction === right.direction;\n}\n\n/** Next controlled sort from a Sort menu selection. Does not toggle like a header click. */\nexport function nextTableSortStateFromMenuItem(\n columns: readonly TableColumn[],\n current: TableSortState | null | undefined,\n item: MenuItemData\n): TableSortState | null {\n const fields = tableSortFields(columns);\n if (fields.length === 0) return current ?? null;\n\n if (item.value === TABLE_SORT_DIRECTION_ASC || item.value === TABLE_SORT_DIRECTION_DESC) {\n const direction = item.value === TABLE_SORT_DIRECTION_ASC ? 'asc' : 'desc';\n const columnId = current?.columnId ?? fields[0]!.id;\n return { columnId, direction };\n }\n\n if (!item.value || !fields.some(field => field.id === item.value)) return current ?? null;\n if (current?.columnId === item.value) return current ?? null;\n return { columnId: item.value, direction: current?.direction ?? 'asc' };\n}\n",
226
+ "type": "registry:ui"
227
+ }
228
+ ]
229
+ }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "table-toolbar",
4
4
  "title": "TableToolbar",
5
- "description": "Data-agnostic companion layout for application-owned controls in a table header, with leading and trailing control groups and narrow-width horizontal overflow.",
5
+ "description": "Data-agnostic companion layout for application-owned table caption controls, with a start cluster, optional search, trailing Filter/Group/Sort, and narrow-width horizontal overflow.",
6
6
  "type": "registry:ui",
7
7
  "meta": {
8
8
  "library": "@ds-mo/ui",
@@ -13,9 +13,9 @@
13
13
  "intent": {
14
14
  "audience": "general",
15
15
  "status": "stable",
16
- "summary": "Data-agnostic companion layout for application-owned controls in a table header, with leading and trailing control groups and narrow-width horizontal overflow.",
16
+ "summary": "Data-agnostic companion layout for application-owned table caption controls, with a start cluster, optional search, trailing Filter/Group/Sort, and narrow-width horizontal overflow.",
17
17
  "useWhen": [
18
- "Several table pages repeat the same leading discovery controls and trailing result-shaping controls.",
18
+ "Several table pages repeat the same saved-view cluster, middle discovery and result-shaping controls, and table-owned caption actions.",
19
19
  "A ds-table header slot needs consistent control spacing, alignment, grouping, and narrow-width overflow without moving product behavior into the table."
20
20
  ],
21
21
  "avoidWhen": [
@@ -26,34 +26,39 @@
26
26
  "commonlyComposedWith": [
27
27
  "component:ds-table",
28
28
  "component:ds-table-saved-views",
29
+ "component:ds-divider",
29
30
  "component:ds-input",
30
31
  "component:ds-filter-menu",
32
+ "component:ds-select",
33
+ "component:ds-table-sort",
31
34
  "component:ds-button-unfilled"
32
35
  ],
33
36
  "patterns": [],
34
37
  "accessibility": [
35
38
  "The component exposes a named toolbar while each slotted control retains its own accessible name and native keyboard behavior.",
36
- "Use the leading slot for discovery and context controls such as saved views and search. Use the trailing slot for result-shaping controls such as filters and grouping.",
39
+ "Use the start slot for saved views. Use search for the optional discovery field, leading for any additional left-side middle controls, and trailing for Filter, Group, and Sort. Table-owned column customizer and data-mode actions remain in the table caption trailing cluster.",
37
40
  "Tab order follows authored DOM order. The toolbar does not add roving focus because it may contain heterogeneous form and popup controls."
38
41
  ],
39
42
  "states": [
40
43
  "The component owns layout only. Slotted controls, values, menus, loading, persistence, and consequences remain controlled by the application. Use ds-table-saved-views when saved-view interaction needs the shared CompoMo behavior while application storage remains external.",
41
- "Either region may be empty. The populated region continues to occupy the available width without adding placeholder chrome."
44
+ "The start cluster is followed by a decorative vertical divider, then the spanning middle region. The start cluster and divider hide when nothing is projected. Search is a dedicated slot that hides when nothing is projected. Empty leading or trailing clusters add no placeholder chrome. The middle region occupies remaining width so trailing controls stay at its inline end.",
45
+ "Column customizer and data-mode switching stay table-owned caption actions. Do not recreate those menus in the toolbar."
42
46
  ],
43
47
  "responsiveBehavior": [
44
- "The two control groups stay on one line and horizontally scroll when their complete controls no longer fit.",
48
+ "The start cluster, search, trailing Filter/Group/Sort, and table caption trailing actions stay on one line. Saved views, Filter, Group, and Sort triggers cap at the compact menu-width token and truncate their labels, keeping a saved-view dirty dot visible. Group and Sort popups keep that same compact menu-width floor. The toolbar horizontally scrolls when its complete controls no longer fit.",
49
+ "Inside a named ds-table container narrower than 900px, Filter, Group, and Sort omit their labels and chevrons and become icon-only. Active filtering or grouping promotes the prefix icon to primary. Sort keeps resting secondary chrome. Table-owned Customize also becomes the icon-only Table menu button at that width.",
45
50
  "The scrollbar is visually suppressed without removing touch, trackpad, mouse-wheel, or keyboard scrolling from the overflow container.",
46
51
  "Trailing controls remain grouped after leading controls; the toolbar does not collapse controls into an application-specific overflow menu."
47
52
  ],
48
53
  "frameworkCaveats": {
49
54
  "customElements": [
50
- "Project controls through slot=leading or slot=trailing and provide a label that names their table context when Table controls is insufficient."
55
+ "Project saved views through slot=start, search through slot=search, additional left-side middle controls through slot=leading, and Filter, Group, and Sort through slot=trailing. Provide a label that names their table context when Table controls is insufficient."
51
56
  ],
52
57
  "react": [
53
- "Set slot=leading or slot=trailing on each child control; keep values and event handlers in the application."
58
+ "Set slot=start, slot=search, slot=leading, or slot=trailing on each child control; keep values and event handlers in the application."
54
59
  ],
55
60
  "angular": [
56
- "Set slot=leading or slot=trailing on each child control and import the generated DsTableToolbar adapter alongside those controls."
61
+ "Set slot=start, slot=search, slot=leading, or slot=trailing on each child control and import the generated DsTableToolbar adapter alongside those controls."
57
62
  ]
58
63
  },
59
64
  "references": [
@@ -135,16 +140,18 @@
135
140
  "@ds-mo/ui",
136
141
  "@ds-mo/tokens"
137
142
  ],
138
- "registryDependencies": [],
143
+ "registryDependencies": [
144
+ "divider"
145
+ ],
139
146
  "files": [
140
147
  {
141
148
  "path": "src/wc/components/TableToolbar/TableToolbar.css",
142
- "content": ":host {\n display: block;\n min-inline-size: 0;\n max-inline-size: 100%;\n}\n\n.table-toolbar {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n inline-size: 100%;\n min-inline-size: 0;\n gap: var(--dimension-space-100);\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n}\n\n.table-toolbar::-webkit-scrollbar {\n display: none;\n}\n\n.table-toolbar__leading,\n.table-toolbar__trailing {\n display: flex;\n align-items: center;\n min-inline-size: 0;\n gap: var(--dimension-space-100);\n}\n\n.table-toolbar__leading {\n flex: 1 0 auto;\n}\n\n.table-toolbar__trailing {\n flex: 0 0 auto;\n margin-inline-start: auto;\n}\n\n.table-toolbar__leading slot,\n.table-toolbar__trailing slot {\n display: contents;\n}\n",
149
+ "content": ":host {\n display: block;\n min-inline-size: 0;\n max-inline-size: 100%;\n}\n\n.table-toolbar {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n inline-size: 100%;\n min-inline-size: 0;\n gap: var(--dimension-space-100);\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n}\n\n.table-toolbar::-webkit-scrollbar {\n display: none;\n}\n\n.table-toolbar__start,\n.table-toolbar__search,\n.table-toolbar__main,\n.table-toolbar__leading,\n.table-toolbar__trailing {\n display: flex;\n align-items: center;\n min-inline-size: 0;\n gap: var(--dimension-space-100);\n}\n\n.table-toolbar__start {\n flex: 0 1 var(--dimension-menu-width-xs);\n min-inline-size: 0;\n max-inline-size: var(--dimension-menu-width-xs);\n overflow: hidden;\n}\n\n.table-toolbar__rule {\n flex: 0 0 auto;\n}\n\n.table-toolbar__main {\n flex: 1 1 auto;\n min-inline-size: min-content;\n}\n\n.table-toolbar__search {\n flex: 0 1 var(--dimension-panel-width-xs);\n max-inline-size: var(--dimension-panel-width-xs);\n}\n\n:host(:not(:has([slot='search']))) .table-toolbar__search {\n display: none;\n}\n\n:host(:not(:has([slot='start']))) .table-toolbar__start,\n:host(:not(:has([slot='start']))) .table-toolbar__rule {\n display: none;\n}\n\n.table-toolbar__leading {\n flex: 1 0 auto;\n}\n\n.table-toolbar__trailing {\n flex: 0 0 auto;\n margin-inline-start: auto;\n}\n\n.table-toolbar__trailing > *,\n.table-toolbar [slot='start'],\n.table-toolbar ds-filter-menu,\n.table-toolbar ds-select,\n.table-toolbar ds-table-sort {\n min-inline-size: 0;\n max-inline-size: var(--dimension-menu-width-xs);\n --ds-choice-popup-min-inline-size: var(--dimension-menu-width-xs);\n}\n\n.table-toolbar__start slot,\n.table-toolbar__search slot,\n.table-toolbar__leading slot,\n.table-toolbar__trailing slot {\n display: contents;\n}\n",
143
150
  "type": "registry:ui"
144
151
  },
145
152
  {
146
153
  "path": "src/wc/components/TableToolbar/TableToolbar.tsx",
147
- "content": "import { Component, h, Host, Prop } from '@stencil/core';\n\n@Component({\n tag: 'ds-table-toolbar',\n styleUrl: 'TableToolbar.css',\n scoped: true,\n})\nexport class TableToolbar {\n /** Accessible name for the grouped table controls. */\n @Prop() label: string = 'Table controls';\n\n render() {\n return (\n <Host>\n <div class=\"table-toolbar\" role=\"toolbar\" aria-label={this.label}>\n <div class=\"table-toolbar__leading\">\n <slot name=\"leading\" />\n </div>\n <div class=\"table-toolbar__trailing\">\n <slot name=\"trailing\" />\n </div>\n </div>\n </Host>\n );\n }\n}\n",
154
+ "content": "import { Component, Element, h, Host, Prop } from '@stencil/core';\n\nconst CAPTION_CONTROL_SELECTOR = 'ds-filter-menu, ds-select';\n\n@Component({\n tag: 'ds-table-toolbar',\n styleUrl: 'TableToolbar.css',\n scoped: true,\n})\nexport class TableToolbar {\n @Element() private el!: HTMLElement;\n\n /** Accessible name for the grouped table controls. */\n @Prop() label: string = 'Table controls';\n\n private captionControlObserver: MutationObserver | undefined;\n\n componentDidLoad(): void {\n this.connectCaptionControlObserver();\n }\n\n disconnectedCallback(): void {\n this.captionControlObserver?.disconnect();\n }\n\n render() {\n return (\n <Host>\n <div class=\"table-toolbar\" role=\"toolbar\" aria-label={this.label}>\n <div class=\"table-toolbar__start\">\n <slot name=\"start\" />\n </div>\n <ds-divider class=\"table-toolbar__rule\" orientation=\"vertical\" length=\"32px\" />\n <div class=\"table-toolbar__main\">\n <div class=\"table-toolbar__search\">\n <slot name=\"search\" />\n </div>\n <div class=\"table-toolbar__leading\">\n <slot name=\"leading\" />\n </div>\n <div class=\"table-toolbar__trailing\">\n <slot name=\"trailing\" />\n </div>\n </div>\n </div>\n </Host>\n );\n }\n\n private connectCaptionControlObserver(): void {\n if (this.captionControlObserver || typeof MutationObserver === 'undefined') return;\n this.captionControlObserver = new MutationObserver(this.syncCaptionControls);\n this.captionControlObserver.observe(this.el, { childList: true, subtree: true });\n this.syncCaptionControls();\n }\n\n private syncCaptionControls = () => {\n for (const control of this.el.querySelectorAll<\n HTMLElement & { collapseLabel?: boolean }\n >(CAPTION_CONTROL_SELECTOR)) {\n if (control.closest('ds-table-saved-views')) continue;\n if (control.collapseLabel) continue;\n control.collapseLabel = true;\n }\n };\n}\n",
148
155
  "type": "registry:ui"
149
156
  }
150
157
  ]