@ds-mo/ui 15.0.0 → 15.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 (145) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +7 -0
  3. package/dist/.package-ready.json +1 -1
  4. package/dist/agent-patterns.json +1 -1
  5. package/dist/agent.json +2320 -90
  6. package/dist/components/ds-agent-questionnaire.js +1 -1
  7. package/dist/components/ds-agent-response.js +1 -1
  8. package/dist/components/ds-banner.js +1 -1
  9. package/dist/components/ds-banner.js.map +1 -1
  10. package/dist/components/ds-card-chart.js +1 -1
  11. package/dist/components/ds-card-chart.js.map +1 -1
  12. package/dist/components/ds-card-overview.js +1 -1
  13. package/dist/components/ds-card-overview.js.map +1 -1
  14. package/dist/components/ds-card-setting.js +1 -1
  15. package/dist/components/ds-card-setting.js.map +1 -1
  16. package/dist/components/ds-chart.js +1 -1
  17. package/dist/components/ds-chart.js.map +1 -1
  18. package/dist/components/ds-checkbox.js +1 -1
  19. package/dist/components/ds-conversation-list-item.js +1 -1
  20. package/dist/components/ds-conversation-list-item.js.map +1 -1
  21. package/dist/components/ds-data-filter.js +1 -1
  22. package/dist/components/ds-data-preferences.js +1 -1
  23. package/dist/components/ds-data-saved-views.js +1 -1
  24. package/dist/components/ds-data-search.js +1 -1
  25. package/dist/components/ds-data-search.js.map +1 -1
  26. package/dist/components/ds-filter-menu.js +1 -1
  27. package/dist/components/ds-message-scroller.js.map +1 -1
  28. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  29. package/dist/components/ds-pagination.js +1 -1
  30. package/dist/components/ds-scroll-overlay.js.map +1 -1
  31. package/dist/components/ds-select.js +1 -1
  32. package/dist/components/ds-shell-app.js +1 -1
  33. package/dist/components/ds-table.js +1 -1
  34. package/dist/components/{p-Dpr2y6_S.js → p-B_jdoM7e.js} +2 -2
  35. package/dist/components/{p-CElUM-xU.js → p-C6xVxqOn.js} +2 -2
  36. package/dist/components/{p-MG4lcN0D.js → p-CdGXzRP8.js} +2 -2
  37. package/dist/components/{p-D6Wl3dRO.js → p-Cqc2U1ZQ.js} +2 -2
  38. package/dist/components/{p-D6Wl3dRO.js.map → p-Cqc2U1ZQ.js.map} +1 -1
  39. package/dist/components/{p-Nc76seSq.js → p-DEW7r0U9.js} +2 -2
  40. package/dist/components/{p-IEq_06J0.js → p-Dfs98tpL.js} +2 -2
  41. package/dist/components/{p-IEq_06J0.js.map → p-Dfs98tpL.js.map} +1 -1
  42. package/dist/components/p-ZhP2tVmM.js.map +1 -1
  43. package/dist/components/p-etifxiDc.js.map +1 -1
  44. package/dist/docs/components.json +1 -1
  45. package/dist/lint/STYLELINT-LICENSE +20 -0
  46. package/dist/lint/THIRD-PARTY-NOTICES +553 -0
  47. package/dist/lint/index.d.ts +7 -0
  48. package/dist/lint/index.js +51385 -0
  49. package/dist/lint/types.d.ts +13 -0
  50. package/dist/mcp-data/registry/agent-activity.json +6 -1
  51. package/dist/mcp-data/registry/agent-questionnaire.json +6 -1
  52. package/dist/mcp-data/registry/agent-response.json +12 -1
  53. package/dist/mcp-data/registry/agent-source-list.json +6 -1
  54. package/dist/mcp-data/registry/agent-tool-call.json +6 -1
  55. package/dist/mcp-data/registry/attachment-list.json +6 -1
  56. package/dist/mcp-data/registry/avatar.json +21 -1
  57. package/dist/mcp-data/registry/badge.json +30 -2
  58. package/dist/mcp-data/registry/banner.json +28 -2
  59. package/dist/mcp-data/registry/bar-action.json +24 -1
  60. package/dist/mcp-data/registry/bar-nav.json +6 -1
  61. package/dist/mcp-data/registry/bar-page-title.json +24 -1
  62. package/dist/mcp-data/registry/bar-title.json +26 -1
  63. package/dist/mcp-data/registry/bar-workflow.json +24 -1
  64. package/dist/mcp-data/registry/breadcrumb.json +6 -1
  65. package/dist/mcp-data/registry/button-filled.json +68 -1
  66. package/dist/mcp-data/registry/button-inverted.json +64 -1
  67. package/dist/mcp-data/registry/button-unfilled.json +66 -1
  68. package/dist/mcp-data/registry/calendar.json +14 -1
  69. package/dist/mcp-data/registry/card-action-center.json +24 -1
  70. package/dist/mcp-data/registry/card-chart.json +37 -2
  71. package/dist/mcp-data/registry/card-navigation.json +36 -1
  72. package/dist/mcp-data/registry/card-overview.json +27 -2
  73. package/dist/mcp-data/registry/card-setting.json +35 -2
  74. package/dist/mcp-data/registry/card-settings-scope.json +24 -1
  75. package/dist/mcp-data/registry/chart-legend.json +6 -1
  76. package/dist/mcp-data/registry/chart.json +7 -2
  77. package/dist/mcp-data/registry/checkbox.json +38 -2
  78. package/dist/mcp-data/registry/chip.json +50 -1
  79. package/dist/mcp-data/registry/code-block.json +30 -1
  80. package/dist/mcp-data/registry/conversation-list-item.json +25 -2
  81. package/dist/mcp-data/registry/conversation-list-section.json +14 -1
  82. package/dist/mcp-data/registry/conversation-list.json +6 -1
  83. package/dist/mcp-data/registry/data-customize.json +26 -1
  84. package/dist/mcp-data/registry/data-filter.json +26 -1
  85. package/dist/mcp-data/registry/data-group.json +26 -1
  86. package/dist/mcp-data/registry/data-preferences.json +26 -1
  87. package/dist/mcp-data/registry/data-saved-views.json +26 -1
  88. package/dist/mcp-data/registry/data-search.json +27 -2
  89. package/dist/mcp-data/registry/data-sort.json +26 -1
  90. package/dist/mcp-data/registry/data-toolbar.json +14 -1
  91. package/dist/mcp-data/registry/divider.json +6 -1
  92. package/dist/mcp-data/registry/empty-state.json +6 -1
  93. package/dist/mcp-data/registry/field.json +14 -1
  94. package/dist/mcp-data/registry/filter-menu.json +48 -1
  95. package/dist/mcp-data/registry/icon.json +21 -1
  96. package/dist/mcp-data/registry/input-date.json +48 -1
  97. package/dist/mcp-data/registry/input-time.json +48 -1
  98. package/dist/mcp-data/registry/input.json +48 -1
  99. package/dist/mcp-data/registry/loader.json +21 -1
  100. package/dist/mcp-data/registry/markdown.json +17 -1
  101. package/dist/mcp-data/registry/menu.json +26 -1
  102. package/dist/mcp-data/registry/message-actions.json +6 -1
  103. package/dist/mcp-data/registry/message-bubble.json +26 -1
  104. package/dist/mcp-data/registry/message-composer.json +24 -1
  105. package/dist/mcp-data/registry/message-scroller.json +12 -1
  106. package/dist/mcp-data/registry/message.json +6 -1
  107. package/dist/mcp-data/registry/mobile-bar-nav.json +24 -1
  108. package/dist/mcp-data/registry/mobile-header.json +26 -1
  109. package/dist/mcp-data/registry/mobile-section-switcher.json +26 -1
  110. package/dist/mcp-data/registry/mobile-sheet-nav.json +27 -2
  111. package/dist/mcp-data/registry/modal.json +24 -1
  112. package/dist/mcp-data/registry/pagination.json +6 -1
  113. package/dist/mcp-data/registry/panel-nav.json +28 -2
  114. package/dist/mcp-data/registry/panel-sub-nav.json +25 -0
  115. package/dist/mcp-data/registry/panel-tool-header.json +24 -1
  116. package/dist/mcp-data/registry/panel-tool-search.json +24 -1
  117. package/dist/mcp-data/registry/panel-tools.json +25 -1
  118. package/dist/mcp-data/registry/paper-texture.json +6 -1
  119. package/dist/mcp-data/registry/radio.json +35 -1
  120. package/dist/mcp-data/registry/registry.json +2319 -89
  121. package/dist/mcp-data/registry/score.json +38 -1
  122. package/dist/mcp-data/registry/scroll-overlay.json +6 -1
  123. package/dist/mcp-data/registry/select.json +52 -1
  124. package/dist/mcp-data/registry/shell-app.json +26 -1
  125. package/dist/mcp-data/registry/shell-page.json +6 -1
  126. package/dist/mcp-data/registry/shell-tools.json +25 -1
  127. package/dist/mcp-data/registry/skeleton.json +41 -1
  128. package/dist/mcp-data/registry/slider.json +42 -1
  129. package/dist/mcp-data/registry/swatch-picker.json +24 -1
  130. package/dist/mcp-data/registry/switch.json +37 -1
  131. package/dist/mcp-data/registry/tab-group.json +49 -1
  132. package/dist/mcp-data/registry/table.json +102 -1
  133. package/dist/mcp-data/registry/tag.json +52 -1
  134. package/dist/mcp-data/registry/text.json +22 -1
  135. package/dist/mcp-data/registry/textarea.json +38 -1
  136. package/dist/mcp-data/registry/time-picker.json +14 -1
  137. package/dist/mcp-data/registry/toast.json +24 -1
  138. package/dist/mcp-data/registry/tooltip-chart.json +24 -1
  139. package/dist/mcp-data/registry/tooltip.json +26 -1
  140. package/dist/mcp-data/registry/typing-indicator.json +6 -1
  141. package/package.json +32 -7
  142. /package/dist/components/{p-Dpr2y6_S.js.map → p-B_jdoM7e.js.map} +0 -0
  143. /package/dist/components/{p-CElUM-xU.js.map → p-C6xVxqOn.js.map} +0 -0
  144. /package/dist/components/{p-MG4lcN0D.js.map → p-CdGXzRP8.js.map} +0 -0
  145. /package/dist/components/{p-Nc76seSq.js.map → p-DEW7r0U9.js.map} +0 -0
@@ -79,7 +79,32 @@
79
79
  "label": "Surface ownership and trigger state",
80
80
  "path": "src/docs/SurfaceOwnership.mdx"
81
81
  }
82
- ]
82
+ ],
83
+ "styling": {
84
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
85
+ "protected": [
86
+ {
87
+ "properties": [
88
+ "background",
89
+ "border",
90
+ "box-shadow",
91
+ "color"
92
+ ],
93
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
94
+ "props": [
95
+ "hasBorder"
96
+ ]
97
+ },
98
+ {
99
+ "properties": [
100
+ "border-radius"
101
+ ],
102
+ "reason": "The component owns its surface radius; retain the shared recipe and customize an outer wrapper when needed.",
103
+ "props": []
104
+ }
105
+ ],
106
+ "customProperties": []
107
+ }
83
108
  },
84
109
  "api": {
85
110
  "props": {
@@ -350,7 +375,7 @@
350
375
  },
351
376
  {
352
377
  "path": "src/wc/components/DataSearch/DataSearch.tsx",
353
- "content": "import type { DataField } from '../../utils/data-field';\nimport {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n choicePopupMinWidth,\n resolveChoicePopupAlignOffset,\n resolveCssLengthPx,\n TOKEN_DEFAULTS,\n} from '../../utils';\nimport { AnchoredPositionController } from '../../utils/anchored-position-controller';\nimport { AnchoredOverlayInteractionController } from '../../utils/anchored-overlay-interaction-controller';\nimport { resolveAnchoredOverlayBoundaryRect } from '../../utils/anchored-overlay-boundary';\nimport {\n availableTableSearchFields,\n filterTableSearchFields,\n nextTableSearchActiveIndex,\n selectedTableSearchFields,\n tableSearchFields,\n} from './data-search-model';\nimport type { DataSearchField, DataSearchFieldsChangeDetail } from './data-search-types';\n\nlet tableSearchSequence = 0;\n\n@Component({\n tag: 'ds-data-search',\n styleUrl: 'DataSearch.css',\n scoped: true,\n})\nexport class DataSearch {\n @Element() private el!: HTMLElement;\n\n /** Show the resting field border; keyboard focus remains visible. */\n @Prop({ reflect: true }) hasBorder: boolean = true;\n\n /** Controlled free-text query. */\n @Prop() value: string = '';\n /** Table column catalog used to derive searchable data points and their complete labels. */\n @Prop() fields: DataField[] = [];\n /** Controlled ordered field scopes rendered as Tags. */\n @Prop() selectedFieldIds: string[] = [];\n @Prop() placeholder: string = 'Search';\n @Prop({ attribute: 'aria-label' }) ariaLabel: string = 'Search table';\n @Prop() clearLabel: string = 'Clear search';\n @Prop() fieldMenuLabel: string = 'Choose search fields';\n @Prop() isInactive: boolean = false;\n\n @Event({ bubbles: false }) dsChange!: EventEmitter<string>;\n @Event({ bubbles: false }) dsFieldsChange!: EventEmitter<DataSearchFieldsChangeDetail>;\n @Event({ bubbles: false }) dsClear!: EventEmitter<void>;\n\n @State() private menuOpen = false;\n @State() private menuQuery = '';\n @State() private activeIndex = 0;\n @State() private focusRingVisible = false;\n @State() private position = { x: 0, y: 0 };\n @State() private positionReady = false;\n @State() private availableHeight: number | null = null;\n @State() private announcement = '';\n\n private readonly componentId = `ds-data-search-${++tableSearchSequence}`;\n private readonly inputId = `${this.componentId}-input`;\n private readonly listboxId = `${this.componentId}-listbox`;\n private readonly descriptionId = `${this.componentId}-description`;\n private inputEl: HTMLInputElement | null = null;\n private controlEl: HTMLElement | null = null;\n private popupEl: HTMLElement | null = null;\n\n private readonly positionController = new AnchoredPositionController({\n getAnchor: () => this.controlEl,\n getPopup: () => this.popupEl,\n getOwnerDocument: () => this.el.ownerDocument,\n measure: (anchor, popup) => {\n if (!this.menuOpen || !popup.isConnected) return null;\n const inset = resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);\n popup.style.minWidth = `max(var(--ds-choice-popup-min-inline-size, ${TOKEN_DEFAULTS.menuWidthXs}), ${choicePopupMinWidth(anchor.offsetWidth, inset)}px)`;\n const anchorRect = anchor.getBoundingClientRect();\n return {\n anchorRect,\n popupWidth: popup.offsetWidth,\n popupHeight: popup.offsetHeight,\n side: 'bottom',\n align: 'start',\n sideOffsetPx: inset,\n alignOffsetPx: resolveChoicePopupAlignOffset({\n align: 'start',\n alignOffsetPx: 0,\n sectionInsetPx: inset,\n }),\n viewportPadPx: inset,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n collisionRect: resolveAnchoredOverlayBoundaryRect(anchor),\n };\n },\n apply: ({ x, y, availableHeight }) => {\n this.position = { x, y };\n this.availableHeight = availableHeight;\n },\n onReady: () => {\n this.positionReady = true;\n },\n topLayer: true,\n liveUpdate: 'frame',\n observeResize: true,\n observeAnchorMotion: true,\n });\n\n private readonly interactionController = new AnchoredOverlayInteractionController({\n getAnchor: () => this.controlEl,\n getPopup: () => this.popupEl,\n getOwnerDocument: () => this.el.ownerDocument,\n onOutsideActivation: () => this.closeMenu(),\n });\n\n disconnectedCallback(): void {\n this.closeMenu();\n }\n\n @Watch('columns')\n @Watch('selectedFieldIds')\n syncAvailableFields(): void {\n const count = this.availableFields.length;\n if (count === 0) {\n this.closeMenu();\n return;\n }\n if (this.activeIndex >= count) this.activeIndex = count - 1;\n }\n\n @Watch('isInactive')\n syncInactive(isInactive: boolean): void {\n if (isInactive) this.closeMenu();\n }\n\n @Method()\n async setFocus(): Promise<void> {\n this.inputEl?.focus();\n }\n\n private get selectedFields(): DataSearchField[] {\n return selectedTableSearchFields(this.searchFields, this.selectedFieldIds);\n }\n\n private get availableFields(): DataSearchField[] {\n return availableTableSearchFields(this.searchFields, this.selectedFieldIds);\n }\n\n private get visibleFields(): DataSearchField[] {\n return filterTableSearchFields(this.availableFields, this.menuQuery);\n }\n\n private get searchFields(): DataSearchField[] {\n return tableSearchFields(this.fields);\n }\n\n private get activeOptionId(): string | undefined {\n return this.menuOpen && this.activeIndex >= 0\n ? `${this.componentId}-option-${this.activeIndex}`\n : undefined;\n }\n\n private openMenu(fromKeyboard: boolean): void {\n if (this.isInactive || this.menuOpen || this.availableFields.length === 0) return;\n this.menuQuery = '';\n // The native value can be ahead of the controlled prop when slash follows input\n // in the same frame, so clear it before the queued render can skip an equal value.\n if (this.inputEl) this.inputEl.value = '';\n this.activeIndex = 0;\n this.focusRingVisible = fromKeyboard;\n this.positionReady = false;\n this.menuOpen = true;\n requestAnimationFrame(() => {\n if (!this.menuOpen) return;\n this.interactionController.connect();\n this.positionController.observe();\n this.positionController.schedule();\n });\n }\n\n private closeMenu(): void {\n if (!this.menuOpen && !this.popupEl) return;\n if (this.popupEl?.matches(':popover-open')) this.popupEl.hidePopover();\n this.positionController.unobserve();\n this.interactionController.disconnect();\n this.popupEl = null;\n this.positionReady = false;\n this.availableHeight = null;\n this.menuOpen = false;\n this.menuQuery = '';\n }\n\n private handleInput(event: Event): void {\n const nextValue = (event.target as HTMLInputElement).value;\n if (this.menuOpen) {\n this.menuQuery = nextValue;\n this.activeIndex = filterTableSearchFields(this.availableFields, nextValue).length ? 0 : -1;\n this.focusRingVisible = false;\n this.positionController.schedule();\n return;\n }\n\n this.dsChange.emit(nextValue);\n }\n\n private selectField(field: DataSearchField): void {\n this.dsFieldsChange.emit({ selectedFieldIds: [...this.selectedFieldIds, field.id] });\n this.announcement = `${field.label} search field added.`;\n this.closeMenu();\n requestAnimationFrame(() => this.inputEl?.focus({ preventScroll: true }));\n }\n\n private removeLastField(): void {\n const selected = this.selectedFields;\n const removed = selected[selected.length - 1];\n if (!removed) return;\n this.dsFieldsChange.emit({\n selectedFieldIds: this.selectedFieldIds.filter(fieldId => fieldId !== removed.id),\n });\n this.announcement = `${removed.label} search field removed.`;\n }\n\n private removeField(field: DataSearchField): void {\n this.dsFieldsChange.emit({\n selectedFieldIds: this.selectedFieldIds.filter(fieldId => fieldId !== field.id),\n });\n this.announcement = `${field.label} search field removed.`;\n requestAnimationFrame(() => this.inputEl?.focus({ preventScroll: true }));\n }\n\n private clearSearch(): void {\n if (this.value) this.dsChange.emit('');\n if (this.selectedFieldIds.length) this.dsFieldsChange.emit({ selectedFieldIds: [] });\n this.dsClear.emit();\n this.announcement = 'Search cleared.';\n this.closeMenu();\n requestAnimationFrame(() => this.inputEl?.focus({ preventScroll: true }));\n }\n\n private handleKeyDown(event: KeyboardEvent): void {\n if (event.key === '/' && !this.menuOpen && !event.altKey && !event.ctrlKey && !event.metaKey) {\n event.preventDefault();\n this.openMenu(true);\n return;\n }\n\n if (event.key === 'Backspace' && this.menuOpen && this.menuQuery.length === 0) {\n event.preventDefault();\n this.closeMenu();\n return;\n }\n\n if (event.key === 'Backspace' && this.value.length === 0) {\n if (this.selectedFields.length) {\n event.preventDefault();\n this.removeLastField();\n }\n return;\n }\n\n if (!this.menuOpen) return;\n\n if (event.key === 'Escape') {\n event.preventDefault();\n this.closeMenu();\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n this.focusRingVisible = true;\n this.activeIndex = nextTableSearchActiveIndex(\n this.activeIndex,\n this.visibleFields.length,\n event.key === 'ArrowDown' ? 1 : -1\n );\n this.el\n .querySelector<HTMLElement>(`#${CSS.escape(this.activeOptionId ?? '')}`)\n ?.scrollIntoView({ block: 'nearest' });\n return;\n }\n\n if (event.key === 'Enter') {\n const field = this.visibleFields[this.activeIndex];\n if (!field) return;\n event.preventDefault();\n this.selectField(field);\n return;\n }\n\n if (event.key === 'Tab') this.closeMenu();\n }\n\n render() {\n const selected = this.selectedFields;\n const available = this.availableFields;\n const visible = this.visibleFields;\n const inputValue = this.menuOpen ? this.menuQuery : this.value;\n const hasValue = this.value.length > 0 || selected.length > 0;\n const description = selected.length\n ? `Search limited to ${selected.map(field => field.label).join(' or ')}.`\n : 'Search includes every available table field. Type slash to choose fields.';\n const popupStyle: Record<string, string> = {\n position: 'fixed',\n left: '0',\n top: '0',\n transform: `translate(${Math.round(this.position.x)}px, ${Math.round(this.position.y)}px)`,\n zIndex: 'var(--dimension-z-index-floating)',\n visibility: this.positionReady ? 'visible' : 'hidden',\n };\n if (this.availableHeight !== null) {\n popupStyle['max-height'] = `${Math.floor(this.availableHeight)}px`;\n }\n\n return (\n <Host\n class={{\n 'data-search-host': true,\n 'data-search-host--borderless': !this.hasBorder,\n 'ds-control--md': true,\n 'ds-control-inactive': this.isInactive,\n }}\n data-filled={hasValue ? '' : undefined}\n data-expanded={this.menuOpen ? '' : undefined}\n >\n <div\n ref={element => {\n this.controlEl = element ?? null;\n }}\n class=\"data-search__control ds-control-frame ds-control--md ds-interaction-fill\"\n >\n <span\n class=\"data-search__icon ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name=\"MagnifyingGlass\" size=\"md\" color=\"inherit\" />\n </span>\n <div class=\"data-search__editor ds-interaction-fill__content\">\n {selected.map(field => (\n <ds-chip\n class=\"data-search__chip\"\n key={field.id}\n label={field.label}\n size=\"md\"\n state=\"default\"\n isInset\n insetDepth=\"double\"\n isInactive={this.isInactive}\n onDsRemove={() => this.removeField(field)}\n />\n ))}\n <input\n ref={element => {\n this.inputEl = element ?? null;\n }}\n id={this.inputId}\n class=\"data-search__input ds-text--body-medium ds-text--regular\"\n type=\"search\"\n role=\"combobox\"\n value={inputValue}\n placeholder={this.placeholder}\n disabled={this.isInactive}\n aria-label={this.ariaLabel}\n aria-describedby={this.descriptionId}\n aria-haspopup=\"listbox\"\n aria-expanded={String(this.menuOpen)}\n aria-controls={this.menuOpen ? this.listboxId : undefined}\n aria-activedescendant={this.activeOptionId}\n aria-keyshortcuts=\"/\"\n autoComplete=\"off\"\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellcheck={false}\n onInput={event => this.handleInput(event)}\n onKeyDown={event => this.handleKeyDown(event)}\n />\n </div>\n {inputValue.length > 0 && !this.isInactive && (\n <ds-button-unfilled\n class=\"data-search__clear ds-interaction-fill__content\"\n variant=\"icon\"\n size=\"sm\"\n icon=\"CrossCircle\"\n hasBorder={false}\n rounded\n ariaLabel={this.clearLabel}\n onDsClick={() => this.clearSearch()}\n />\n )}\n </div>\n <span id={this.descriptionId} class=\"ds-visually-hidden\">\n {description}\n </span>\n <span class=\"ds-visually-hidden\" aria-live=\"polite\">\n {this.announcement}\n </span>\n {this.menuOpen && (\n <div\n popover=\"manual\"\n ref={element => {\n this.popupEl = element ?? null;\n }}\n class=\"data-search__popup ds-choice-popup\"\n style={popupStyle}\n >\n <div\n id={this.listboxId}\n class=\"data-search__list ds-choice-list\"\n role=\"listbox\"\n aria-label={this.fieldMenuLabel}\n >\n {visible.map((field, index) => {\n const active = index === this.activeIndex;\n return (\n /* eslint-disable-next-line local/prefer-direct-ds-text -- The option owns listbox semantics and pointer interaction while ds-text remains its typography child. */\n <div\n id={`${this.componentId}-option-${index}`}\n key={field.id}\n class={{\n 'data-search__option': true,\n 'ds-choice-item': true,\n 'ds-control-frame': true,\n 'ds-control--md': true,\n 'ds-focus-ring-inset': true,\n 'ds-focus-ring--visible': active && this.focusRingVisible,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': active,\n }}\n role=\"option\"\n aria-selected={String(active)}\n onMouseDown={event => event.preventDefault()}\n onMouseMove={() => {\n this.focusRingVisible = false;\n this.activeIndex = index;\n }}\n onClick={() => this.selectField(field)}\n >\n <ds-text\n class=\"ds-choice-item__label ds-control-label-box ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n color={active ? 'primary' : 'secondary'}\n >\n {field.label}\n </ds-text>\n </div>\n );\n })}\n {visible.length === 0 && (\n <div class=\"data-search__empty ds-choice-empty ds-empty-region\">\n <ds-empty-state body=\"No matching data points\" />\n </div>\n )}\n </div>\n </div>\n )}\n </Host>\n );\n }\n}\n",
378
+ "content": "import type { DataField } from '../../utils/data-field';\nimport {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n choicePopupMinWidth,\n resolveChoicePopupAlignOffset,\n resolveCssLengthPx,\n TOKEN_DEFAULTS,\n} from '../../utils';\nimport { AnchoredPositionController } from '../../utils/anchored-position-controller';\nimport { AnchoredOverlayInteractionController } from '../../utils/anchored-overlay-interaction-controller';\nimport { resolveAnchoredOverlayBoundaryRect } from '../../utils/anchored-overlay-boundary';\nimport {\n availableTableSearchFields,\n filterTableSearchFields,\n nextTableSearchActiveIndex,\n selectedTableSearchFields,\n tableSearchFields,\n} from './data-search-model';\nimport type { DataSearchField, DataSearchFieldsChangeDetail } from './data-search-types';\n\nlet tableSearchSequence = 0;\n\n@Component({\n tag: 'ds-data-search',\n styleUrl: 'DataSearch.css',\n scoped: true,\n})\nexport class DataSearch {\n @Element() private el!: HTMLElement;\n\n /** Show the resting field border; keyboard focus remains visible. */\n @Prop({ reflect: true }) hasBorder: boolean = true;\n\n /** Controlled free-text query. */\n @Prop() value: string = '';\n /** Table column catalog used to derive searchable data points and their complete labels. */\n @Prop() fields: DataField[] = [];\n /** Controlled ordered field scopes rendered as Tags. */\n @Prop() selectedFieldIds: string[] = [];\n @Prop() placeholder: string = 'Search';\n @Prop({ attribute: 'aria-label' }) ariaLabel: string = 'Search table';\n @Prop() clearLabel: string = 'Clear search';\n @Prop() fieldMenuLabel: string = 'Choose search fields';\n @Prop() isInactive: boolean = false;\n\n @Event({ bubbles: false }) dsChange!: EventEmitter<string>;\n @Event({ bubbles: false }) dsFieldsChange!: EventEmitter<DataSearchFieldsChangeDetail>;\n @Event({ bubbles: false }) dsClear!: EventEmitter<void>;\n\n @State() private menuOpen = false;\n @State() private menuQuery = '';\n @State() private activeIndex = 0;\n @State() private focusRingVisible = false;\n @State() private position = { x: 0, y: 0 };\n @State() private positionReady = false;\n @State() private availableHeight: number | null = null;\n @State() private announcement = '';\n\n private readonly componentId = `ds-data-search-${++tableSearchSequence}`;\n private readonly inputId = `${this.componentId}-input`;\n private readonly listboxId = `${this.componentId}-listbox`;\n private readonly descriptionId = `${this.componentId}-description`;\n private inputEl: HTMLInputElement | null = null;\n private controlEl: HTMLElement | null = null;\n private popupEl: HTMLElement | null = null;\n\n private readonly positionController = new AnchoredPositionController({\n getAnchor: () => this.controlEl,\n getPopup: () => this.popupEl,\n getOwnerDocument: () => this.el.ownerDocument,\n measure: (anchor, popup) => {\n if (!this.menuOpen || !popup.isConnected) return null;\n const inset = resolveCssLengthPx(TOKEN_DEFAULTS.space050, TOKEN_DEFAULTS.space050);\n popup.style.minWidth = `max(var(--ds-choice-popup-min-inline-size, ${TOKEN_DEFAULTS.menuWidthXs}), ${choicePopupMinWidth(anchor.offsetWidth, inset)}px)`;\n const anchorRect = anchor.getBoundingClientRect();\n return {\n anchorRect,\n popupWidth: popup.offsetWidth,\n popupHeight: popup.offsetHeight,\n side: 'bottom',\n align: 'start',\n sideOffsetPx: inset,\n alignOffsetPx: resolveChoicePopupAlignOffset({\n align: 'start',\n alignOffsetPx: 0,\n sectionInsetPx: inset,\n }),\n viewportPadPx: inset,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n collisionRect: resolveAnchoredOverlayBoundaryRect(anchor),\n };\n },\n apply: ({ x, y, availableHeight }) => {\n this.position = { x, y };\n this.availableHeight = availableHeight;\n },\n onReady: () => {\n this.positionReady = true;\n },\n topLayer: true,\n liveUpdate: 'frame',\n observeResize: true,\n observeAnchorMotion: true,\n });\n\n private readonly interactionController = new AnchoredOverlayInteractionController({\n getAnchor: () => this.controlEl,\n getPopup: () => this.popupEl,\n getOwnerDocument: () => this.el.ownerDocument,\n onOutsideActivation: () => this.closeMenu(),\n });\n\n disconnectedCallback(): void {\n this.closeMenu();\n }\n\n @Watch('columns')\n @Watch('selectedFieldIds')\n syncAvailableFields(): void {\n const count = this.availableFields.length;\n if (count === 0) {\n this.closeMenu();\n return;\n }\n if (this.activeIndex >= count) this.activeIndex = count - 1;\n }\n\n @Watch('isInactive')\n syncInactive(isInactive: boolean): void {\n if (isInactive) this.closeMenu();\n }\n\n @Method()\n async setFocus(): Promise<void> {\n this.inputEl?.focus();\n }\n\n private get selectedFields(): DataSearchField[] {\n return selectedTableSearchFields(this.searchFields, this.selectedFieldIds);\n }\n\n private get availableFields(): DataSearchField[] {\n return availableTableSearchFields(this.searchFields, this.selectedFieldIds);\n }\n\n private get visibleFields(): DataSearchField[] {\n return filterTableSearchFields(this.availableFields, this.menuQuery);\n }\n\n private get searchFields(): DataSearchField[] {\n return tableSearchFields(this.fields);\n }\n\n private get activeOptionId(): string | undefined {\n return this.menuOpen && this.activeIndex >= 0\n ? `${this.componentId}-option-${this.activeIndex}`\n : undefined;\n }\n\n private openMenu(fromKeyboard: boolean): void {\n if (this.isInactive || this.menuOpen || this.availableFields.length === 0) return;\n this.menuQuery = '';\n // The native value can be ahead of the controlled prop when slash follows input\n // in the same frame, so clear it before the queued render can skip an equal value.\n if (this.inputEl) this.inputEl.value = '';\n this.activeIndex = 0;\n this.focusRingVisible = fromKeyboard;\n this.positionReady = false;\n this.menuOpen = true;\n requestAnimationFrame(() => {\n if (!this.menuOpen) return;\n this.interactionController.connect();\n this.positionController.observe();\n this.positionController.schedule();\n });\n }\n\n private closeMenu(): void {\n if (!this.menuOpen && !this.popupEl) return;\n if (this.popupEl?.matches(':popover-open')) this.popupEl.hidePopover();\n this.positionController.unobserve();\n this.interactionController.disconnect();\n this.popupEl = null;\n this.positionReady = false;\n this.availableHeight = null;\n this.menuOpen = false;\n this.menuQuery = '';\n }\n\n private handleInput(event: Event): void {\n const nextValue = (event.target as HTMLInputElement).value;\n if (this.menuOpen) {\n this.menuQuery = nextValue;\n this.activeIndex = filterTableSearchFields(this.availableFields, nextValue).length ? 0 : -1;\n this.focusRingVisible = false;\n this.positionController.schedule();\n return;\n }\n\n this.dsChange.emit(nextValue);\n }\n\n private selectField(field: DataSearchField): void {\n this.dsFieldsChange.emit({ selectedFieldIds: [...this.selectedFieldIds, field.id] });\n this.announcement = `${field.label} search field added.`;\n this.closeMenu();\n requestAnimationFrame(() => this.inputEl?.focus({ preventScroll: true }));\n }\n\n private removeLastField(): void {\n const selected = this.selectedFields;\n const removed = selected[selected.length - 1];\n if (!removed) return;\n this.dsFieldsChange.emit({\n selectedFieldIds: this.selectedFieldIds.filter(fieldId => fieldId !== removed.id),\n });\n this.announcement = `${removed.label} search field removed.`;\n }\n\n private removeField(field: DataSearchField): void {\n this.dsFieldsChange.emit({\n selectedFieldIds: this.selectedFieldIds.filter(fieldId => fieldId !== field.id),\n });\n this.announcement = `${field.label} search field removed.`;\n requestAnimationFrame(() => this.inputEl?.focus({ preventScroll: true }));\n }\n\n private clearSearch(): void {\n if (this.value) this.dsChange.emit('');\n if (this.selectedFieldIds.length) this.dsFieldsChange.emit({ selectedFieldIds: [] });\n this.dsClear.emit();\n this.announcement = 'Search cleared.';\n this.closeMenu();\n requestAnimationFrame(() => this.inputEl?.focus({ preventScroll: true }));\n }\n\n private handleKeyDown(event: KeyboardEvent): void {\n if (event.key === '/' && !this.menuOpen && !event.altKey && !event.ctrlKey && !event.metaKey) {\n event.preventDefault();\n this.openMenu(true);\n return;\n }\n\n if (event.key === 'Backspace' && this.menuOpen && this.menuQuery.length === 0) {\n event.preventDefault();\n this.closeMenu();\n return;\n }\n\n if (event.key === 'Backspace' && this.value.length === 0) {\n if (this.selectedFields.length) {\n event.preventDefault();\n this.removeLastField();\n }\n return;\n }\n\n if (!this.menuOpen) return;\n\n if (event.key === 'Escape') {\n event.preventDefault();\n this.closeMenu();\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n this.focusRingVisible = true;\n this.activeIndex = nextTableSearchActiveIndex(\n this.activeIndex,\n this.visibleFields.length,\n event.key === 'ArrowDown' ? 1 : -1\n );\n this.el\n .querySelector<HTMLElement>(`#${CSS.escape(this.activeOptionId ?? '')}`)\n ?.scrollIntoView({ block: 'nearest' });\n return;\n }\n\n if (event.key === 'Enter') {\n const field = this.visibleFields[this.activeIndex];\n if (!field) return;\n event.preventDefault();\n this.selectField(field);\n return;\n }\n\n if (event.key === 'Tab') this.closeMenu();\n }\n\n render() {\n const selected = this.selectedFields;\n const available = this.availableFields;\n const visible = this.visibleFields;\n const inputValue = this.menuOpen ? this.menuQuery : this.value;\n const hasValue = this.value.length > 0 || selected.length > 0;\n const description = selected.length\n ? `Search limited to ${selected.map(field => field.label).join(' or ')}.`\n : 'Search includes every available table field. Type slash to choose fields.';\n const popupStyle: Record<string, string> = {\n position: 'fixed',\n left: '0',\n top: '0',\n transform: `translate(${Math.round(this.position.x)}px, ${Math.round(this.position.y)}px)`,\n zIndex: 'var(--dimension-z-index-floating)',\n visibility: this.positionReady ? 'visible' : 'hidden',\n };\n if (this.availableHeight !== null) {\n popupStyle['max-height'] = `${Math.floor(this.availableHeight)}px`;\n }\n\n return (\n <Host\n class={{\n 'data-search-host': true,\n 'data-search-host--borderless': !this.hasBorder,\n 'ds-control--md': true,\n 'ds-control-inactive': this.isInactive,\n }}\n data-filled={hasValue ? '' : undefined}\n data-expanded={this.menuOpen ? '' : undefined}\n >\n <div\n ref={element => {\n this.controlEl = element ?? null;\n }}\n class=\"data-search__control ds-control-frame ds-control--md ds-interaction-fill\"\n >\n <span\n class=\"data-search__icon ds-control-icon-box ds-interaction-fill__content\"\n aria-hidden=\"true\"\n >\n <ds-icon name=\"MagnifyingGlass\" size=\"md\" color=\"inherit\" />\n </span>\n <div class=\"data-search__editor ds-interaction-fill__content\">\n {selected.map(field => (\n <ds-chip\n class=\"data-search__chip\"\n key={field.id}\n label={field.label}\n size=\"md\"\n state=\"default\"\n isInset\n insetDepth=\"double\"\n isInactive={this.isInactive}\n onDsRemove={() => this.removeField(field)}\n />\n ))}\n <input\n ref={element => {\n this.inputEl = element ?? null;\n }}\n id={this.inputId}\n class=\"data-search__input ds-text--body-medium ds-text--regular\"\n type=\"search\"\n role=\"combobox\"\n value={inputValue}\n placeholder={this.placeholder}\n disabled={this.isInactive}\n aria-label={this.ariaLabel}\n aria-describedby={this.descriptionId}\n aria-haspopup=\"listbox\"\n aria-expanded={String(this.menuOpen)}\n aria-controls={this.menuOpen ? this.listboxId : undefined}\n aria-activedescendant={this.activeOptionId}\n aria-keyshortcuts=\"/\"\n autoComplete=\"off\"\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellcheck={false}\n onInput={event => this.handleInput(event)}\n onKeyDown={event => this.handleKeyDown(event)}\n />\n </div>\n {inputValue.length > 0 && !this.isInactive && (\n <ds-button-unfilled\n class=\"data-search__clear ds-interaction-fill__content\"\n variant=\"icon\"\n size=\"sm\"\n icon=\"CrossCircle\"\n hasBorder={false}\n rounded\n ariaLabel={this.clearLabel}\n onDsClick={() => this.clearSearch()}\n />\n )}\n </div>\n <span id={this.descriptionId} class=\"ds-visually-hidden\">\n {description}\n </span>\n <span class=\"ds-visually-hidden\" aria-live=\"polite\">\n {this.announcement}\n </span>\n {this.menuOpen && (\n <div\n popover=\"manual\"\n ref={element => {\n this.popupEl = element ?? null;\n }}\n class=\"data-search__popup ds-choice-popup\"\n style={popupStyle}\n >\n <div\n id={this.listboxId}\n class=\"data-search__list ds-choice-list\"\n role=\"listbox\"\n aria-label={this.fieldMenuLabel}\n >\n {visible.map((field, index) => {\n const active = index === this.activeIndex;\n return (\n /* eslint-disable-next-line compomo/prefer-direct-ds-text -- The option owns listbox semantics and pointer interaction while ds-text remains its typography child. */\n <div\n id={`${this.componentId}-option-${index}`}\n key={field.id}\n class={{\n 'data-search__option': true,\n 'ds-choice-item': true,\n 'ds-control-frame': true,\n 'ds-control--md': true,\n 'ds-focus-ring-inset': true,\n 'ds-focus-ring--visible': active && this.focusRingVisible,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--selected': active,\n }}\n role=\"option\"\n aria-selected={String(active)}\n onMouseDown={event => event.preventDefault()}\n onMouseMove={() => {\n this.focusRingVisible = false;\n this.activeIndex = index;\n }}\n onClick={() => this.selectField(field)}\n >\n <ds-text\n class=\"ds-choice-item__label ds-control-label-box ds-interaction-fill__content\"\n as=\"span\"\n variant=\"text-body-medium\"\n color={active ? 'primary' : 'secondary'}\n >\n {field.label}\n </ds-text>\n </div>\n );\n })}\n {visible.length === 0 && (\n <div class=\"data-search__empty ds-choice-empty ds-empty-region\">\n <ds-empty-state body=\"No matching data points\" />\n </div>\n )}\n </div>\n </div>\n )}\n </Host>\n );\n }\n}\n",
354
379
  "type": "registry:ui"
355
380
  },
356
381
  {
@@ -85,7 +85,32 @@
85
85
  "label": "Surface ownership and trigger state",
86
86
  "path": "src/docs/SurfaceOwnership.mdx"
87
87
  }
88
- ]
88
+ ],
89
+ "styling": {
90
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
91
+ "protected": [
92
+ {
93
+ "properties": [
94
+ "background",
95
+ "border",
96
+ "box-shadow",
97
+ "color"
98
+ ],
99
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
100
+ "props": [
101
+ "hasBorder"
102
+ ]
103
+ },
104
+ {
105
+ "properties": [
106
+ "border-radius"
107
+ ],
108
+ "reason": "The component owns its surface radius; retain the shared recipe and customize an outer wrapper when needed.",
109
+ "props": []
110
+ }
111
+ ],
112
+ "customProperties": []
113
+ }
89
114
  },
90
115
  "api": {
91
116
  "props": {
@@ -75,7 +75,20 @@
75
75
  "label": "Table composition owner",
76
76
  "path": "src/wc/components/Table/Table.agent.json"
77
77
  }
78
- ]
78
+ ],
79
+ "styling": {
80
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
81
+ "protected": [
82
+ {
83
+ "properties": [
84
+ "gap"
85
+ ],
86
+ "reason": "The component owns spacing between its immediate content regions; the application controls surrounding margins and placement.",
87
+ "props": []
88
+ }
89
+ ],
90
+ "customProperties": []
91
+ }
79
92
  },
80
93
  "api": {
81
94
  "props": {
@@ -74,7 +74,12 @@
74
74
  "label": "Storybook examples",
75
75
  "path": "src/wc/components/Divider/Divider.stories.ts"
76
76
  }
77
- ]
77
+ ],
78
+ "styling": {
79
+ "rationale": "Orientation and length are public props; the host supports application layout around the internal divider.",
80
+ "protected": [],
81
+ "customProperties": []
82
+ }
78
83
  },
79
84
  "api": {
80
85
  "props": {
@@ -85,7 +85,12 @@
85
85
  "label": "Shared layout recipe foundation",
86
86
  "path": "docs/layout-recipe-foundation.md"
87
87
  }
88
- ]
88
+ ],
89
+ "styling": {
90
+ "rationale": "The application explicitly owns available width, height and surrounding padding.",
91
+ "protected": [],
92
+ "customProperties": []
93
+ }
89
94
  },
90
95
  "api": {
91
96
  "props": {
@@ -81,7 +81,20 @@
81
81
  "label": "Cross-browser form and accessibility coverage",
82
82
  "path": "tests/e2e/forms.spec.ts"
83
83
  }
84
- ]
84
+ ],
85
+ "styling": {
86
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
87
+ "protected": [
88
+ {
89
+ "properties": [
90
+ "gap"
91
+ ],
92
+ "reason": "The component owns spacing between its immediate content regions; the application controls surrounding margins and placement.",
93
+ "props": []
94
+ }
95
+ ],
96
+ "customProperties": []
97
+ }
85
98
  },
86
99
  "api": {
87
100
  "props": {
@@ -92,7 +92,54 @@
92
92
  "label": "Surface ownership and trigger state",
93
93
  "path": "src/docs/SurfaceOwnership.mdx"
94
94
  }
95
- ]
95
+ ],
96
+ "styling": {
97
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
98
+ "protected": [
99
+ {
100
+ "properties": [
101
+ "font",
102
+ "font-size",
103
+ "font-weight",
104
+ "font-family",
105
+ "line-height",
106
+ "letter-spacing",
107
+ "height",
108
+ "min-height",
109
+ "max-height",
110
+ "padding",
111
+ "gap"
112
+ ],
113
+ "reason": "The control density is one atomic recipe; use the size and inset props instead of replacing internal metrics.",
114
+ "props": [
115
+ "size"
116
+ ]
117
+ },
118
+ {
119
+ "properties": [
120
+ "border-radius"
121
+ ],
122
+ "reason": "The component owns its corner treatment; select the supported radius treatment.",
123
+ "props": [
124
+ "size"
125
+ ]
126
+ },
127
+ {
128
+ "properties": [
129
+ "background",
130
+ "border",
131
+ "box-shadow",
132
+ "color"
133
+ ],
134
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
135
+ "props": [
136
+ "hasBorder",
137
+ "size"
138
+ ]
139
+ }
140
+ ],
141
+ "customProperties": []
142
+ }
96
143
  },
97
144
  "api": {
98
145
  "props": {
@@ -89,7 +89,27 @@
89
89
  "label": "Preload API guidance",
90
90
  "path": "src/wc/components/Icon/icon-cache.ts"
91
91
  }
92
- ]
92
+ ],
93
+ "styling": {
94
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
95
+ "protected": [
96
+ {
97
+ "properties": [
98
+ "width",
99
+ "height",
100
+ "min-width",
101
+ "min-height",
102
+ "max-width",
103
+ "max-height"
104
+ ],
105
+ "reason": "The primitive owns its size recipe; select size and position it through the parent layout.",
106
+ "props": [
107
+ "size"
108
+ ]
109
+ }
110
+ ],
111
+ "customProperties": []
112
+ }
93
113
  },
94
114
  "api": {
95
115
  "props": {
@@ -98,7 +98,54 @@
98
98
  "label": "Cross-browser form and accessibility coverage",
99
99
  "path": "tests/e2e/forms.spec.ts"
100
100
  }
101
- ]
101
+ ],
102
+ "styling": {
103
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
104
+ "protected": [
105
+ {
106
+ "properties": [
107
+ "font",
108
+ "font-size",
109
+ "font-weight",
110
+ "font-family",
111
+ "line-height",
112
+ "letter-spacing",
113
+ "height",
114
+ "min-height",
115
+ "max-height",
116
+ "padding",
117
+ "gap"
118
+ ],
119
+ "reason": "The control density is one atomic recipe; use the size and inset props instead of replacing internal metrics.",
120
+ "props": [
121
+ "size"
122
+ ]
123
+ },
124
+ {
125
+ "properties": [
126
+ "border-radius"
127
+ ],
128
+ "reason": "The component owns its corner treatment; select the supported radius treatment.",
129
+ "props": [
130
+ "size"
131
+ ]
132
+ },
133
+ {
134
+ "properties": [
135
+ "background",
136
+ "border",
137
+ "box-shadow",
138
+ "color"
139
+ ],
140
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
141
+ "props": [
142
+ "hasBorder",
143
+ "size"
144
+ ]
145
+ }
146
+ ],
147
+ "customProperties": []
148
+ }
102
149
  },
103
150
  "api": {
104
151
  "props": {
@@ -99,7 +99,54 @@
99
99
  "label": "Cross-browser form and accessibility coverage",
100
100
  "path": "tests/e2e/forms.spec.ts"
101
101
  }
102
- ]
102
+ ],
103
+ "styling": {
104
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
105
+ "protected": [
106
+ {
107
+ "properties": [
108
+ "font",
109
+ "font-size",
110
+ "font-weight",
111
+ "font-family",
112
+ "line-height",
113
+ "letter-spacing",
114
+ "height",
115
+ "min-height",
116
+ "max-height",
117
+ "padding",
118
+ "gap"
119
+ ],
120
+ "reason": "The control density is one atomic recipe; use the size and inset props instead of replacing internal metrics.",
121
+ "props": [
122
+ "size"
123
+ ]
124
+ },
125
+ {
126
+ "properties": [
127
+ "border-radius"
128
+ ],
129
+ "reason": "The component owns its corner treatment; select the supported radius treatment.",
130
+ "props": [
131
+ "size"
132
+ ]
133
+ },
134
+ {
135
+ "properties": [
136
+ "background",
137
+ "border",
138
+ "box-shadow",
139
+ "color"
140
+ ],
141
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
142
+ "props": [
143
+ "hasBorder",
144
+ "size"
145
+ ]
146
+ }
147
+ ],
148
+ "customProperties": []
149
+ }
103
150
  },
104
151
  "api": {
105
152
  "props": {
@@ -113,7 +113,54 @@
113
113
  "label": "Cross-browser form and accessibility coverage",
114
114
  "path": "tests/e2e/forms.spec.ts"
115
115
  }
116
- ]
116
+ ],
117
+ "styling": {
118
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
119
+ "protected": [
120
+ {
121
+ "properties": [
122
+ "font",
123
+ "font-size",
124
+ "font-weight",
125
+ "font-family",
126
+ "line-height",
127
+ "letter-spacing",
128
+ "height",
129
+ "min-height",
130
+ "max-height",
131
+ "padding",
132
+ "gap"
133
+ ],
134
+ "reason": "The control density is one atomic recipe; use the size and inset props instead of replacing internal metrics.",
135
+ "props": [
136
+ "size"
137
+ ]
138
+ },
139
+ {
140
+ "properties": [
141
+ "border-radius"
142
+ ],
143
+ "reason": "The component owns its corner treatment; select the supported radius treatment.",
144
+ "props": [
145
+ "size"
146
+ ]
147
+ },
148
+ {
149
+ "properties": [
150
+ "background",
151
+ "border",
152
+ "box-shadow",
153
+ "color"
154
+ ],
155
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
156
+ "props": [
157
+ "hasBorder",
158
+ "size"
159
+ ]
160
+ }
161
+ ],
162
+ "customProperties": []
163
+ }
117
164
  },
118
165
  "api": {
119
166
  "props": {
@@ -78,7 +78,27 @@
78
78
  "label": "Storybook examples",
79
79
  "path": "src/wc/components/Loader/Loader.stories.ts"
80
80
  }
81
- ]
81
+ ],
82
+ "styling": {
83
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
84
+ "protected": [
85
+ {
86
+ "properties": [
87
+ "width",
88
+ "height",
89
+ "min-width",
90
+ "min-height",
91
+ "max-width",
92
+ "max-height"
93
+ ],
94
+ "reason": "The primitive owns its size recipe; select size and position it through the parent layout.",
95
+ "props": [
96
+ "size"
97
+ ]
98
+ }
99
+ ],
100
+ "customProperties": []
101
+ }
82
102
  },
83
103
  "api": {
84
104
  "props": {
@@ -49,7 +49,23 @@
49
49
  "label": "Prose distribution decision",
50
50
  "path": "docs/prose-foundation.md"
51
51
  }
52
- ]
52
+ ],
53
+ "styling": {
54
+ "rationale": "The public prose stylesheet intentionally owns typography and exposes consumer customization.",
55
+ "protected": [],
56
+ "customProperties": [
57
+ {
58
+ "name": "--ds-prose-text-max-inline-size",
59
+ "source": "src/wc/styles/prose.css",
60
+ "reason": "Application-owned readable prose measure."
61
+ },
62
+ {
63
+ "name": "--ds-code-font-variant-ligatures",
64
+ "source": "src/wc/styles/prose.css",
65
+ "reason": "Application-owned code ligature preference."
66
+ }
67
+ ]
68
+ }
53
69
  },
54
70
  "api": {
55
71
  "props": {
@@ -106,7 +106,32 @@
106
106
  "label": "Framework integration",
107
107
  "path": "docs/framework-integration.md"
108
108
  }
109
- ]
109
+ ],
110
+ "styling": {
111
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
112
+ "protected": [
113
+ {
114
+ "properties": [
115
+ "background",
116
+ "border",
117
+ "box-shadow",
118
+ "color"
119
+ ],
120
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
121
+ "props": [
122
+ "size"
123
+ ]
124
+ },
125
+ {
126
+ "properties": [
127
+ "border-radius"
128
+ ],
129
+ "reason": "The component owns its surface radius; retain the shared recipe and customize an outer wrapper when needed.",
130
+ "props": []
131
+ }
132
+ ],
133
+ "customProperties": []
134
+ }
110
135
  },
111
136
  "api": {
112
137
  "props": {
@@ -47,7 +47,12 @@
47
47
  "label": "Message metadata composition",
48
48
  "path": "src/wc/components/Message/Message.stories.ts"
49
49
  }
50
- ]
50
+ ],
51
+ "styling": {
52
+ "rationale": "The application may position the action group; child buttons own their appearance.",
53
+ "protected": [],
54
+ "customProperties": []
55
+ }
51
56
  },
52
57
  "api": {
53
58
  "props": {
@@ -45,7 +45,32 @@
45
45
  "label": "Storybook examples",
46
46
  "path": "src/wc/components/MessageScroller/MessageScroller.stories.ts"
47
47
  }
48
- ]
48
+ ],
49
+ "styling": {
50
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
51
+ "protected": [
52
+ {
53
+ "properties": [
54
+ "background",
55
+ "border",
56
+ "box-shadow",
57
+ "color"
58
+ ],
59
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
60
+ "props": [
61
+ "variant"
62
+ ]
63
+ },
64
+ {
65
+ "properties": [
66
+ "border-radius"
67
+ ],
68
+ "reason": "The component owns its surface radius; retain the shared recipe and customize an outer wrapper when needed.",
69
+ "props": []
70
+ }
71
+ ],
72
+ "customProperties": []
73
+ }
49
74
  },
50
75
  "api": {
51
76
  "props": {
@@ -55,7 +55,30 @@
55
55
  "label": "Scroller overlay composition",
56
56
  "path": "src/wc/components/MessageScroller/MessageScroller.stories.ts"
57
57
  }
58
- ]
58
+ ],
59
+ "styling": {
60
+ "rationale": "The component owns the protected recipes below; unspecified host properties and application layout remain customizable.",
61
+ "protected": [
62
+ {
63
+ "properties": [
64
+ "background",
65
+ "border",
66
+ "box-shadow",
67
+ "color"
68
+ ],
69
+ "reason": "The component owns its surface and interaction treatment; use its semantic presentation props or compose an application-owned surrounding surface.",
70
+ "props": []
71
+ },
72
+ {
73
+ "properties": [
74
+ "border-radius"
75
+ ],
76
+ "reason": "The component owns its surface radius; retain the shared recipe and customize an outer wrapper when needed.",
77
+ "props": []
78
+ }
79
+ ],
80
+ "customProperties": []
81
+ }
59
82
  },
60
83
  "api": {
61
84
  "props": {