@ds-mo/ui 9.0.0 → 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 (122) hide show
  1. package/README.md +28 -0
  2. package/dist/.build-stamp +1 -1
  3. package/dist/agent-patterns.json +233 -0
  4. package/dist/agent.json +155 -19
  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-swatch-picker.js +42 -0
  9. package/dist/angular/index.d.ts +1 -1
  10. package/dist/angular/index.js +1 -0
  11. package/dist/angular/proxies.d.ts +1 -0
  12. package/dist/angular/proxies.js +1 -0
  13. package/dist/components/ds-app-shell.js +1 -1
  14. package/dist/components/ds-bar-nav.js +1 -1
  15. package/dist/components/ds-bar-nav.js.map +1 -1
  16. package/dist/components/ds-menu.js +1 -1
  17. package/dist/components/ds-panel-nav.js +1 -1
  18. package/dist/components/ds-panel-nav.js.map +1 -1
  19. package/dist/components/ds-select-multi.js +1 -1
  20. package/dist/components/ds-select-multi.js.map +1 -1
  21. package/dist/components/ds-select.js +1 -1
  22. package/dist/components/ds-select.js.map +1 -1
  23. package/dist/components/ds-shell-gradient-picker.js +1 -1
  24. package/dist/components/ds-shell-gradient-picker.js.map +1 -1
  25. package/dist/components/ds-shell-gradient-swatch.js +1 -1
  26. package/dist/components/ds-shell-gradient-swatch.js.map +1 -1
  27. package/dist/components/ds-slider.js +1 -1
  28. package/dist/components/ds-slider.js.map +1 -1
  29. package/dist/components/ds-swatch-picker.d.ts +11 -0
  30. package/dist/components/ds-swatch-picker.js +2 -0
  31. package/dist/components/ds-swatch-picker.js.map +1 -0
  32. package/dist/components/ds-tooltip-data-viz.js +1 -1
  33. package/dist/components/p-D1HFMTdC.js +2 -0
  34. package/dist/components/p-D1HFMTdC.js.map +1 -0
  35. package/dist/components/p-HBIxvS5C.js +2 -0
  36. package/dist/components/p-HBIxvS5C.js.map +1 -0
  37. package/dist/components/p-l6PmnW_P.js +2 -0
  38. package/dist/components/p-l6PmnW_P.js.map +1 -0
  39. package/dist/components/p-tQ1QHeOE.js +2 -0
  40. package/dist/components/p-tQ1QHeOE.js.map +1 -0
  41. package/dist/docs/components.json +268 -30
  42. package/dist/lib/components/Menu/menu-placement.d.ts +6 -3
  43. package/dist/lib/shell/index.d.ts +2 -2
  44. package/dist/lib/shell/index.js +40 -39
  45. package/dist/lib/shell/index.js.map +3 -3
  46. package/dist/lib/shell/shell-gradient-presets.d.ts +15 -0
  47. package/dist/lib/utils/choice-popup-alignment.d.ts +20 -0
  48. package/dist/lib/utils/index.d.ts +2 -0
  49. package/dist/lib/utils/index.js +16 -0
  50. package/dist/lib/utils/index.js.map +3 -3
  51. package/dist/mcp/cli.js +31409 -0
  52. package/dist/mcp-data/registry/app-shell.json +210 -0
  53. package/dist/mcp-data/registry/badge.json +289 -0
  54. package/dist/mcp-data/registry/banner.json +206 -0
  55. package/dist/mcp-data/registry/bar-nav.json +341 -0
  56. package/dist/mcp-data/registry/button-filled.json +386 -0
  57. package/dist/mcp-data/registry/button-unfilled.json +537 -0
  58. package/dist/mcp-data/registry/card-data-viz-donut.json +143 -0
  59. package/dist/mcp-data/registry/card-setting.json +194 -0
  60. package/dist/mcp-data/registry/card-shell-data-viz.json +173 -0
  61. package/dist/mcp-data/registry/card.json +126 -0
  62. package/dist/mcp-data/registry/chart-bar.json +121 -0
  63. package/dist/mcp-data/registry/chart-donut.json +257 -0
  64. package/dist/mcp-data/registry/chart-legend.json +209 -0
  65. package/dist/mcp-data/registry/chart-line.json +153 -0
  66. package/dist/mcp-data/registry/checkbox.json +361 -0
  67. package/dist/mcp-data/registry/chip.json +276 -0
  68. package/dist/mcp-data/registry/divider.json +225 -0
  69. package/dist/mcp-data/registry/empty-state.json +197 -0
  70. package/dist/mcp-data/registry/field.json +222 -0
  71. package/dist/mcp-data/registry/icon.json +242 -0
  72. package/dist/mcp-data/registry/input.json +597 -0
  73. package/dist/mcp-data/registry/loader.json +192 -0
  74. package/dist/mcp-data/registry/menu.json +459 -0
  75. package/dist/mcp-data/registry/modal.json +150 -0
  76. package/dist/mcp-data/registry/panel-nav.json +622 -0
  77. package/dist/mcp-data/registry/panel-sub-nav.json +249 -0
  78. package/dist/mcp-data/registry/panel-tools.json +374 -0
  79. package/dist/mcp-data/registry/radio.json +374 -0
  80. package/dist/mcp-data/registry/registry.json +5389 -0
  81. package/dist/mcp-data/registry/select-multi.json +755 -0
  82. package/dist/mcp-data/registry/select.json +740 -0
  83. package/dist/mcp-data/registry/shell-gradient-picker.json +195 -0
  84. package/dist/mcp-data/registry/shell-gradient-swatch.json +215 -0
  85. package/dist/mcp-data/registry/skeleton.json +282 -0
  86. package/dist/mcp-data/registry/slider.json +657 -0
  87. package/dist/mcp-data/registry/swatch-picker.json +231 -0
  88. package/dist/mcp-data/registry/switch.json +361 -0
  89. package/dist/mcp-data/registry/tab-group.json +255 -0
  90. package/dist/mcp-data/registry/tag.json +352 -0
  91. package/dist/mcp-data/registry/text.json +371 -0
  92. package/dist/mcp-data/registry/tooltip-data-viz.json +158 -0
  93. package/dist/mcp-data/registry/tooltip.json +311 -0
  94. package/dist/react/components.d.ts +1 -0
  95. package/dist/react/components.js +1 -0
  96. package/dist/react/ds-menu.d.ts +1 -0
  97. package/dist/react/ds-menu.js +2 -1
  98. package/dist/react/ds-swatch-picker.d.ts +12 -0
  99. package/dist/react/ds-swatch-picker.js +12 -0
  100. package/dist/types/components/Menu/Menu.d.ts +7 -1
  101. package/dist/types/components/Menu/menu-placement.d.ts +6 -3
  102. package/dist/types/components/Menu/menu-types.d.ts +14 -1
  103. package/dist/types/components/ShellGradientPicker/ShellGradientPicker.d.ts +2 -5
  104. package/dist/types/components/ShellGradientSwatch/ShellGradientSwatch.d.ts +1 -0
  105. package/dist/types/components/SwatchPicker/SwatchPicker.d.ts +21 -0
  106. package/dist/types/components/SwatchPicker/swatch-picker-types.d.ts +26 -0
  107. package/dist/types/components.d.ts +117 -0
  108. package/dist/types/shell/index.d.ts +2 -2
  109. package/dist/types/shell/shell-gradient-presets.d.ts +15 -0
  110. package/dist/types/utils/choice-popup-alignment.d.ts +21 -0
  111. package/dist/types/utils/index.d.ts +2 -0
  112. package/package.json +6 -2
  113. package/dist/components/p-Bafp65bw.js +0 -2
  114. package/dist/components/p-Bafp65bw.js.map +0 -1
  115. package/dist/components/p-BdnR3jxi.js +0 -2
  116. package/dist/components/p-BdnR3jxi.js.map +0 -1
  117. package/dist/components/p-C_cu-HIT.js +0 -2
  118. package/dist/components/p-C_cu-HIT.js.map +0 -1
  119. package/dist/components/p-D0B9uDTP.js +0 -2
  120. package/dist/components/p-D0B9uDTP.js.map +0 -1
  121. package/dist/components/p-DF0yLZG_.js +0 -2
  122. package/dist/components/p-DF0yLZG_.js.map +0 -1
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):
package/dist/.build-stamp CHANGED
@@ -1 +1 @@
1
- 1784322516816
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
+ }
package/dist/agent.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
3
  "package": "@ds-mo/ui",
4
- "packageVersion": "9.0.0",
4
+ "packageVersion": "9.1.0",
5
5
  "kind": "components",
6
6
  "entries": [
7
7
  {
@@ -29,7 +29,7 @@
29
29
  "component:ds-panel-nav",
30
30
  "component:ds-bar-nav",
31
31
  "component:ds-panel-tools",
32
- "component:ds-shell-gradient-picker"
32
+ "component:ds-swatch-picker"
33
33
  ],
34
34
  "patterns": [
35
35
  "pattern:application-shell"
@@ -672,6 +672,9 @@
672
672
  "component:ds-menu",
673
673
  "component:ds-panel-tools"
674
674
  ],
675
+ "patterns": [
676
+ "pattern:menu-trigger"
677
+ ],
675
678
  "accessibility": [
676
679
  "Provide an explicit accessible name for every icon-only action.",
677
680
  "Use active state only for a persistent toggle or mode, and synchronize pressed semantics unless an owning composite exposes the selection state.",
@@ -2382,14 +2385,17 @@
2382
2385
  "component:ds-button-unfilled",
2383
2386
  "component:ds-panel-nav",
2384
2387
  "component:ds-switch",
2385
- "component:ds-shell-gradient-picker"
2388
+ "component:ds-swatch-picker"
2389
+ ],
2390
+ "patterns": [
2391
+ "pattern:menu-trigger"
2386
2392
  ],
2387
2393
  "accessibility": [
2388
2394
  "Anchor the menu to the control that opens it and return focus to that control when the menu closes.",
2389
2395
  "Open with visible initial focus only when the trigger was activated from the keyboard.",
2390
2396
  "Keep disabled items discoverable only when their unavailable state is meaningful.",
2391
2397
  "Switch rows use menuitemcheckbox semantics on the row; the nested ds-switch is an aria-hidden md presentation indicator.",
2392
- "A gradient-picker section changes the popup to a non-modal dialog so its radio group and ordinary preference buttons keep valid nested semantics; Tab moves between its controls, then leaves and closes at the popup boundary.",
2398
+ "A swatch-picker section changes the popup to a non-modal dialog so its radio group and ordinary preference buttons keep valid nested semantics; Tab moves between its controls, then leaves and closes at the popup boundary.",
2393
2399
  "Use notification dots only as supplemental status and not as the sole indication of important information."
2394
2400
  ],
2395
2401
  "states": [
@@ -2397,9 +2403,10 @@
2397
2403
  "Use either flat items or sections, not both.",
2398
2404
  "After dsSelect from a switch row, update switchValue in the items property so the controlled indicator reflects the new state.",
2399
2405
  "Arrays, objects, and element anchors must be assigned as JavaScript properties.",
2400
- "Use one gradient-picker section in the user or account Appearance or Theme popup for the global AppShell wash preference; apply dsGradientSelect immediately without closing the popup."
2406
+ "Use a swatch-picker section for a small curated visual preference and apply dsSwatchSelect immediately without closing the popup; the legacy gradient-picker section and dsGradientSelect remain compatibility-only."
2401
2407
  ],
2402
2408
  "responsiveBehavior": [
2409
+ "Keep the default choice-cell anchor alignment so the first or last interactive row edge aligns with its trigger, matching Select; use popup-frame only for deliberate custom frame geometry.",
2403
2410
  "Choose placement relative to the trigger and allow the component to clamp the popup to the viewport.",
2404
2411
  "Avoid fixed widths that exceed narrow viewports."
2405
2412
  ],
@@ -2439,6 +2446,10 @@
2439
2446
  "type": "HTMLElement | undefined",
2440
2447
  "required": false
2441
2448
  },
2449
+ "anchorAlignment": {
2450
+ "type": "ChoicePopupAnchorAlignment",
2451
+ "required": false
2452
+ },
2442
2453
  "anchorId": {
2443
2454
  "type": "string | undefined",
2444
2455
  "required": false
@@ -2492,6 +2503,10 @@
2492
2503
  {
2493
2504
  "name": "dsSelect",
2494
2505
  "detail": "MenuItemData"
2506
+ },
2507
+ {
2508
+ "name": "dsSwatchSelect",
2509
+ "detail": "string"
2495
2510
  }
2496
2511
  ],
2497
2512
  "slots": []
@@ -2507,7 +2522,7 @@
2507
2522
  ],
2508
2523
  "componentDependencies": [
2509
2524
  "badge",
2510
- "shell-gradient-picker",
2525
+ "swatch-picker",
2511
2526
  "switch",
2512
2527
  "text"
2513
2528
  ]
@@ -3757,12 +3772,14 @@
3757
3772
  "tag": "ds-shell-gradient-picker",
3758
3773
  "name": "shell-gradient-picker",
3759
3774
  "title": "ShellGradientPicker",
3760
- "summary": "The canonical compact control for selecting the single global AppShell chrome wash preference from fixed token-based presets.",
3775
+ "summary": "Deprecated compatibility wrapper that maps the fixed AppShell wash presets into SwatchPicker.",
3761
3776
  "intentStatus": "complete",
3762
3777
  "intent": {
3763
3778
  "audience": "specialized",
3764
- "status": "stable",
3765
- "summary": "The canonical compact control for selecting the single global AppShell chrome wash preference from fixed token-based presets.",
3779
+ "status": "deprecated",
3780
+ "replacedBy": "component:ds-swatch-picker",
3781
+ "replacementReason": "SwatchPicker now owns the same accessible interaction with a generic option model that supports both flat colors and gradients.",
3782
+ "summary": "Deprecated compatibility wrapper that maps the fixed AppShell wash presets into SwatchPicker.",
3766
3783
  "useWhen": [
3767
3784
  "Adding the Appearance or Theme section to the application's user or account menu.",
3768
3785
  "Users must choose the global AppShell chrome treatment from none, cool, neutral, warm, or fresh."
@@ -3816,8 +3833,8 @@
3816
3833
  },
3817
3834
  "references": [
3818
3835
  {
3819
- "label": "Executable stories",
3820
- "path": "src/wc/components/ShellGradientPicker/ShellGradientPicker.stories.ts"
3836
+ "label": "Replacement stories",
3837
+ "path": "src/wc/components/SwatchPicker/SwatchPicker.stories.ts"
3821
3838
  },
3822
3839
  {
3823
3840
  "label": "AppShell gradient contract",
@@ -3858,7 +3875,7 @@
3858
3875
  "@ds-mo/tokens"
3859
3876
  ],
3860
3877
  "componentDependencies": [
3861
- "shell-gradient-swatch"
3878
+ "swatch-picker"
3862
3879
  ]
3863
3880
  },
3864
3881
  {
@@ -3866,12 +3883,14 @@
3866
3883
  "tag": "ds-shell-gradient-swatch",
3867
3884
  "name": "shell-gradient-swatch",
3868
3885
  "title": "ShellGradientSwatch",
3869
- "summary": "Internal radio swatch used by ShellGradientPicker to preview one fixed AppShell chrome preset.",
3886
+ "summary": "Deprecated internal compatibility component for one legacy AppShell gradient swatch.",
3870
3887
  "intentStatus": "complete",
3871
3888
  "intent": {
3872
3889
  "audience": "internal",
3873
- "status": "stable",
3874
- "summary": "Internal radio swatch used by ShellGradientPicker to preview one fixed AppShell chrome preset.",
3890
+ "status": "deprecated",
3891
+ "replacedBy": "component:ds-swatch-picker",
3892
+ "replacementReason": "SwatchPicker renders its option buttons internally so an individually published radio swatch is no longer needed.",
3893
+ "summary": "Deprecated internal compatibility component for one legacy AppShell gradient swatch.",
3875
3894
  "useWhen": [
3876
3895
  "Maintaining or extending ShellGradientPicker inside CompoMo."
3877
3896
  ],
@@ -3883,12 +3902,12 @@
3883
3902
  "alternatives": [
3884
3903
  {
3885
3904
  "when": "An application needs the supported global shell appearance control.",
3886
- "component": "component:ds-shell-gradient-picker",
3887
- "reason": "The picker provides the complete preset set, radio semantics, keyboard behavior, and selection event."
3905
+ "component": "component:ds-swatch-picker",
3906
+ "reason": "SwatchPicker provides the complete radio-group semantics, keyboard behavior, generic previews, and selection event."
3888
3907
  }
3889
3908
  ],
3890
3909
  "commonlyComposedWith": [
3891
- "component:ds-shell-gradient-picker"
3910
+ "component:ds-swatch-picker"
3892
3911
  ],
3893
3912
  "accessibility": [
3894
3913
  "Keep radio semantics, aria-checked state, and a preset-specific accessible name on the internal button.",
@@ -4146,6 +4165,7 @@
4146
4165
  "Focused, filled, dirty, touched, dragging, valid, disabled, read-only, and orientation data hooks expose interaction state without changing the value contract."
4147
4166
  ],
4148
4167
  "responsiveBehavior": [
4168
+ "The visible label uses the same primary emphasized text-body-small recipe as Field at every density; the value uses the same primary metric without emphasis.",
4149
4169
  "Horizontal Slider fills the width supplied by its parent; the parent chooses an appropriate readable track length.",
4150
4170
  "Vertical Slider owns a token-based default length that may be overridden with --ds-slider-vertical-length in layout-specific compositions.",
4151
4171
  "Use md in ordinary forms and sm or xs only when the surrounding control density uses the same recipe.",
@@ -4315,6 +4335,121 @@
4315
4335
  "text"
4316
4336
  ]
4317
4337
  },
4338
+ {
4339
+ "id": "component:ds-swatch-picker",
4340
+ "tag": "ds-swatch-picker",
4341
+ "name": "swatch-picker",
4342
+ "title": "SwatchPicker",
4343
+ "summary": "Compact one-of-many picker for curated flat-color, gradient, or visually absent presets with optional section separators and roving radio-group interaction.",
4344
+ "intentStatus": "complete",
4345
+ "intent": {
4346
+ "audience": "general",
4347
+ "status": "stable",
4348
+ "summary": "Compact one-of-many picker for curated flat-color, gradient, or visually absent presets with optional section separators and roving radio-group interaction.",
4349
+ "useWhen": [
4350
+ "Users choose exactly one visual preset from a small curated set.",
4351
+ "Options are best compared through compact flat-color or gradient previews.",
4352
+ "A settings surface or richer menu needs an immediately applied visual preference."
4353
+ ],
4354
+ "avoidWhen": [
4355
+ "Users need an unrestricted spectrum, numeric channels, opacity editing, or custom CSS gradient construction.",
4356
+ "Text labels are the primary distinction between choices; use Radio instead.",
4357
+ "The choice is a submitted form value with validation or a long compact list; use Radio or Select.",
4358
+ "Color or gradient is the only indication of status, meaning, or required action."
4359
+ ],
4360
+ "alternatives": [
4361
+ {
4362
+ "when": "The user needs an arbitrary color rather than a curated preset.",
4363
+ "component": "component:ds-input",
4364
+ "reason": "SwatchPicker intentionally does not provide spectrum, channel, or free-form value editing."
4365
+ },
4366
+ {
4367
+ "when": "Visible text labels are more important than visual previews.",
4368
+ "component": "component:ds-radio",
4369
+ "reason": "Radio presents explicit one-of-many option labels and native form behavior."
4370
+ },
4371
+ {
4372
+ "when": "The option set is long or must remain compact.",
4373
+ "component": "component:ds-select",
4374
+ "reason": "Select scales a larger named choice set into an anchored listbox."
4375
+ }
4376
+ ],
4377
+ "commonlyComposedWith": [
4378
+ "component:ds-menu",
4379
+ "component:ds-app-shell"
4380
+ ],
4381
+ "accessibility": [
4382
+ "Provide a localized groupLabel describing the complete choice.",
4383
+ "Give every option a meaningful label because its visual preview is never its accessible name.",
4384
+ "The options form one radio group with one Tab stop; Arrow keys wrap across active options while Home and End select the boundaries.",
4385
+ "Inactive options remain named and disabled, and keyboard movement skips them."
4386
+ ],
4387
+ "states": [
4388
+ "value is the selected option value and updates when the user chooses an active option.",
4389
+ "options provides one flat list; sections takes precedence and adds visual separators while preserving one radio group.",
4390
+ "A preview may use a token-based backgroundColor, a CSS gradient backgroundImage, and an optional opacity from zero through one.",
4391
+ "The parent owns persistence and application of the selected value; SwatchPicker owns only choice interaction and dsChange."
4392
+ ],
4393
+ "responsiveBehavior": [
4394
+ "Options wrap within the width supplied by the parent while retaining compact fixed swatch geometry.",
4395
+ "Keep the set small enough for direct visual comparison; switch to a named list when wrapping harms comprehension."
4396
+ ],
4397
+ "frameworkCaveats": {
4398
+ "customElements": [
4399
+ "Assign options or sections as JavaScript properties, bind value, and listen for dsChange."
4400
+ ],
4401
+ "react": [
4402
+ "Use DsSwatchPicker, pass option arrays as props, and handle onDsChange."
4403
+ ],
4404
+ "angular": [
4405
+ "Use property bindings for options or sections and handle dsChange to update application state."
4406
+ ]
4407
+ },
4408
+ "references": [
4409
+ {
4410
+ "label": "Executable stories",
4411
+ "path": "src/wc/components/SwatchPicker/SwatchPicker.stories.ts"
4412
+ }
4413
+ ]
4414
+ },
4415
+ "api": {
4416
+ "props": {
4417
+ "groupLabel": {
4418
+ "type": "string",
4419
+ "required": false
4420
+ },
4421
+ "options": {
4422
+ "type": "SwatchPickerOption[]",
4423
+ "required": false
4424
+ },
4425
+ "sections": {
4426
+ "type": "SwatchPickerSection[]",
4427
+ "required": false
4428
+ },
4429
+ "value": {
4430
+ "type": "string",
4431
+ "required": false
4432
+ }
4433
+ },
4434
+ "events": [
4435
+ {
4436
+ "name": "dsChange",
4437
+ "detail": "string"
4438
+ }
4439
+ ],
4440
+ "slots": []
4441
+ },
4442
+ "exports": {
4443
+ "customElement": "ds-swatch-picker",
4444
+ "react": "DsSwatchPicker",
4445
+ "angular": "DsSwatchPicker"
4446
+ },
4447
+ "dependencies": [
4448
+ "@ds-mo/ui",
4449
+ "@ds-mo/tokens"
4450
+ ],
4451
+ "componentDependencies": []
4452
+ },
4318
4453
  {
4319
4454
  "id": "component:ds-switch",
4320
4455
  "tag": "ds-switch",
@@ -4651,7 +4786,8 @@
4651
4786
  "commonlyComposedWith": [
4652
4787
  "component:ds-card",
4653
4788
  "component:ds-text",
4654
- "component:ds-icon"
4789
+ "component:ds-icon",
4790
+ "component:ds-menu"
4655
4791
  ],
4656
4792
  "accessibility": [
4657
4793
  "The visible label provides Tag's accessible name; a separate aria-label is normally unnecessary.",
@@ -10,9 +10,10 @@ export declare class DsMenu {
10
10
  dsClose: EventEmitter<DsMenuCustomEvent<void>>;
11
11
  dsSelect: EventEmitter<DsMenuCustomEvent<IDsMenuMenuItemData>>;
12
12
  dsGradientSelect: EventEmitter<DsMenuCustomEvent<IDsMenuShellGradientPreset>>;
13
+ dsSwatchSelect: EventEmitter<DsMenuCustomEvent<string>>;
13
14
  constructor(c: ChangeDetectorRef, r: ElementRef, z: NgZone);
14
15
  static ɵfac: i0.ɵɵFactoryDeclaration<DsMenu, never>;
15
- static ɵcmp: i0.ɵɵComponentDeclaration<DsMenu, "ds-menu", never, { "align": { "alias": "align"; "required": false; }; "alignOffset": { "alias": "alignOffset"; "required": false; }; "anchor": { "alias": "anchor"; "required": false; }; "anchorId": { "alias": "anchorId"; "required": false; }; "initialFocusVisible": { "alias": "initialFocusVisible"; "required": false; }; "items": { "alias": "items"; "required": false; }; "menuLabel": { "alias": "menuLabel"; "required": false; }; "menuWidth": { "alias": "menuWidth"; "required": false; }; "minWidth": { "alias": "minWidth"; "required": false; }; "open": { "alias": "open"; "required": false; }; "sections": { "alias": "sections"; "required": false; }; "side": { "alias": "side"; "required": false; }; "sideOffset": { "alias": "sideOffset"; "required": false; }; }, { "dsClose": "dsClose"; "dsSelect": "dsSelect"; "dsGradientSelect": "dsGradientSelect"; }, never, ["*"], true, never>;
16
+ static ɵcmp: i0.ɵɵComponentDeclaration<DsMenu, "ds-menu", never, { "align": { "alias": "align"; "required": false; }; "alignOffset": { "alias": "alignOffset"; "required": false; }; "anchor": { "alias": "anchor"; "required": false; }; "anchorAlignment": { "alias": "anchorAlignment"; "required": false; }; "anchorId": { "alias": "anchorId"; "required": false; }; "initialFocusVisible": { "alias": "initialFocusVisible"; "required": false; }; "items": { "alias": "items"; "required": false; }; "menuLabel": { "alias": "menuLabel"; "required": false; }; "menuWidth": { "alias": "menuWidth"; "required": false; }; "minWidth": { "alias": "minWidth"; "required": false; }; "open": { "alias": "open"; "required": false; }; "sections": { "alias": "sections"; "required": false; }; "side": { "alias": "side"; "required": false; }; "sideOffset": { "alias": "sideOffset"; "required": false; }; }, { "dsClose": "dsClose"; "dsSelect": "dsSelect"; "dsGradientSelect": "dsGradientSelect"; "dsSwatchSelect": "dsSwatchSelect"; }, never, ["*"], true, never>;
16
17
  }
17
18
  export declare interface DsMenu extends Components.DsMenu {
18
19
  dsClose: EventEmitter<DsMenuCustomEvent<void>>;
@@ -21,4 +22,8 @@ export declare interface DsMenu extends Components.DsMenu {
21
22
  * Emitted when a `gradient-picker` section swatch is chosen.
22
23
  */
23
24
  dsGradientSelect: EventEmitter<DsMenuCustomEvent<IDsMenuShellGradientPreset>>;
25
+ /**
26
+ * Emitted when a generic `swatch-picker` section option is chosen.
27
+ */
28
+ dsSwatchSelect: EventEmitter<DsMenuCustomEvent<string>>;
24
29
  }
@@ -16,16 +16,17 @@ let DsMenu = class DsMenu {
16
16
  this.dsClose = new EventEmitter();
17
17
  this.dsSelect = new EventEmitter();
18
18
  this.dsGradientSelect = new EventEmitter();
19
+ this.dsSwatchSelect = new EventEmitter();
19
20
  c.detach();
20
21
  this.el = r.nativeElement;
21
22
  }
22
23
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: DsMenu, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
23
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: DsMenu, isStandalone: true, selector: "ds-menu", inputs: { align: "align", alignOffset: "alignOffset", anchor: "anchor", anchorId: "anchorId", initialFocusVisible: "initialFocusVisible", items: "items", menuLabel: "menuLabel", menuWidth: "menuWidth", minWidth: "minWidth", open: "open", sections: "sections", side: "side", sideOffset: "sideOffset" }, outputs: { dsClose: "dsClose", dsSelect: "dsSelect", dsGradientSelect: "dsGradientSelect" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: DsMenu, isStandalone: true, selector: "ds-menu", inputs: { align: "align", alignOffset: "alignOffset", anchor: "anchor", anchorAlignment: "anchorAlignment", anchorId: "anchorId", initialFocusVisible: "initialFocusVisible", items: "items", menuLabel: "menuLabel", menuWidth: "menuWidth", minWidth: "minWidth", open: "open", sections: "sections", side: "side", sideOffset: "sideOffset" }, outputs: { dsClose: "dsClose", dsSelect: "dsSelect", dsGradientSelect: "dsGradientSelect", dsSwatchSelect: "dsSwatchSelect" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
24
25
  };
25
26
  DsMenu = __decorate([
26
27
  ProxyCmp({
27
28
  defineCustomElementFn: defineDsMenu,
28
- inputs: ['align', 'alignOffset', 'anchor', 'anchorId', 'initialFocusVisible', 'items', 'menuLabel', 'menuWidth', 'minWidth', 'open', 'sections', 'side', 'sideOffset']
29
+ inputs: ['align', 'alignOffset', 'anchor', 'anchorAlignment', 'anchorId', 'initialFocusVisible', 'items', 'menuLabel', 'menuWidth', 'minWidth', 'open', 'sections', 'side', 'sideOffset']
29
30
  })
30
31
  ], DsMenu);
31
32
  export { DsMenu };
@@ -36,8 +37,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
36
37
  changeDetection: ChangeDetectionStrategy.OnPush,
37
38
  template: '<ng-content></ng-content>',
38
39
  // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
39
- inputs: ['align', 'alignOffset', 'anchor', 'anchorId', 'initialFocusVisible', 'items', 'menuLabel', 'menuWidth', 'minWidth', 'open', 'sections', 'side', 'sideOffset'],
40
- outputs: ['dsClose', 'dsSelect', 'dsGradientSelect'],
40
+ inputs: ['align', 'alignOffset', 'anchor', 'anchorAlignment', 'anchorId', 'initialFocusVisible', 'items', 'menuLabel', 'menuWidth', 'minWidth', 'open', 'sections', 'side', 'sideOffset'],
41
+ outputs: ['dsClose', 'dsSelect', 'dsGradientSelect', 'dsSwatchSelect'],
41
42
  }]
42
43
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { dsClose: [{
43
44
  type: Output
@@ -45,4 +46,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
45
46
  type: Output
46
47
  }], dsGradientSelect: [{
47
48
  type: Output
49
+ }], dsSwatchSelect: [{
50
+ type: Output
48
51
  }] } });