@ds-mo/ui 8.3.1 → 9.1.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 (184) hide show
  1. package/README.md +28 -3
  2. package/dist/.build-stamp +1 -1
  3. package/dist/agent-patterns.json +233 -0
  4. package/dist/agent.json +155 -174
  5. package/dist/angular/ds-menu.d.ts +6 -1
  6. package/dist/angular/ds-menu.js +7 -4
  7. package/dist/angular/ds-swatch-picker.d.ts +15 -0
  8. package/dist/angular/{ds-pagination.js → ds-swatch-picker.js} +15 -15
  9. package/dist/angular/index.d.ts +1 -1
  10. package/dist/angular/index.js +1 -2
  11. package/dist/angular/proxies.d.ts +1 -2
  12. package/dist/angular/proxies.js +1 -2
  13. package/dist/components/ds-app-shell.js +1 -1
  14. package/dist/components/ds-badge.js +1 -1
  15. package/dist/components/ds-banner.js +1 -1
  16. package/dist/components/ds-bar-nav.js +1 -1
  17. package/dist/components/ds-bar-nav.js.map +1 -1
  18. package/dist/components/ds-button-filled.js +1 -1
  19. package/dist/components/ds-button-unfilled.js +1 -1
  20. package/dist/components/ds-card-data-viz-donut.js +1 -1
  21. package/dist/components/ds-card-setting.js +1 -1
  22. package/dist/components/ds-card-shell-data-viz.js +1 -1
  23. package/dist/components/ds-card.js +1 -1
  24. package/dist/components/ds-chart-bar.js +1 -1
  25. package/dist/components/ds-chart-bar.js.map +1 -1
  26. package/dist/components/ds-chart-donut.js +1 -1
  27. package/dist/components/ds-chart-legend.js +1 -1
  28. package/dist/components/ds-chart-line.js +1 -1
  29. package/dist/components/ds-checkbox.js +1 -1
  30. package/dist/components/ds-chip.js +1 -1
  31. package/dist/components/ds-empty-state.js +1 -1
  32. package/dist/components/ds-field.js +1 -1
  33. package/dist/components/ds-input.js +1 -1
  34. package/dist/components/ds-menu.js +1 -1
  35. package/dist/components/ds-modal.js +1 -1
  36. package/dist/components/ds-panel-nav.js +1 -1
  37. package/dist/components/ds-panel-nav.js.map +1 -1
  38. package/dist/components/ds-panel-sub-nav.js +1 -1
  39. package/dist/components/ds-panel-tools.js +1 -1
  40. package/dist/components/ds-radio.js +1 -1
  41. package/dist/components/ds-select-multi.js +1 -1
  42. package/dist/components/ds-select-multi.js.map +1 -1
  43. package/dist/components/ds-select.js +1 -1
  44. package/dist/components/ds-select.js.map +1 -1
  45. package/dist/components/ds-shell-gradient-picker.js +1 -1
  46. package/dist/components/ds-shell-gradient-picker.js.map +1 -1
  47. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  48. package/dist/components/ds-shell-gradient-swatch.js.map +1 -1
  49. package/dist/components/ds-skeleton.js +1 -1
  50. package/dist/components/ds-skeleton.js.map +1 -1
  51. package/dist/components/ds-slider.js +1 -1
  52. package/dist/components/ds-slider.js.map +1 -1
  53. package/dist/components/{ds-pagination.d.ts → ds-swatch-picker.d.ts} +4 -4
  54. package/dist/components/ds-swatch-picker.js +2 -0
  55. package/dist/components/ds-swatch-picker.js.map +1 -0
  56. package/dist/components/ds-tab-group.js +1 -1
  57. package/dist/components/ds-tag.js +1 -1
  58. package/dist/components/ds-text.js +1 -1
  59. package/dist/components/ds-tooltip-data-viz.js +1 -1
  60. package/dist/components/ds-tooltip.js +1 -1
  61. package/dist/components/{p-CysJosvS.js → p-B-zhgAvL.js} +2 -2
  62. package/dist/components/{p-C21sIElQ.js → p-BLzivrE3.js} +2 -2
  63. package/dist/components/{p-DAuadHYu.js → p-BbV68F23.js} +2 -2
  64. package/dist/components/{p-_y99wWNU.js → p-C7RObioO.js} +2 -2
  65. package/dist/components/{p-DxQ4gzbA.js → p-CZ9wemOA.js} +2 -2
  66. package/dist/components/{p-BKA6houZ.js → p-CbS_WWs2.js} +2 -2
  67. package/dist/components/p-D1HFMTdC.js +2 -0
  68. package/dist/components/p-D1HFMTdC.js.map +1 -0
  69. package/dist/components/{p-BGcSrHKl.js → p-DT2MK8Kt.js} +2 -2
  70. package/dist/components/{p-DV23KMU-.js → p-DyZj3k9S.js} +2 -2
  71. package/dist/components/p-HBIxvS5C.js +2 -0
  72. package/dist/components/p-HBIxvS5C.js.map +1 -0
  73. package/dist/components/{p-D6hvtWDk.js → p-KRGBGTu5.js} +2 -2
  74. package/dist/components/p-l6PmnW_P.js +2 -0
  75. package/dist/components/p-l6PmnW_P.js.map +1 -0
  76. package/dist/components/p-tQ1QHeOE.js +2 -0
  77. package/dist/components/p-tQ1QHeOE.js.map +1 -0
  78. package/dist/docs/components.json +546 -1024
  79. package/dist/lib/components/Menu/menu-placement.d.ts +6 -3
  80. package/dist/lib/shell/index.d.ts +2 -2
  81. package/dist/lib/shell/index.js +40 -39
  82. package/dist/lib/shell/index.js.map +3 -3
  83. package/dist/lib/shell/shell-gradient-presets.d.ts +15 -0
  84. package/dist/lib/utils/choice-popup-alignment.d.ts +20 -0
  85. package/dist/lib/utils/index.d.ts +2 -0
  86. package/dist/lib/utils/index.js +16 -0
  87. package/dist/lib/utils/index.js.map +3 -3
  88. package/dist/mcp/cli.js +31409 -0
  89. package/dist/mcp-data/registry/app-shell.json +210 -0
  90. package/dist/mcp-data/registry/badge.json +289 -0
  91. package/dist/mcp-data/registry/banner.json +206 -0
  92. package/dist/mcp-data/registry/bar-nav.json +341 -0
  93. package/dist/mcp-data/registry/button-filled.json +386 -0
  94. package/dist/mcp-data/registry/button-unfilled.json +537 -0
  95. package/dist/mcp-data/registry/card-data-viz-donut.json +143 -0
  96. package/dist/mcp-data/registry/card-setting.json +194 -0
  97. package/dist/mcp-data/registry/card-shell-data-viz.json +173 -0
  98. package/dist/mcp-data/registry/card.json +126 -0
  99. package/dist/mcp-data/registry/chart-bar.json +121 -0
  100. package/dist/mcp-data/registry/chart-donut.json +257 -0
  101. package/dist/mcp-data/registry/chart-legend.json +209 -0
  102. package/dist/mcp-data/registry/chart-line.json +153 -0
  103. package/dist/mcp-data/registry/checkbox.json +361 -0
  104. package/dist/mcp-data/registry/chip.json +276 -0
  105. package/dist/mcp-data/registry/divider.json +225 -0
  106. package/dist/mcp-data/registry/empty-state.json +197 -0
  107. package/dist/mcp-data/registry/field.json +222 -0
  108. package/dist/mcp-data/registry/icon.json +242 -0
  109. package/dist/mcp-data/registry/input.json +597 -0
  110. package/dist/mcp-data/registry/loader.json +192 -0
  111. package/dist/mcp-data/registry/menu.json +459 -0
  112. package/dist/mcp-data/registry/modal.json +150 -0
  113. package/dist/mcp-data/registry/panel-nav.json +622 -0
  114. package/dist/mcp-data/registry/panel-sub-nav.json +249 -0
  115. package/dist/mcp-data/registry/panel-tools.json +374 -0
  116. package/dist/mcp-data/registry/radio.json +374 -0
  117. package/dist/mcp-data/registry/registry.json +5389 -0
  118. package/dist/mcp-data/registry/select-multi.json +755 -0
  119. package/dist/mcp-data/registry/select.json +740 -0
  120. package/dist/mcp-data/registry/shell-gradient-picker.json +195 -0
  121. package/dist/mcp-data/registry/shell-gradient-swatch.json +215 -0
  122. package/dist/mcp-data/registry/skeleton.json +282 -0
  123. package/dist/mcp-data/registry/slider.json +657 -0
  124. package/dist/mcp-data/registry/swatch-picker.json +231 -0
  125. package/dist/mcp-data/registry/switch.json +361 -0
  126. package/dist/mcp-data/registry/tab-group.json +255 -0
  127. package/dist/mcp-data/registry/tag.json +352 -0
  128. package/dist/mcp-data/registry/text.json +371 -0
  129. package/dist/mcp-data/registry/tooltip-data-viz.json +158 -0
  130. package/dist/mcp-data/registry/tooltip.json +311 -0
  131. package/dist/react/components.d.ts +1 -2
  132. package/dist/react/components.js +1 -2
  133. package/dist/react/ds-menu.d.ts +1 -0
  134. package/dist/react/ds-menu.js +2 -1
  135. package/dist/react/ds-swatch-picker.d.ts +12 -0
  136. package/dist/react/ds-swatch-picker.js +12 -0
  137. package/dist/types/components/Menu/Menu.d.ts +7 -1
  138. package/dist/types/components/Menu/menu-placement.d.ts +6 -3
  139. package/dist/types/components/Menu/menu-types.d.ts +14 -1
  140. package/dist/types/components/ShellGradientPicker/ShellGradientPicker.d.ts +2 -5
  141. package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +1 -0
  142. package/dist/types/components/SwatchPicker/SwatchPicker.d.ts +21 -0
  143. package/dist/types/components/SwatchPicker/swatch-picker-types.d.ts +26 -0
  144. package/dist/types/components.d.ts +117 -237
  145. package/dist/types/shell/index.d.ts +2 -2
  146. package/dist/types/shell/shell-gradient-presets.d.ts +15 -0
  147. package/dist/types/utils/choice-popup-alignment.d.ts +21 -0
  148. package/dist/types/utils/index.d.ts +2 -0
  149. package/package.json +6 -2
  150. package/dist/angular/ds-pagination.d.ts +0 -15
  151. package/dist/angular/ds-table.d.ts +0 -33
  152. package/dist/angular/ds-table.js +0 -48
  153. package/dist/components/ds-pagination.js +0 -2
  154. package/dist/components/ds-pagination.js.map +0 -1
  155. package/dist/components/ds-table.d.ts +0 -11
  156. package/dist/components/ds-table.js +0 -2
  157. package/dist/components/ds-table.js.map +0 -1
  158. package/dist/components/p-CKV6rOGa.js +0 -2
  159. package/dist/components/p-CKV6rOGa.js.map +0 -1
  160. package/dist/components/p-CSTr2132.js +0 -2
  161. package/dist/components/p-CSTr2132.js.map +0 -1
  162. package/dist/components/p-C_cu-HIT.js +0 -2
  163. package/dist/components/p-C_cu-HIT.js.map +0 -1
  164. package/dist/components/p-D0B9uDTP.js +0 -2
  165. package/dist/components/p-D0B9uDTP.js.map +0 -1
  166. package/dist/components/p-DhxWfdX0.js +0 -2
  167. package/dist/components/p-DhxWfdX0.js.map +0 -1
  168. package/dist/components/p-Pip-LNhl.js +0 -2
  169. package/dist/components/p-Pip-LNhl.js.map +0 -1
  170. package/dist/react/ds-pagination.d.ts +0 -12
  171. package/dist/react/ds-pagination.js +0 -12
  172. package/dist/react/ds-table.d.ts +0 -21
  173. package/dist/react/ds-table.js +0 -16
  174. package/dist/types/components/Pagination/Pagination.d.ts +0 -14
  175. package/dist/types/components/Table/Table.d.ts +0 -56
  176. /package/dist/components/{p-CysJosvS.js.map → p-B-zhgAvL.js.map} +0 -0
  177. /package/dist/components/{p-C21sIElQ.js.map → p-BLzivrE3.js.map} +0 -0
  178. /package/dist/components/{p-DAuadHYu.js.map → p-BbV68F23.js.map} +0 -0
  179. /package/dist/components/{p-_y99wWNU.js.map → p-C7RObioO.js.map} +0 -0
  180. /package/dist/components/{p-DxQ4gzbA.js.map → p-CZ9wemOA.js.map} +0 -0
  181. /package/dist/components/{p-BKA6houZ.js.map → p-CbS_WWs2.js.map} +0 -0
  182. /package/dist/components/{p-BGcSrHKl.js.map → p-DT2MK8Kt.js.map} +0 -0
  183. /package/dist/components/{p-DV23KMU-.js.map → p-DyZj3k9S.js.map} +0 -0
  184. /package/dist/components/{p-D6hvtWDk.js.map → p-KRGBGTu5.js.map} +0 -0
package/README.md CHANGED
@@ -48,6 +48,34 @@ There is **no** published `@ds-mo/ui/loader` or global `@ds-mo/ui/css` bundle
48
48
 
49
49
  **SPA hosts (Angular / React):** `ds-panel-nav` and `ds-bar-nav` need a [first-paint integration contract](docs/framework-integration.md) on hard reload — seed bar-nav state and stamp `data-nav-style` before custom elements upgrade.
50
50
 
51
+ ## Agent recipes through MCP
52
+
53
+ The package includes a local stdio MCP server that exposes component metadata and
54
+ executable Custom Elements, React, and Angular composition recipes. It runs on the
55
+ developer's machine; no hosted MCP service or API key is required.
56
+
57
+ When `@ds-mo/ui` is installed in the consuming project, point Codex at the package binary:
58
+
59
+ ```toml
60
+ # .codex/config.toml
61
+ [mcp_servers.compomo]
62
+ command = "./node_modules/.bin/compomo-mcp"
63
+ ```
64
+
65
+ Or let npm obtain the latest public package when the MCP client starts:
66
+
67
+ ```toml
68
+ [mcp_servers.compomo]
69
+ command = "npx"
70
+ args = ["-y", "--package", "@ds-mo/ui@latest", "compomo-mcp"]
71
+ ```
72
+
73
+ The server provides `list_components`, `get_component`, `get_setup_guide`,
74
+ `get_component_source`, `list_patterns`, and `get_pattern`. Add a repository
75
+ instruction telling agents to retrieve the applicable pattern before composing
76
+ multiple design-system components; connecting the MCP makes recipes available,
77
+ while the instruction makes their use consistent.
78
+
51
79
  ## Components
52
80
 
53
81
  All tags are `ds-*` custom elements. Grouped by role (see Storybook for props and stories):
@@ -61,9 +89,6 @@ All tags are `ds-*` custom elements. Grouped by role (see Storybook for props an
61
89
  ### Controls
62
90
  - **Toggle**, **Checkbox**, **Radio**, **Input**, **Slider**, **Field**
63
91
 
64
- ### Data display
65
- - **Table**, **Pagination**
66
-
67
92
  ### Overlays
68
93
  - **Modal**, **Menu**, **Tooltip**, **Select**, **Banner**
69
94
 
package/dist/.build-stamp CHANGED
@@ -1 +1 @@
1
- 1784320108642
1
+ 1784331945246
@@ -0,0 +1,233 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "package": "@ds-mo/ui",
4
+ "packageVersion": "9.1.0",
5
+ "kind": "patterns",
6
+ "entries": [
7
+ {
8
+ "id": "pattern:application-shell",
9
+ "status": "experimental",
10
+ "summary": "Required base-page composition for standard authenticated applications, with one AppShell coordinating primary destinations, secondary route sections, tools chrome, responsive modes, and stable main content geometry.",
11
+ "useWhen": [
12
+ "Building any standard authenticated product or workspace page with CompoMo.",
13
+ "Application navigation and tools must remain consistent around changing route content.",
14
+ "Creating a reusable base-page template for CompoMo applications."
15
+ ],
16
+ "avoidWhen": [
17
+ "Building authentication, onboarding, public, marketing, print, error, or intentionally focused full-screen experiences.",
18
+ "The page is already rendered inside an AppShell.",
19
+ "A nested or second application shell is being considered."
20
+ ],
21
+ "components": [
22
+ {
23
+ "component": "component:ds-app-shell",
24
+ "role": "Provides the single workspace root and coordinates shell composition, shared chrome, responsive modes, and stable content geometry.",
25
+ "required": true
26
+ },
27
+ {
28
+ "component": "component:ds-panel-nav",
29
+ "role": "Provides the application's one primary navigation with top-level destinations and standard Dashboard/Settings and account footer controls.",
30
+ "required": true
31
+ },
32
+ {
33
+ "component": "component:ds-bar-nav",
34
+ "role": "Provides secondary route-section navigation or the active primary destination label while preserving shell height.",
35
+ "required": true
36
+ },
37
+ {
38
+ "component": "component:ds-panel-tools",
39
+ "role": "Provides required tools chrome with Search and Help as the minimum standard tools and optional additional product tools.",
40
+ "required": true
41
+ }
42
+ ],
43
+ "stateOwnership": [
44
+ "The application router owns the current route, Dashboard or Settings context, authorization filtering, destination and section collections, localized labels, and navigation execution.",
45
+ "AppShell owns desktop, tablet, and mobile composition, shell geometry, navStyle propagation, gradient alignment, chrome transitions, metrics, and shell shortcuts.",
46
+ "PanelNav owns primary-destination presentation and the user-controlled desktop collapse preference.",
47
+ "BarNav derives the active secondary route from currentUrl and basePath and receives the active PanelNav destination label as heading.",
48
+ "PanelTools owns tool-rail and drawer interaction while the application owns available tool content and active-tool state.",
49
+ "PanelNav groups and BarNav dividers visually organize peers and never introduce nested navigation hierarchy."
50
+ ],
51
+ "accessibility": [
52
+ "Render one main landmark in the default content region.",
53
+ "Use PanelNav exclusively for primary destinations and BarNav exclusively for secondary route sections.",
54
+ "Keep localized visible labels and accessible names available when navigation is collapsed or moved into overflow.",
55
+ "Provide Search and Help as the minimum accessible PanelTools actions.",
56
+ "Preserve keyboard focus and route state across responsive and Dashboard/Settings context changes."
57
+ ],
58
+ "responsiveBehavior": [
59
+ "Use fixed system breakpoints: desktop at 1200px and wider, tablet from 768px through 1199px, and mobile below 768px.",
60
+ "Desktop permits PanelNav expansion and collapse and restores the persisted user preference.",
61
+ "Tablet keeps PanelNav permanently collapsed and does not overwrite the desktop preference.",
62
+ "Mobile keeps AppShell as the root while replacing desktop navigation with a future dedicated mobile navigation design.",
63
+ "Keep BarNav height, PanelTools chrome, and main-content origin stable across route changes.",
64
+ "Treat the complete responsive composition as an intended contract that is not yet fully implemented by the current AppShell runtime."
65
+ ],
66
+ "implementations": {
67
+ "customElements": {
68
+ "status": "verified",
69
+ "references": [
70
+ "src/wc/components/AppShell/AppShell.stories.ts",
71
+ "docs/framework-integration.md"
72
+ ]
73
+ },
74
+ "react": {
75
+ "status": "planned",
76
+ "references": [
77
+ "scripts/verify-package-imports.mjs",
78
+ "docs/framework-integration.md"
79
+ ]
80
+ },
81
+ "angular": {
82
+ "status": "verified",
83
+ "references": [
84
+ "scripts/verify-package-imports.mjs",
85
+ "docs/framework-integration.md"
86
+ ]
87
+ }
88
+ },
89
+ "references": [
90
+ "src/wc/components/AppShell/AppShell.mdx",
91
+ "docs/framework-integration.md"
92
+ ]
93
+ },
94
+ {
95
+ "id": "pattern:menu-trigger",
96
+ "status": "stable",
97
+ "summary": "Connect a ButtonUnfilled trigger to an anchored Menu for a compact command or view-selection popup without application-authored positioning.",
98
+ "useWhen": [
99
+ "A button should reveal a short contextual collection of commands.",
100
+ "A compact button should show the current view or mode and let the user choose another one from a menu."
101
+ ],
102
+ "avoidWhen": [
103
+ "The choice is a submitted form value with a field label or validation; use Select.",
104
+ "The available local views can remain visible; use TabGroup.",
105
+ "The choices are primary or secondary application destinations; use the matching navigation component.",
106
+ "The popup contains substantial content, confirmation, or a form; use Modal."
107
+ ],
108
+ "components": [
109
+ {
110
+ "component": "component:ds-button-unfilled",
111
+ "role": "Provides the visible trigger, popup relationship attributes, and focus target.",
112
+ "required": true
113
+ },
114
+ {
115
+ "component": "component:ds-menu",
116
+ "role": "Owns anchored placement, menu semantics, keyboard navigation, viewport clamping, selection, and close behavior.",
117
+ "required": true
118
+ }
119
+ ],
120
+ "stateOwnership": [
121
+ "The application owns the selected command or view value and derives the trigger label and each item's isSelected state from it.",
122
+ "The application synchronizes one open boolean to ButtonUnfilled.expanded and Menu.open.",
123
+ "Menu owns popup placement and keyboard navigation; the application never calculates popup coordinates or applies positional CSS.",
124
+ "On dsSelect, update the selected value, close the Menu, and restore focus to the trigger.",
125
+ "On dsClose, synchronize the application open state to false."
126
+ ],
127
+ "accessibility": [
128
+ "Set ButtonUnfilled.haspopup to menu, controls to the Menu id, and expanded from the same open state passed to Menu.",
129
+ "Give Menu a concise menuLabel describing the available commands or views.",
130
+ "Set Menu.initialFocusVisible only when the trigger MouseEvent detail is zero, indicating keyboard activation.",
131
+ "Keep visible selected styling synchronized with the application's selected value; Menu exposes the selected command through aria-current.",
132
+ "Return focus to the trigger after selection; Menu already restores it for Escape and outside dismissal."
133
+ ],
134
+ "responsiveBehavior": [
135
+ "Keep Menu's default choice-cell anchor alignment unless deliberate custom frame geometry is required.",
136
+ "Let Menu clamp to the viewport and avoid application-authored fixed or absolute positioning.",
137
+ "Use a concise trigger label that can fit the owning layout without hiding the selected meaning."
138
+ ],
139
+ "implementations": {
140
+ "customElements": {
141
+ "status": "verified",
142
+ "recipes": [
143
+ {
144
+ "id": "selected-view-menu",
145
+ "title": "Selected view menu",
146
+ "summary": "A ButtonUnfilled displays the active view and opens a Menu whose selected row updates that view.",
147
+ "files": [
148
+ {
149
+ "path": "view-menu.html",
150
+ "language": "html",
151
+ "content": "<ds-button-unfilled\n id=\"view-menu-trigger\"\n label=\"View · Overview\"\n haspopup=\"menu\"\n controls=\"view-menu\"\n></ds-button-unfilled>\n<ds-menu\n id=\"view-menu\"\n anchor-id=\"view-menu-trigger\"\n menu-label=\"Choose view\"\n></ds-menu>"
152
+ },
153
+ {
154
+ "path": "view-menu.ts",
155
+ "language": "typescript",
156
+ "content": "import '@ds-mo/ui/components/ds-button-unfilled.js'\nimport '@ds-mo/ui/components/ds-menu.js'\nimport type { MenuItemData } from '@ds-mo/ui'\n\nconst choices: MenuItemData[] = [\n { label: 'Overview', value: 'overview' },\n { label: 'Map', value: 'map' },\n { label: 'Timeline', value: 'timeline' },\n]\n\nconst trigger = document.querySelector<HTMLDsButtonUnfilledElement>('#view-menu-trigger')!\nconst menu = document.querySelector<HTMLDsMenuElement>('#view-menu')!\nlet selectedValue = 'overview'\n\nfunction render() {\n const selected = choices.find(item => item.value === selectedValue) ?? choices[0]\n trigger.label = `View · ${selected.label}`\n menu.items = choices.map(item => ({ ...item, isSelected: item.value === selectedValue }))\n}\n\nfunction setOpen(open: boolean) {\n trigger.expanded = open\n menu.open = open\n}\n\ntrigger.addEventListener('dsClick', event => {\n const mouseEvent = (event as CustomEvent<MouseEvent>).detail\n menu.initialFocusVisible = mouseEvent.detail === 0\n setOpen(!menu.open)\n})\nmenu.addEventListener('dsClose', () => setOpen(false))\nmenu.addEventListener('dsSelect', event => {\n selectedValue = (event as CustomEvent<MenuItemData>).detail.value ?? selectedValue\n render()\n setOpen(false)\n requestAnimationFrame(() => trigger.setFocus())\n})\n\nrender()"
157
+ }
158
+ ],
159
+ "notes": [
160
+ "Assign Menu.items as a JavaScript property; complex arrays cannot be expressed as an HTML attribute.",
161
+ "Do not add positioning styles to either element."
162
+ ]
163
+ }
164
+ ],
165
+ "references": [
166
+ "src/wc/components/Tag/Tag.stories.ts",
167
+ "src/wc/components/Menu/Menu.stories.ts"
168
+ ]
169
+ },
170
+ "react": {
171
+ "status": "verified",
172
+ "recipes": [
173
+ {
174
+ "id": "selected-view-menu",
175
+ "title": "Selected view menu",
176
+ "summary": "A controlled React composition using generated ButtonUnfilled and Menu wrappers.",
177
+ "files": [
178
+ {
179
+ "path": "ViewMenu.tsx",
180
+ "language": "tsx",
181
+ "content": "import { useState } from 'react'\nimport type { HTMLDsButtonUnfilledElement, MenuItemData } from '@ds-mo/ui'\nimport { DsButtonUnfilled, DsMenu } from '@ds-mo/ui/react'\n\nconst choices: MenuItemData[] = [\n { label: 'Overview', value: 'overview' },\n { label: 'Map', value: 'map' },\n { label: 'Timeline', value: 'timeline' },\n]\n\nexport function ViewMenu() {\n const [selectedValue, setSelectedValue] = useState('overview')\n const [open, setOpen] = useState(false)\n const [initialFocusVisible, setInitialFocusVisible] = useState(false)\n const selected = choices.find(item => item.value === selectedValue) ?? choices[0]\n const items = choices.map(item => ({ ...item, isSelected: item.value === selectedValue }))\n\n function focusTrigger() {\n requestAnimationFrame(() => {\n const trigger = document.querySelector<HTMLDsButtonUnfilledElement>('#view-menu-trigger')\n trigger?.setFocus()\n })\n }\n\n return (\n <>\n <DsButtonUnfilled\n id=\"view-menu-trigger\"\n label={`View · ${selected.label}`}\n haspopup=\"menu\"\n controls=\"view-menu\"\n expanded={open}\n onDsClick={event => {\n setInitialFocusVisible(event.detail.detail === 0)\n setOpen(current => !current)\n }}\n />\n <DsMenu\n id=\"view-menu\"\n anchorId=\"view-menu-trigger\"\n menuLabel=\"Choose view\"\n items={items}\n open={open}\n initialFocusVisible={initialFocusVisible}\n onDsClose={() => setOpen(false)}\n onDsSelect={event => {\n setSelectedValue(event.detail.value ?? selectedValue)\n setOpen(false)\n focusTrigger()\n }}\n />\n </>\n )\n}"
182
+ }
183
+ ],
184
+ "notes": [
185
+ "Use one React state value for both the trigger expanded prop and Menu open prop.",
186
+ "Keep the stable trigger and menu ids unique when rendering more than one composition."
187
+ ]
188
+ }
189
+ ],
190
+ "references": [
191
+ "src/react/ds-button-unfilled.ts",
192
+ "src/react/ds-menu.ts"
193
+ ]
194
+ },
195
+ "angular": {
196
+ "status": "verified",
197
+ "recipes": [
198
+ {
199
+ "id": "selected-view-menu",
200
+ "title": "Selected view menu",
201
+ "summary": "A controlled Angular composition using generated standalone ButtonUnfilled and Menu adapters.",
202
+ "files": [
203
+ {
204
+ "path": "view-menu.component.html",
205
+ "language": "html",
206
+ "content": "<ds-button-unfilled\n id=\"view-menu-trigger\"\n [label]=\"'View · ' + selectedLabel\"\n haspopup=\"menu\"\n controls=\"view-menu\"\n [expanded]=\"open\"\n (dsClick)=\"toggleMenu($event)\"\n></ds-button-unfilled>\n<ds-menu\n id=\"view-menu\"\n anchorId=\"view-menu-trigger\"\n menuLabel=\"Choose view\"\n [items]=\"menuItems\"\n [open]=\"open\"\n [initialFocusVisible]=\"initialFocusVisible\"\n (dsClose)=\"open = false\"\n (dsSelect)=\"selectView($event)\"\n></ds-menu>"
207
+ },
208
+ {
209
+ "path": "view-menu.component.ts",
210
+ "language": "typescript",
211
+ "content": "import { Component } from '@angular/core'\nimport type { HTMLDsButtonUnfilledElement, MenuItemData } from '@ds-mo/ui'\nimport { DsButtonUnfilled } from '@ds-mo/ui/angular/ds-button-unfilled'\nimport { DsMenu } from '@ds-mo/ui/angular/ds-menu'\n\nconst choices: MenuItemData[] = [\n { label: 'Overview', value: 'overview' },\n { label: 'Map', value: 'map' },\n { label: 'Timeline', value: 'timeline' },\n]\n\n@Component({\n selector: 'app-view-menu',\n standalone: true,\n imports: [DsButtonUnfilled, DsMenu],\n templateUrl: './view-menu.component.html',\n})\nexport class ViewMenuComponent {\n selectedValue = 'overview'\n open = false\n initialFocusVisible = false\n\n get selectedLabel() {\n return choices.find(item => item.value === this.selectedValue)?.label ?? choices[0].label\n }\n\n get menuItems() {\n return choices.map(item => ({ ...item, isSelected: item.value === this.selectedValue }))\n }\n\n toggleMenu(event: CustomEvent<MouseEvent>) {\n this.initialFocusVisible = event.detail.detail === 0\n this.open = !this.open\n }\n\n selectView(event: CustomEvent<MenuItemData>) {\n this.selectedValue = event.detail.value ?? this.selectedValue\n this.open = false\n requestAnimationFrame(() => {\n const trigger = document.querySelector<HTMLDsButtonUnfilledElement>('#view-menu-trigger')\n trigger?.setFocus()\n })\n }\n}"
212
+ }
213
+ ],
214
+ "notes": [
215
+ "Bind items as a property and import both generated standalone adapters.",
216
+ "Keep the stable trigger and menu ids unique when rendering more than one composition."
217
+ ]
218
+ }
219
+ ],
220
+ "references": [
221
+ "src/angular/ds-button-unfilled.ts",
222
+ "src/angular/ds-menu.ts"
223
+ ]
224
+ }
225
+ },
226
+ "references": [
227
+ "src/wc/components/Tag/Tag.stories.ts",
228
+ "src/wc/components/Menu/Menu.agent.json",
229
+ "docs/framework-integration.md"
230
+ ]
231
+ }
232
+ ]
233
+ }