{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select",
  "title": "Select",
  "description": "Form-associated select for one value or several independent values through the multiple mode, with listbox semantics for ordinary choices, grid semantics for choices with contextual actions, local search, grouped sections, loading, clearing, and surface-aware trigger chrome.",
  "type": "registry:ui",
  "meta": {
    "library": "@ds-mo/ui",
    "distribution": "npm",
    "storybook": "https://zainadeel.github.io/compomo/?path=/story/select",
    "source": "src/wc/components/Select/Select.tsx",
    "intentStatus": "complete",
    "intent": {
      "audience": "general",
      "status": "stable",
      "summary": "Form-associated select for one value or several independent values through the multiple mode, with listbox semantics for ordinary choices, grid semantics for choices with contextual actions, local search, grouped sections, loading, clearing, and surface-aware trigger chrome.",
      "useWhen": [
        "The user must choose one value from a finite set.",
        "The user may choose several independent values when multiple is enabled.",
        "The option set benefits from local search, subtext, prefix icons, grouping, or disabled choices.",
        "The choice set has more than roughly 20 options, comes from a user-managed listing, or represents a large domain such as addresses where search materially improves retrieval.",
        "The selected value must participate in native, Angular, React, or Vue form state."
      ],
      "avoidWhen": [
        "The rows perform commands rather than choosing a form value.",
        "The choices form a hierarchy with expandable parents."
      ],
      "alternatives": [
        {
          "when": "The popup contains commands or state-changing actions.",
          "component": "component:ds-menu",
          "reason": "Menu uses action-menu roles and command-focused keyboard behavior."
        },
        {
          "when": "A small set of choices should remain visible.",
          "component": "component:ds-radio",
          "reason": "Prefer Radio for roughly two to five mutually exclusive choices when the available space allows all choices to remain visible."
        }
      ],
      "commonlyComposedWith": [
        "component:ds-text",
        "component:ds-icon",
        "component:ds-empty-state",
        "component:ds-loader"
      ],
      "accessibility": [
        "In forms, provide a persistent visible external field label; placeholder text is guidance and must not be the field's only label. In a compact filter bar, the trigger's visible text may supply the accessible name.",
        "The trigger uses combobox semantics. Ordinary choices use a listbox with options; when any choice has a contextual action, the popup promotes to a grid with selectable rows, a choice cell, and an action cell. Do not replace either popup with menu roles.",
        "Multiple mode adds aria-multiselectable, exposes independent aria-selected state, and uses presentation-only checkbox indicators while each option remains the sole interaction target.",
        "Disabled options remain visible, expose aria-disabled, and are skipped by keyboard navigation and typeahead.",
        "Keyboard navigation keeps the aria-activedescendant option scrolled into the visible listbox viewport.",
        "Selected options rely on active fill and foreground treatment without a redundant trailing check icon.",
        "A contextual option action is a sibling gridcell rather than an interactive descendant of the selectable choice cell. Pointer hover, focus, ArrowRight, and Shift+F10 make the ellipsis action reachable without changing the selected value.",
        "Local search moves focus into the search field while preserving the active option through aria-activedescendant.",
        "Use errorMessage with error so the trigger is described by the rendered alert."
      ],
      "states": [
        "The multiple property changes cardinality without changing the component family: scalar mode selects and closes, while multiple mode toggles array values and keeps the popup open.",
        "Every size applies one complete control-density recipe across trigger height, inline padding, icon zones, text-container inset, gap, and radius; error content follows the shared 4px field flow.",
        "Value is controlled through the mutable value property as a string in scalar mode or a string array in multiple mode; dsChange reports the matching shape.",
        "Multiple mode keeps the persistent placeholder label visible, appends the selected count, and submits one repeated form entry per selected value.",
        "Required Selects begin without an automatic selection and require the user to make an explicit choice; do not preselect the first option merely to satisfy validity.",
        "When allowClear is enabled, the clear affordance appears only after a value exists. Clearing is valid for optional and required fields; a required field becomes invalid until the user selects again.",
        "Clearing emits dsChange with an empty string or empty array according to mode plus dsClear, keeps the popup open, and restores useful focus.",
        "Loading keeps the trigger visible, replaces its prefix icon, marks the control busy, and makes selection interaction temporarily unavailable without presenting the control as inactive. When the popup is open, its single loader is centered vertically and horizontally within the loading region.",
        "The trigger label stays left-aligned at every width. The default size is md in both forms and filter bars; smaller sizes require an explicit density need.",
        "In scalar mode, triggerLabel may provide compact trigger-only copy while the popup continues to show the selected option's full label. Set triggerLabelPlaceholder when that copy represents the default or browse state and should retain secondary placeholder emphasis despite a real selected value. Use dot for a supplemental changed or attention state without appending status prose to the label.",
        "Use option icons only when they help users differentiate choices. Icons render only when every option in the complete list provides one; mixed icon data falls back to the text-only row layout.",
        "Create sections only for categories that are meaningful to customers, not merely to break a long list into arbitrary chunks.",
        "When any option supplies subtext, every row uses the same subtext structure and missing values render an em dash. Subtext should disambiguate or add decision-helpful information.",
        "Option labels and the selected trigger label stay on one line and truncate when constrained. Option subtext may wrap to at most two lines.",
        "Use an option action only for a contextual menu about that user-managed option. The fixed ellipsis action appears on hover, keyboard activity, focus, and touch; expanded keeps it visible while its controlled menu is open. Activating it emits dsOptionAction without selecting the option or closing the Select popup.",
        "Use option subtextActions for a compact set of immediate actions that belong to one option. They render beneath only that option's label using the standard supporting-text inset and rhythm, use action or negative semantic color, and emit dsOptionSubtextAction without selecting the option or closing the popup. Unlike descriptive subtext, they do not reserve a blank supporting line on neighboring options.",
        "The optional prefix icon and trailing indicator always use the parent surface's secondary foreground color, including when a selected value promotes the label to primary. When collapseLabel compact icon-only chrome is active, a selected value promotes the prefix icon to primary instead. The default down indicator communicates disclosure; up-down is reserved for compact value steppers such as pagination page size.",
        "While the popup is open, the trigger holds the pressed interaction wash without changing its resting foreground or selected-value treatment.",
        "The shared popup search control follows the Select density, using the same text, icon, and internal spacing as a borderless trigger. Its container uses menu-item padding on every side and owns a 1px bottom divider that becomes a 1.5px bold-brand stroke while search has focus. Search placeholder text is secondary at rest and quaternary while focused because it becomes decorative once the editable field is active.",
        "When search contains text, the shared control shows a rounded icon-only ButtonUnfilled with the CrossCircle icon and no border, sized to remain inset within the current row density. It clears the local term, stays hidden for an empty term, and returns focus to search.",
        "The shared clear footer mirrors the owning density: menu-item padding surrounds a borderless density row, and the Clear label uses the same row and label insets as option text. The clear action never wraps. In multiple mode, the selected summary stays on one line and collapses to its numeric count when the full localized phrase would compete with the clear action. The outer footer owns a 1px top divider.",
        "Use footerActionLabel for one product-owned text action beneath the option list. It replaces the clear affordance, stays aligned with option labels, uses the shared brand text-action treatment with a matching brand underline on hover, emits dsFooterAction, and closes the popup when activated.",
        "An empty filtered list composes the message-only body variant of EmptyState. Keep the default neutral no-results message unless localized product guidance makes the outcome clearer.",
        "Enable searchable for more than roughly 20 options, user-managed listings, large domains such as addresses, or sets users cannot efficiently scan. Local matching includes option labels, subtext, and section headings; the owner resolves remote data before assigning options.",
        "Validation timing belongs to the form owner and may be immediate or deferred until blur or submit; the Select exposes validity and error presentation without imposing that policy.",
        "The error trigger uses a 1.5px bold-negative inset stroke so invalid state remains distinguishable from the normal 1px border without changing control geometry. Set hasBorder to false for chrome where the trigger must remain borderless at rest, when selected or focused, and when invalid; error semantics and supporting copy remain available.",
        "An initially empty option set is not an expected Select use case. The no-results state describes a search with no matches, not a source with no choices.",
        "Use options for a flat list or sections for grouped choices; when sections are present they take precedence.",
        "The background prop describes the actual parent surface and uses the same surface set as ButtonUnfilled."
      ],
      "responsiveBehavior": [
        "Use width fill for form fields and width hug for compact filter bars. Override those defaults only when the owning layout has a clear constraint.",
        "When collapseLabel is set inside a named ds-table container narrower than 900px, the trigger omits its label and chevron and becomes icon-only. A selected value promotes the prefix icon to primary.",
        "The popup matches the trigger minimum width, may grow for content, and clamps to the viewport.",
        "Keep popupAlign at start for ordinary fields and filters. Use end when a control sits against a right layout edge and the popup should grow inward.",
        "Long option collections scroll inside the popup; virtualization and remote search are not part of this component."
      ],
      "frameworkCaveats": {
        "customElements": [
          "Assign options, sections, and multiple-mode array values through JavaScript properties because arrays and objects cannot be represented safely as attributes."
        ],
        "react": [
          "Pass options or sections as wrapper props, set multiple for array values, and handle onDsChange, onDsClear, onDsOpenChange, onDsFooterAction, onDsOptionAction, and onDsOptionSubtextAction."
        ],
        "vue": [
          "Use DsSelect with v-model for the selected value, bind options or sections as properties, and listen for dsChange, dsClear, dsOpenChange, dsFooterAction, dsOptionAction, and dsOptionSubtextAction."
        ],
        "angular": [
          "Import DsSelect and SelectValueAccessor when integrating with Angular Forms; bind options and sections as properties."
        ]
      },
      "references": [
        {
          "label": "Storybook examples",
          "path": "src/wc/components/Select/Select.stories.ts"
        },
        {
          "label": "Selection patterns",
          "path": "src/docs/SelectionPatterns.mdx"
        }
      ]
    },
    "api": {
      "props": {
        "activeFill": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "active-fill",
          "default": "true",
          "required": false,
          "mutable": false,
          "description": "Show the selected interaction fill when a valid value exists."
        },
        "allowClear": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "allow-clear",
          "default": "true",
          "required": false,
          "mutable": false,
          "description": "Show the clear footer action when a value exists."
        },
        "ariaDescribedby": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "aria-describedby",
          "required": false,
          "mutable": false,
          "description": "Additional IDs that describe the combobox."
        },
        "ariaLabel": {
          "type": "string | null",
          "resolvedType": "null | string",
          "attribute": "aria-label",
          "default": "null",
          "required": false,
          "mutable": false,
          "description": "Direct accessible name when no external label is available."
        },
        "ariaLabelledby": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "aria-labelledby",
          "required": false,
          "mutable": false,
          "description": "IDs of elements that label the combobox."
        },
        "background": {
          "type": "SelectBackground | undefined",
          "resolvedType": "\"always-dark\" | \"bold\" | \"faint\" | \"inverted\" | \"media\" | \"medium\" | \"strong\" | \"translucent\" | undefined",
          "attribute": "background",
          "required": false,
          "mutable": false,
          "description": "Actual parent surface context; omit on primary and secondary surfaces."
        },
        "clearLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "clear-label",
          "default": "'Clear'",
          "required": false,
          "mutable": false,
          "description": "Localized clear action label."
        },
        "collapseLabel": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "collapse-label",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Opt into table-caption icon-only chrome below 900px. The trigger omits its\nvisible label and chevron; keep an accessible name via aria-label."
        },
        "disabled": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "disabled",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Native disabled state."
        },
        "dot": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "dot",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Show a supplemental notification dot beside the trigger label."
        },
        "error": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "error",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Show invalid visual state."
        },
        "errorMessage": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "error-message",
          "required": false,
          "mutable": false,
          "description": "Error text rendered below the trigger when error is true."
        },
        "footerActionLabel": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "footer-action-label",
          "required": false,
          "mutable": false,
          "description": "Optional text action shown in the popup footer instead of the clear action."
        },
        "hasBorder": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "has-border",
          "default": "true",
          "required": false,
          "mutable": false,
          "description": "Show the surface-aware inset border, including focused and invalid strokes."
        },
        "icon": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "icon",
          "required": false,
          "mutable": false,
          "description": "Optional trigger prefix icon name."
        },
        "indicator": {
          "type": "SelectIndicator",
          "resolvedType": "\"down\" | \"up-down\"",
          "attribute": "indicator",
          "default": "'down'",
          "required": false,
          "mutable": false,
          "description": "Trailing choice indicator. Use up-down for compact value steppers such as page size."
        },
        "inputId": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "input-id",
          "required": false,
          "mutable": false,
          "description": "ID applied to the internal combobox trigger for external labels."
        },
        "isInactive": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "is-inactive",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Shared inactive treatment; removes interaction and form submission."
        },
        "isLoading": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "is-loading",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Replace the prefix with a loader and disable option interaction."
        },
        "loadingLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "loading-label",
          "default": "'Loading'",
          "required": false,
          "mutable": false,
          "description": "Accessible loading status label."
        },
        "multiple": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "multiple",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Enable independent multi-value selection while keeping the popup open."
        },
        "name": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "name",
          "required": false,
          "mutable": false,
          "description": "Native form field name."
        },
        "noResultsText": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "no-results-text",
          "default": "'No results found'",
          "required": false,
          "mutable": false,
          "description": "Localized empty-filter result text."
        },
        "open": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "open",
          "default": "false",
          "required": false,
          "mutable": true,
          "description": "Controlled popup visibility."
        },
        "options": {
          "type": "SelectOption[]",
          "resolvedType": "ChoiceOption[]",
          "default": "[]",
          "required": false,
          "mutable": false,
          "description": "Flat choices. Assign arrays through the JavaScript property."
        },
        "placeholder": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "placeholder",
          "default": "'Select'",
          "required": false,
          "mutable": false,
          "description": "Trigger text shown when no valid value is selected."
        },
        "popupAlign": {
          "type": "SelectPopupAlign",
          "resolvedType": "\"end\" | \"start\"",
          "attribute": "popup-align",
          "default": "'start'",
          "required": false,
          "mutable": false,
          "description": "Align the popup's choice edge to the trigger start or end edge."
        },
        "required": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "required",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Require one valid selected value."
        },
        "requiredMessage": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "required-message",
          "default": "'This field is required.'",
          "required": false,
          "mutable": false,
          "description": "Validation message used when required is missing."
        },
        "searchPlaceholder": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "search-placeholder",
          "default": "'Search'",
          "required": false,
          "mutable": false,
          "description": "Localized search-field placeholder and accessible name."
        },
        "searchable": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "searchable",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Show immediate local filtering over option labels, subtext, and section headings."
        },
        "sections": {
          "type": "SelectSection[]",
          "resolvedType": "ChoiceSection[]",
          "default": "[]",
          "required": false,
          "mutable": false,
          "description": "Grouped choices; takes precedence over options. Assign through JavaScript."
        },
        "selectedLabel": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "selected-label",
          "default": "'selected'",
          "required": false,
          "mutable": false,
          "description": "Localized noun displayed after the selected count in multiple mode."
        },
        "size": {
          "type": "SelectSize",
          "resolvedType": "\"lg\" | \"md\" | \"sm\" | \"xs\"",
          "attribute": "size",
          "default": "'md'",
          "required": false,
          "mutable": false,
          "description": "Control density."
        },
        "triggerLabel": {
          "type": "string | undefined",
          "resolvedType": "string | undefined",
          "attribute": "trigger-label",
          "required": false,
          "mutable": false,
          "description": "Optional scalar-mode trigger text that does not change the selected option label."
        },
        "triggerLabelPlaceholder": {
          "type": "boolean",
          "resolvedType": "boolean",
          "attribute": "trigger-label-placeholder",
          "default": "false",
          "required": false,
          "mutable": false,
          "description": "Present triggerLabel with placeholder emphasis while preserving the selected value."
        },
        "value": {
          "type": "SelectValue",
          "resolvedType": "string | string[]",
          "attribute": "value",
          "default": "''",
          "required": false,
          "mutable": true,
          "description": "Selected scalar or array value according to `multiple`."
        },
        "width": {
          "type": "SelectWidth",
          "resolvedType": "\"fill\" | \"hug\"",
          "attribute": "width",
          "default": "'hug'",
          "required": false,
          "mutable": false,
          "description": "Width fit — hug content (default) or fill the parent."
        }
      },
      "events": [
        {
          "name": "dsChange",
          "detail": "SelectValue",
          "bubbles": true,
          "cancelable": true,
          "composed": true,
          "description": "Emitted after selection or clearing with the next scalar or array value."
        },
        {
          "name": "dsClear",
          "detail": "void",
          "bubbles": true,
          "cancelable": true,
          "composed": true,
          "description": "Emitted after the footer clear action."
        },
        {
          "name": "dsFooterAction",
          "detail": "void",
          "bubbles": true,
          "cancelable": true,
          "composed": true,
          "description": "Emitted when the optional popup footer action is activated."
        },
        {
          "name": "dsOpenChange",
          "detail": "boolean",
          "bubbles": true,
          "cancelable": true,
          "composed": true,
          "description": "Emitted whenever popup visibility changes."
        },
        {
          "name": "dsOptionAction",
          "detail": "SelectOptionActionDetail",
          "bubbles": true,
          "cancelable": true,
          "composed": true,
          "description": "Emitted when an option's contextual ellipsis action is activated."
        },
        {
          "name": "dsOptionSubtextAction",
          "detail": "SelectOptionSubtextActionDetail",
          "bubbles": true,
          "cancelable": true,
          "composed": true,
          "description": "Emitted when an option's supporting text action is activated."
        }
      ],
      "methods": [
        {
          "name": "setFocus",
          "signature": "setFocus() => Promise<void>"
        }
      ],
      "slots": []
    },
    "props": {
      "activeFill": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "active-fill",
        "default": "true",
        "required": false,
        "mutable": false,
        "description": "Show the selected interaction fill when a valid value exists."
      },
      "allowClear": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "allow-clear",
        "default": "true",
        "required": false,
        "mutable": false,
        "description": "Show the clear footer action when a value exists."
      },
      "ariaDescribedby": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "aria-describedby",
        "required": false,
        "mutable": false,
        "description": "Additional IDs that describe the combobox."
      },
      "ariaLabel": {
        "type": "string | null",
        "resolvedType": "null | string",
        "attribute": "aria-label",
        "default": "null",
        "required": false,
        "mutable": false,
        "description": "Direct accessible name when no external label is available."
      },
      "ariaLabelledby": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "aria-labelledby",
        "required": false,
        "mutable": false,
        "description": "IDs of elements that label the combobox."
      },
      "background": {
        "type": "SelectBackground | undefined",
        "resolvedType": "\"always-dark\" | \"bold\" | \"faint\" | \"inverted\" | \"media\" | \"medium\" | \"strong\" | \"translucent\" | undefined",
        "attribute": "background",
        "required": false,
        "mutable": false,
        "description": "Actual parent surface context; omit on primary and secondary surfaces."
      },
      "clearLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "clear-label",
        "default": "'Clear'",
        "required": false,
        "mutable": false,
        "description": "Localized clear action label."
      },
      "collapseLabel": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "collapse-label",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Opt into table-caption icon-only chrome below 900px. The trigger omits its\nvisible label and chevron; keep an accessible name via aria-label."
      },
      "disabled": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "disabled",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Native disabled state."
      },
      "dot": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "dot",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Show a supplemental notification dot beside the trigger label."
      },
      "error": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "error",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Show invalid visual state."
      },
      "errorMessage": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "error-message",
        "required": false,
        "mutable": false,
        "description": "Error text rendered below the trigger when error is true."
      },
      "footerActionLabel": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "footer-action-label",
        "required": false,
        "mutable": false,
        "description": "Optional text action shown in the popup footer instead of the clear action."
      },
      "hasBorder": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "has-border",
        "default": "true",
        "required": false,
        "mutable": false,
        "description": "Show the surface-aware inset border, including focused and invalid strokes."
      },
      "icon": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "icon",
        "required": false,
        "mutable": false,
        "description": "Optional trigger prefix icon name."
      },
      "indicator": {
        "type": "SelectIndicator",
        "resolvedType": "\"down\" | \"up-down\"",
        "attribute": "indicator",
        "default": "'down'",
        "required": false,
        "mutable": false,
        "description": "Trailing choice indicator. Use up-down for compact value steppers such as page size."
      },
      "inputId": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "input-id",
        "required": false,
        "mutable": false,
        "description": "ID applied to the internal combobox trigger for external labels."
      },
      "isInactive": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "is-inactive",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Shared inactive treatment; removes interaction and form submission."
      },
      "isLoading": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "is-loading",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Replace the prefix with a loader and disable option interaction."
      },
      "loadingLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "loading-label",
        "default": "'Loading'",
        "required": false,
        "mutable": false,
        "description": "Accessible loading status label."
      },
      "multiple": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "multiple",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Enable independent multi-value selection while keeping the popup open."
      },
      "name": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "name",
        "required": false,
        "mutable": false,
        "description": "Native form field name."
      },
      "noResultsText": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "no-results-text",
        "default": "'No results found'",
        "required": false,
        "mutable": false,
        "description": "Localized empty-filter result text."
      },
      "open": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "open",
        "default": "false",
        "required": false,
        "mutable": true,
        "description": "Controlled popup visibility."
      },
      "options": {
        "type": "SelectOption[]",
        "resolvedType": "ChoiceOption[]",
        "default": "[]",
        "required": false,
        "mutable": false,
        "description": "Flat choices. Assign arrays through the JavaScript property."
      },
      "placeholder": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "placeholder",
        "default": "'Select'",
        "required": false,
        "mutable": false,
        "description": "Trigger text shown when no valid value is selected."
      },
      "popupAlign": {
        "type": "SelectPopupAlign",
        "resolvedType": "\"end\" | \"start\"",
        "attribute": "popup-align",
        "default": "'start'",
        "required": false,
        "mutable": false,
        "description": "Align the popup's choice edge to the trigger start or end edge."
      },
      "required": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "required",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Require one valid selected value."
      },
      "requiredMessage": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "required-message",
        "default": "'This field is required.'",
        "required": false,
        "mutable": false,
        "description": "Validation message used when required is missing."
      },
      "searchPlaceholder": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "search-placeholder",
        "default": "'Search'",
        "required": false,
        "mutable": false,
        "description": "Localized search-field placeholder and accessible name."
      },
      "searchable": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "searchable",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Show immediate local filtering over option labels, subtext, and section headings."
      },
      "sections": {
        "type": "SelectSection[]",
        "resolvedType": "ChoiceSection[]",
        "default": "[]",
        "required": false,
        "mutable": false,
        "description": "Grouped choices; takes precedence over options. Assign through JavaScript."
      },
      "selectedLabel": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "selected-label",
        "default": "'selected'",
        "required": false,
        "mutable": false,
        "description": "Localized noun displayed after the selected count in multiple mode."
      },
      "size": {
        "type": "SelectSize",
        "resolvedType": "\"lg\" | \"md\" | \"sm\" | \"xs\"",
        "attribute": "size",
        "default": "'md'",
        "required": false,
        "mutable": false,
        "description": "Control density."
      },
      "triggerLabel": {
        "type": "string | undefined",
        "resolvedType": "string | undefined",
        "attribute": "trigger-label",
        "required": false,
        "mutable": false,
        "description": "Optional scalar-mode trigger text that does not change the selected option label."
      },
      "triggerLabelPlaceholder": {
        "type": "boolean",
        "resolvedType": "boolean",
        "attribute": "trigger-label-placeholder",
        "default": "false",
        "required": false,
        "mutable": false,
        "description": "Present triggerLabel with placeholder emphasis while preserving the selected value."
      },
      "value": {
        "type": "SelectValue",
        "resolvedType": "string | string[]",
        "attribute": "value",
        "default": "''",
        "required": false,
        "mutable": true,
        "description": "Selected scalar or array value according to `multiple`."
      },
      "width": {
        "type": "SelectWidth",
        "resolvedType": "\"fill\" | \"hug\"",
        "attribute": "width",
        "default": "'hug'",
        "required": false,
        "mutable": false,
        "description": "Width fit — hug content (default) or fill the parent."
      }
    },
    "events": [
      {
        "name": "dsChange",
        "detail": "SelectValue",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "description": "Emitted after selection or clearing with the next scalar or array value."
      },
      {
        "name": "dsClear",
        "detail": "void",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "description": "Emitted after the footer clear action."
      },
      {
        "name": "dsFooterAction",
        "detail": "void",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "description": "Emitted when the optional popup footer action is activated."
      },
      {
        "name": "dsOpenChange",
        "detail": "boolean",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "description": "Emitted whenever popup visibility changes."
      },
      {
        "name": "dsOptionAction",
        "detail": "SelectOptionActionDetail",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "description": "Emitted when an option's contextual ellipsis action is activated."
      },
      {
        "name": "dsOptionSubtextAction",
        "detail": "SelectOptionSubtextActionDetail",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "description": "Emitted when an option's supporting text action is activated."
      }
    ],
    "methods": [
      {
        "name": "setFocus",
        "signature": "setFocus() => Promise<void>"
      }
    ],
    "slots": [],
    "exports": {
      "customElement": "ds-select",
      "react": "DsSelect",
      "vue": "DsSelect",
      "angular": "DsSelect"
    },
    "consumption": {
      "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
      "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
      "customElements": {
        "import": "import '@ds-mo/ui/dist/components/ds-select.js';",
        "example": "<ds-select></ds-select>"
      },
      "react": {
        "import": "import { DsSelect } from '@ds-mo/ui/react';",
        "example": "<DsSelect />"
      },
      "vue": {
        "import": "import { DsSelect } from '@ds-mo/ui/vue';",
        "example": "<DsSelect />"
      },
      "angular": {
        "import": "import { DsSelect } from '@ds-mo/ui/angular/ds-select';",
        "example": "<ds-select></ds-select>"
      },
      "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: options, sections.",
      "peerDependencies": {
        "required": [
          "@ds-mo/tokens ^6.5.0",
          "@ds-mo/icons ^7.0.0"
        ],
        "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
      }
    }
  },
  "dependencies": [
    "@ds-mo/ui",
    "@ds-mo/tokens",
    "@ds-mo/icons"
  ],
  "registryDependencies": [
    "badge",
    "button-unfilled",
    "checkbox",
    "empty-state",
    "icon",
    "loader",
    "text",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/wc/components/Select/Select.css",
      "content": "@import '../../utils/choice-list.css';\n@import '../../utils/control-parts.css';\n@import '../../utils/control-width.css';\n@import '../../utils/field-stack.css';\n@import '../../utils/search-control.css';\n@import '../../utils/select-trigger.css';\n@import '../../utils/table-caption-control.css';\n@import '../../utils/typography.css';\n@import '../../styles/control-elevation.css';\n\n.wrapper--error {\n  --ds-interaction-border-width: var(--dimension-stroke-width-018);\n  --ds-interaction-border-color: var(--color-border-bold-negative);\n}\n\n.select-popup {\n  color: var(--color-foreground-secondary);\n}\n\n.select-option[aria-selected='true'] .ds-choice-item__icon {\n  color: var(--color-foreground-primary);\n}\n\n.select-option-row {\n  position: relative;\n  isolation: isolate;\n}\n\n.select-option-row__action {\n  position: absolute;\n  top: 50%;\n  right: var(--dimension-space-050);\n  z-index: var(--dimension-z-index-floating);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: var(--dimension-radius-half);\n  background: var(--color-background-transparent);\n  -webkit-backdrop-filter: blur(var(--effect-blur-sm));\n  backdrop-filter: blur(var(--effect-blur-sm));\n  opacity: 0;\n  pointer-events: none;\n  transform: translateY(-50%);\n}\n\n.select-option-row:has(:focus-visible) .select-option-row__action,\n.select-option-row--action-open .select-option-row__action,\n.select-option-row--keyboard-active .select-option-row__action {\n  opacity: 1;\n  pointer-events: auto;\n}\n\n.select-option-row--action-open .select-option::after {\n  background: var(--ds-interaction-hover);\n}\n\n.select-option__subtext-actions {\n  display: flex;\n  align-items: center;\n  gap: var(--dimension-space-050);\n  min-inline-size: 0;\n  color: var(--color-foreground-secondary);\n}\n\n.select-option__subtext-action {\n  appearance: none;\n  display: inline-flex;\n  align-items: center;\n  padding: 0;\n  border: 0;\n  border-radius: var(--dimension-radius-025);\n  background: transparent;\n  cursor: pointer;\n}\n\n.select-option__subtext-action--negative {\n  --ds-text-action-foreground: var(--color-foreground-bold-negative);\n}\n\n.select-option__subtext-action-separator {\n  color: var(--color-foreground-tertiary);\n  pointer-events: none;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .select-option-row:hover .select-option-row__action {\n    opacity: 1;\n    pointer-events: auto;\n  }\n\n  .select-option-row:hover .select-option::after {\n    background: var(--ds-interaction-hover);\n  }\n}\n\n@media (hover: none) {\n  .select-option-row__action {\n    opacity: 1;\n    pointer-events: auto;\n  }\n\n  .select-option-row--has-action .ds-choice-item__content {\n    padding-inline-end: var(--dimension-size-400);\n  }\n}\n\n.error-text {\n  width: 100%;\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/wc/components/Select/Select.tsx",
      "content": "import {\n  AttachInternals,\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  controlWidthClass,\n  CONTROL_SUPPORTING_TEXT_VARIANT,\n  CONTROL_TEXT_VARIANT,\n  DEFAULT_REQUIRED_MESSAGE,\n  restoreStringArrayFormState,\n  restoreStringFormState,\n  setFormControlValue,\n  setRepeatedFormControlValue,\n  setRequiredValidity,\n  type ControlWidth,\n} from '../../utils';\nimport {\n  ChoiceActionFooter,\n  ChoiceFooter,\n  ChoiceOptionRow,\n  ChoiceSearch,\n} from '../../utils/choice-list-parts';\nimport {\n  choiceBackgroundClassMap,\n  choiceListUsesIcons,\n  choiceListUsesSubtext,\n  enabledChoiceIndexes,\n  filterChoiceSections,\n  flattenChoiceSections,\n  resolveChoiceSections,\n  type ChoiceBackground,\n  type ChoiceOption,\n  type ChoiceSection,\n} from '../../utils/choice-list';\nimport { SelectController } from '../../utils/select-controller';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\n\nexport type SelectOption = ChoiceOption;\nexport type SelectSection = ChoiceSection;\nexport type SelectBackground = ChoiceBackground;\nexport type SelectSize = 'lg' | 'md' | 'sm' | 'xs';\nexport type SelectWidth = ControlWidth;\nexport type SelectPopupAlign = 'start' | 'end';\nexport type SelectValue = string | string[];\nexport type SelectIndicator = 'down' | 'up-down';\n\nexport interface SelectOptionActionDetail {\n  value: string;\n  anchorId: string;\n  originalEvent: MouseEvent;\n}\n\nexport interface SelectOptionSubtextActionDetail {\n  value: string;\n  actionValue: string;\n  originalEvent: MouseEvent;\n}\n\nconst ICON_SIZE: Record<SelectSize, 'lg' | 'md' | 'sm' | 'xs'> = {\n  lg: 'lg',\n  md: 'md',\n  sm: 'sm',\n  xs: 'xs',\n};\n\nconst OPTION_ACTION_SIZE: Record<SelectSize, 'md' | 'sm' | 'xs'> = {\n  lg: 'md',\n  md: 'sm',\n  sm: 'xs',\n  xs: 'xs',\n};\n\nlet selectId = 0;\n\n@Component({\n  tag: 'ds-select',\n  styleUrl: 'Select.css',\n  scoped: true,\n  formAssociated: true,\n})\nexport class Select {\n  @Element() el!: HTMLElement;\n  @AttachInternals() internals!: ElementInternals;\n\n  /** Flat choices. Assign arrays through the JavaScript property. */\n  @Prop() options: SelectOption[] = [];\n  /** Grouped choices; takes precedence over options. Assign through JavaScript. */\n  @Prop() sections: SelectSection[] = [];\n  /** Enable independent multi-value selection while keeping the popup open. */\n  @Prop({ reflect: true }) multiple: boolean = false;\n  /** Selected scalar or array value according to `multiple`. */\n  @Prop({ mutable: true }) value: SelectValue = '';\n  /** Controlled popup visibility. */\n  @Prop({ mutable: true, reflect: true }) open: boolean = false;\n  /** Native form field name. */\n  @Prop({ reflect: true }) name: string | undefined;\n  /** Native disabled state. */\n  @Prop({ reflect: true }) disabled: boolean = false;\n  /** Require one valid selected value. */\n  @Prop({ reflect: true }) required: boolean = false;\n  /** Validation message used when required is missing. */\n  @Prop() requiredMessage: string = DEFAULT_REQUIRED_MESSAGE;\n  /** Trigger text shown when no valid value is selected. */\n  @Prop() placeholder: string = 'Select';\n  /** Optional scalar-mode trigger text that does not change the selected option label. */\n  @Prop() triggerLabel: string | undefined;\n  /** Present triggerLabel with placeholder emphasis while preserving the selected value. */\n  @Prop() triggerLabelPlaceholder: boolean = false;\n  /** Show a supplemental notification dot beside the trigger label. */\n  @Prop() dot: boolean = false;\n  /** Control density. */\n  @Prop() size: SelectSize = 'md';\n  /** Width fit — hug content (default) or fill the parent. */\n  @Prop() width: SelectWidth = 'hug';\n  /** Align the popup's choice edge to the trigger start or end edge. */\n  @Prop() popupAlign: SelectPopupAlign = 'start';\n  /** Shared inactive treatment; removes interaction and form submission. */\n  @Prop() isInactive: boolean = false;\n  /** Replace the prefix with a loader and disable option interaction. */\n  @Prop() isLoading: boolean = false;\n  /** Show the selected interaction fill when a valid value exists. */\n  @Prop() activeFill: boolean = true;\n  /**\n   * Opt into table-caption icon-only chrome below 900px. The trigger omits its\n   * visible label and chevron; keep an accessible name via aria-label.\n   */\n  @Prop({ reflect: true }) collapseLabel: boolean = false;\n  /** Show the surface-aware inset border, including focused and invalid strokes. */\n  @Prop() hasBorder: boolean = true;\n  /** Optional trigger prefix icon name. */\n  @Prop() icon: string | undefined;\n  /** Trailing choice indicator. Use up-down for compact value steppers such as page size. */\n  @Prop() indicator: SelectIndicator = 'down';\n  /** Show the clear footer action when a value exists. */\n  @Prop() allowClear: boolean = true;\n  /** Localized clear action label. */\n  @Prop() clearLabel: string = 'Clear';\n  /** Localized noun displayed after the selected count in multiple mode. */\n  @Prop() selectedLabel: string = 'selected';\n  /** Optional text action shown in the popup footer instead of the clear action. */\n  @Prop() footerActionLabel: string | undefined;\n  /** Show immediate local filtering over option labels, subtext, and section headings. */\n  @Prop() searchable: boolean = false;\n  /** Localized search-field placeholder and accessible name. */\n  @Prop() searchPlaceholder: string = 'Search';\n  /** Localized empty-filter result text. */\n  @Prop() noResultsText: string = 'No results found';\n  /** Accessible loading status label. */\n  @Prop() loadingLabel: string = 'Loading';\n  /** Actual parent surface context; omit on primary and secondary surfaces. */\n  @Prop() background: SelectBackground | undefined;\n  /** Show invalid visual state. */\n  @Prop() error: boolean = false;\n  /** Error text rendered below the trigger when error is true. */\n  @Prop() errorMessage: string | undefined;\n  /** ID applied to the internal combobox trigger for external labels. */\n  @Prop() inputId: string | undefined;\n  /** Direct accessible name when no external label is available. */\n  @Prop({ attribute: 'aria-label' }) ariaLabel: string | null = null;\n  /** IDs of elements that label the combobox. */\n  @Prop({ attribute: 'aria-labelledby' }) ariaLabelledby: string | undefined;\n  /** Additional IDs that describe the combobox. */\n  @Prop({ attribute: 'aria-describedby' }) ariaDescribedby: string | undefined;\n\n  /** Emitted after selection or clearing with the next scalar or array value. */\n  @Event() dsChange!: EventEmitter<SelectValue>;\n  /** Emitted after the footer clear action. */\n  @Event() dsClear!: EventEmitter<void>;\n  /** Emitted whenever popup visibility changes. */\n  @Event() dsOpenChange!: EventEmitter<boolean>;\n  /** Emitted when the optional popup footer action is activated. */\n  @Event() dsFooterAction!: EventEmitter<void>;\n  /** Emitted when an option's contextual ellipsis action is activated. */\n  @Event() dsOptionAction!: EventEmitter<SelectOptionActionDetail>;\n  /** Emitted when an option's supporting text action is activated. */\n  @Event() dsOptionSubtextAction!: EventEmitter<SelectOptionSubtextActionDetail>;\n\n  @State() private activeIndex = -1;\n  @State() private searchTerm = '';\n  @State() private formDisabled = false;\n  @State() private position = { x: 0, y: 0 };\n  @State() private positionReady = false;\n  @State() private focusRingVisible = false;\n  @State() private compactFooterSummary = false;\n  @State() private captionCompact = false;\n\n  private readonly generatedId = `ds-select-${++selectId}`;\n  private readonly listboxId = `${this.generatedId}-listbox`;\n  private readonly errorId = `${this.generatedId}-error`;\n  private initialValue: SelectValue = '';\n  private readonly controller = this.createController();\n  private footerContentElement?: HTMLDivElement;\n  private footerSummaryMeasureElement?: HTMLElement;\n  private footerClearElement?: HTMLButtonElement;\n  private footerResizeObserver?: ResizeObserver;\n  private observedFooterContentElement?: HTMLDivElement;\n  private footerMeasurementFrame?: number;\n  private captionCompactDisconnect: (() => void) | undefined;\n  private hasLoaded = false;\n\n  private createController() {\n    // eslint-disable-next-line @typescript-eslint/no-this-alias -- adapter getters preserve reactive component ownership without inheritance.\n    const owner = this;\n    return new SelectController<SelectOption>({\n      get host() {\n        return owner.el;\n      },\n      generatedId: owner.generatedId,\n      get options() {\n        return owner.visibleOptions;\n      },\n      get searchable() {\n        return owner.searchable;\n      },\n      get isLoading() {\n        return owner.isLoading;\n      },\n      get isDisabled() {\n        return owner.isDisabled;\n      },\n      get preferredIndex() {\n        if (owner.multiple) return -1;\n        return owner.visibleOptions.findIndex(\n          option => option.value === owner.scalarValue && !option.isInactive\n        );\n      },\n      get popupAlign() {\n        return owner.popupAlign;\n      },\n      get open() {\n        return owner.open;\n      },\n      set open(value) {\n        owner.open = value;\n      },\n      get activeIndex() {\n        return owner.activeIndex;\n      },\n      set activeIndex(value) {\n        owner.activeIndex = value;\n      },\n      get searchTerm() {\n        return owner.searchTerm;\n      },\n      set searchTerm(value) {\n        owner.searchTerm = value;\n      },\n      get focusRingVisible() {\n        return owner.focusRingVisible;\n      },\n      set focusRingVisible(value) {\n        owner.focusRingVisible = value;\n      },\n      get position() {\n        return owner.position;\n      },\n      set position(value) {\n        owner.position = value;\n      },\n      get positionReady() {\n        return owner.positionReady;\n      },\n      set positionReady(value) {\n        owner.positionReady = value;\n      },\n      selectOption: option => owner.selectOption(option),\n    });\n  }\n\n  componentWillLoad() {\n    this.value = this.normalizeValueForMode(this.value);\n    this.initialValue = Array.isArray(this.value) ? [...this.value] : this.value;\n    this.syncFormValue();\n  }\n\n  componentDidLoad() {\n    this.hasLoaded = true;\n    this.controller.connect();\n    this.observeFooterContent();\n    this.syncCaptionCompactObserver();\n  }\n\n  connectedCallback() {\n    if (!this.hasLoaded) return;\n    this.controller.connect();\n    this.observeFooterContent();\n    this.syncCaptionCompactObserver();\n  }\n\n  componentDidRender() {\n    this.observeFooterContent();\n    this.scheduleFooterSummaryMeasurement();\n  }\n\n  disconnectedCallback() {\n    this.controller.disconnect();\n    this.footerResizeObserver?.disconnect();\n    this.observedFooterContentElement = undefined;\n    if (this.footerMeasurementFrame !== undefined) {\n      cancelAnimationFrame(this.footerMeasurementFrame);\n      this.footerMeasurementFrame = undefined;\n    }\n    this.disconnectCaptionCompactObserver();\n  }\n\n  @Watch('collapseLabel')\n  onCollapseLabelChange() {\n    this.syncCaptionCompactObserver();\n  }\n\n  @Watch('value')\n  @Watch('multiple')\n  @Watch('disabled')\n  @Watch('isInactive')\n  @Watch('required')\n  @Watch('name')\n  @Watch('options')\n  @Watch('sections')\n  syncFormValue() {\n    if (\n      (this.multiple && !Array.isArray(this.value)) ||\n      (!this.multiple && Array.isArray(this.value))\n    ) {\n      this.value = this.normalizeValueForMode(this.value);\n    }\n    const inactive = this.isDisabled;\n    if (this.multiple) {\n      const values = this.resolvedValues;\n      setRepeatedFormControlValue(this.internals, this.name, values, { inactive });\n    } else {\n      const value = this.hasSelection ? this.scalarValue : '';\n      setFormControlValue(this.internals, value, { inactive });\n    }\n    const missing = this.required && !inactive && !this.hasSelection;\n    setRequiredValidity(this.internals, missing, this.requiredMessage);\n    this.controller.optionsChanged();\n  }\n\n  @Watch('isLoading')\n  onLoadingChange() {\n    this.controller.loadingChanged();\n  }\n\n  @Watch('popupAlign')\n  onPopupAlignChange() {\n    this.controller.positionChanged();\n  }\n\n  @Watch('open')\n  onOpenChange(open: boolean) {\n    this.dsOpenChange.emit(open);\n    this.controller.openChanged(open);\n  }\n\n  @Watch('searchTerm')\n  onSearchTermChange() {\n    this.controller.searchChanged();\n  }\n\n  @Watch('activeIndex')\n  onActiveIndexChange() {\n    this.controller.activeIndexChanged();\n  }\n\n  formDisabledCallback(disabled: boolean) {\n    this.formDisabled = disabled;\n    this.syncFormValue();\n  }\n\n  formResetCallback() {\n    this.value = this.normalizeValueForMode(this.initialValue);\n    this.closePopup();\n  }\n\n  formStateRestoreCallback(state: string | File | FormData | null) {\n    if (!this.multiple) {\n      this.value = restoreStringFormState(state);\n      return;\n    }\n    this.value = restoreStringArrayFormState(state);\n  }\n\n  @Method()\n  async setFocus() {\n    this.controller.setFocus();\n  }\n\n  private get isDisabled(): boolean {\n    return this.disabled || this.isInactive || this.formDisabled;\n  }\n\n  private normalizeValueForMode(value: SelectValue): SelectValue {\n    if (this.multiple) {\n      return Array.isArray(value) ? [...value] : value ? [value] : [];\n    }\n    return Array.isArray(value) ? (value[0] ?? '') : value;\n  }\n\n  private get allSections(): SelectSection[] {\n    return resolveChoiceSections(this.options, this.sections);\n  }\n\n  private get allOptions(): SelectOption[] {\n    return flattenChoiceSections(this.allSections);\n  }\n\n  private get visibleSections(): SelectSection[] {\n    return this.searchable\n      ? filterChoiceSections(this.allSections, this.searchTerm)\n      : this.allSections;\n  }\n\n  private get visibleOptions(): SelectOption[] {\n    return flattenChoiceSections(this.visibleSections);\n  }\n\n  private get scalarValue(): string {\n    return typeof this.value === 'string' ? this.value : '';\n  }\n\n  private get resolvedValues(): string[] {\n    if (!this.multiple || !Array.isArray(this.value)) return [];\n    const valid = new Set(this.allOptions.map(option => option.value));\n    return [...new Set(this.value.filter(value => valid.has(value)))];\n  }\n\n  private get selectedOption(): SelectOption | undefined {\n    if (this.multiple) return undefined;\n    return this.allOptions.find(option => option.value === this.scalarValue);\n  }\n\n  private get hasSelection(): boolean {\n    return this.multiple ? this.resolvedValues.length > 0 : Boolean(this.selectedOption);\n  }\n\n  private get activeOptionId(): string | undefined {\n    return this.controller.activeOptionId;\n  }\n\n  private get popupRole(): 'listbox' | 'grid' {\n    return this.allOptions.some(option => Boolean(option.action)) ? 'grid' : 'listbox';\n  }\n\n  private observeFooterContent() {\n    const content =\n      this.open && this.multiple && this.hasSelection ? this.footerContentElement : undefined;\n    if (!content?.isConnected) {\n      this.footerResizeObserver?.disconnect();\n      this.observedFooterContentElement = undefined;\n      return;\n    }\n    if (this.observedFooterContentElement === content) return;\n\n    this.footerResizeObserver?.disconnect();\n    if (typeof ResizeObserver !== 'undefined') {\n      this.footerResizeObserver = new ResizeObserver(() => this.scheduleFooterSummaryMeasurement());\n      this.footerResizeObserver.observe(content);\n    }\n    this.observedFooterContentElement = content;\n  }\n\n  private scheduleFooterSummaryMeasurement() {\n    if (\n      !this.open ||\n      !this.multiple ||\n      !this.hasSelection ||\n      this.footerMeasurementFrame !== undefined\n    ) {\n      return;\n    }\n    this.footerMeasurementFrame = requestAnimationFrame(() => {\n      this.footerMeasurementFrame = undefined;\n      this.measureFooterSummary();\n    });\n  }\n\n  private measureFooterSummary() {\n    const content = this.footerContentElement;\n    const summary = this.footerSummaryMeasureElement;\n    const clear = this.footerClearElement;\n    if (!content?.isConnected || !summary?.isConnected || !clear?.isConnected) return;\n\n    const styles = getComputedStyle(content);\n    const innerWidth =\n      content.clientWidth -\n      Number.parseFloat(styles.paddingInlineStart || '0') -\n      Number.parseFloat(styles.paddingInlineEnd || '0');\n    const requiredWidth =\n      summary.getBoundingClientRect().width +\n      clear.getBoundingClientRect().width +\n      Number.parseFloat(styles.columnGap || '0');\n    const nextCompact = requiredWidth > innerWidth + 0.5;\n    if (nextCompact !== this.compactFooterSummary) {\n      this.compactFooterSummary = nextCompact;\n    }\n  }\n\n  private closePopup(restoreFocus = false) {\n    this.controller.closePopup(restoreFocus);\n  }\n\n  private openPopup(focusVisible: boolean, edge?: 'first' | 'last') {\n    this.controller.openPopup(focusVisible, edge);\n  }\n\n  private selectOption(option: SelectOption) {\n    if (option.isInactive || this.isLoading) return;\n    if (this.multiple) {\n      const current = this.resolvedValues;\n      const next = current.includes(option.value)\n        ? current.filter(value => value !== option.value)\n        : [...current, option.value];\n      this.value = next;\n      this.dsChange.emit([...next]);\n      return;\n    }\n    this.value = option.value;\n    this.dsChange.emit(option.value);\n    this.controller.closePopup(true);\n  }\n\n  private clearSelection = (event: Event) => {\n    event.preventDefault();\n    event.stopPropagation();\n    this.value = this.multiple ? [] : '';\n    this.dsChange.emit(this.multiple ? [] : '');\n    this.dsClear.emit();\n    if (!this.multiple) {\n      const enabled = enabledChoiceIndexes(this.visibleOptions);\n      this.activeIndex = enabled[0] ?? -1;\n    }\n    this.controller.focusSearchOrTrigger();\n  };\n\n  private activateFooterAction = (event: MouseEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n    this.dsFooterAction.emit();\n    this.closePopup();\n  };\n\n  private readonly handleListKeyDown = this.controller.handleListKeyDown;\n  private readonly handleTriggerKeyDown = this.controller.handleTriggerKeyDown;\n\n  private renderOption(\n    option: SelectOption,\n    index: number,\n    usesIcons: boolean,\n    usesSubtext: boolean\n  ) {\n    const selected = this.multiple\n      ? this.resolvedValues.includes(option.value)\n      : option.value === this.scalarValue;\n    const active = index === this.activeIndex;\n    const actionId = `${this.generatedId}-option-action-${index}`;\n    return (\n      <ChoiceOptionRow\n        size={this.size}\n        id={`${this.generatedId}-option-${index}`}\n        option={option}\n        selected={selected}\n        active={active}\n        focusRingVisible={this.focusRingVisible}\n        usesSubtext={usesSubtext}\n        actionOpen={option.action?.expanded}\n        popupRole={this.popupRole}\n        leading={\n          this.multiple ? (\n            <span\n              class=\"ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n              aria-hidden=\"true\"\n            >\n              <ds-checkbox\n                class=\"select-option__checkbox\"\n                label=\"\"\n                size={this.size}\n                checked={selected}\n                presentation\n              />\n            </span>\n          ) : usesIcons && option.icon ? (\n            <span\n              class=\"ds-choice-item__icon ds-control-icon-box ds-interaction-fill__content\"\n              aria-hidden=\"true\"\n            >\n              <ds-icon name={option.icon} size={this.size} color=\"inherit\" />\n            </span>\n          ) : undefined\n        }\n        supporting={\n          option.subtextActions?.length ? (\n            <span class=\"ds-choice-item__subtext ds-control-label-box select-option__subtext-actions\">\n              {option.subtextActions.map((action, actionIndex) => [\n                actionIndex > 0 ? (\n                  <ds-text\n                    class=\"select-option__subtext-action-separator\"\n                    as=\"span\"\n                    variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n                    color=\"inherit\"\n                    aria-hidden=\"true\"\n                  >\n                    ·\n                  </ds-text>\n                ) : null,\n                <button\n                  type=\"button\"\n                  class={{\n                    'select-option__subtext-action': true,\n                    'select-option__subtext-action--negative': action.tone === 'negative',\n                    'ds-text-action': true,\n                    'ds-focus-ring': true,\n                  }}\n                  onKeyDown={(event: KeyboardEvent) => event.stopPropagation()}\n                  onClick={(event: MouseEvent) => {\n                    event.preventDefault();\n                    event.stopPropagation();\n                    this.dsOptionSubtextAction.emit({\n                      value: option.value,\n                      actionValue: action.value,\n                      originalEvent: event,\n                    });\n                  }}\n                >\n                  <ds-text\n                    as=\"span\"\n                    variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n                    color=\"inherit\"\n                  >\n                    {action.label}\n                  </ds-text>\n                </button>,\n              ])}\n            </span>\n          ) : undefined\n        }\n        action={\n          option.action ? (\n            <ds-tooltip label={option.action.label} side=\"left\" size=\"sm\">\n              <ds-button-unfilled\n                id={actionId}\n                variant=\"icon\"\n                size={OPTION_ACTION_SIZE[this.size]}\n                icon=\"Ellipses\"\n                rounded\n                hasBorder={false}\n                activeFill={false}\n                ariaLabel={option.action.label}\n                haspopup=\"menu\"\n                controls={option.action.controls}\n                expanded={option.action.expanded}\n                onKeyDown={event => {\n                  event.stopPropagation();\n                  if (event.key === 'Escape' || event.key === 'ArrowLeft') {\n                    event.preventDefault();\n                    this.controller.focusSearchOrTrigger();\n                  }\n                }}\n                onDsClick={(event: CustomEvent<MouseEvent>) => {\n                  event.stopPropagation();\n                  this.dsOptionAction.emit({\n                    value: option.value,\n                    anchorId: actionId,\n                    originalEvent: event.detail,\n                  });\n                }}\n              />\n            </ds-tooltip>\n          ) : undefined\n        }\n        onHover={() => {\n          this.focusRingVisible = false;\n          this.activeIndex = index;\n        }}\n        onSelect={() => this.selectOption(option)}\n      />\n    );\n  }\n\n  private get captionIconOnly(): boolean {\n    return this.collapseLabel && this.captionCompact && Boolean(this.icon || this.isLoading);\n  }\n\n  private syncCaptionCompactObserver(): void {\n    this.disconnectCaptionCompactObserver();\n    if (!this.collapseLabel) {\n      if (this.captionCompact) this.captionCompact = false;\n      return;\n    }\n    this.captionCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n      if (this.captionCompact !== compact) this.captionCompact = compact;\n    });\n  }\n\n  private disconnectCaptionCompactObserver(): void {\n    this.captionCompactDisconnect?.();\n    this.captionCompactDisconnect = undefined;\n  }\n\n  render() {\n    const inactive = this.isDisabled;\n    const showPlaceholder = !this.hasSelection;\n    const count = this.resolvedValues.length;\n    const label = this.multiple\n      ? `${this.placeholder}${count > 0 ? ` · ${count}` : ''}`\n      : showPlaceholder\n        ? this.placeholder\n        : (this.triggerLabel ?? this.selectedOption?.label);\n    const textVariant = CONTROL_TEXT_VARIANT[this.size];\n    const iconSize = ICON_SIZE[this.size];\n    const usesOptionIcons = !this.multiple && choiceListUsesIcons(this.allOptions);\n    const usesOptionSubtext = choiceListUsesSubtext(this.allOptions);\n    const showError = this.error && Boolean(this.errorMessage);\n    const describedBy =\n      [this.ariaDescribedby, showError ? this.errorId : undefined].filter(Boolean).join(' ') ||\n      undefined;\n    const popupStyle = {\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    let flatIndex = 0;\n\n    return (\n      <Host\n        class={{\n          'select-host': true,\n          'ds-select-trigger-host': true,\n          'select-host--multiple': this.multiple,\n          'ds-field-stack': true,\n          'ds-control-inactive': inactive,\n          [`ds-control--${this.size}`]: true,\n          'ds-table-caption-control': this.collapseLabel,\n          'ds-table-caption-control--compact': this.captionIconOnly,\n          ...controlWidthClass(this.width),\n          [`select-host--background-${this.background}`]: !!this.background,\n          [`ds-select-trigger-host--background-${this.background}`]: !!this.background,\n        }}\n      >\n        <button\n          ref={element => {\n            this.controller.setTriggerElement((element as HTMLButtonElement) ?? null);\n          }}\n          id={this.inputId ?? this.generatedId}\n          type=\"button\"\n          class={{\n            trigger: true,\n            'ds-control-frame': true,\n            'ds-focus-ring-inset': true,\n            'ds-interaction-fill': true,\n            'ds-interaction-fill--selected': !inactive && this.activeFill && this.hasSelection,\n            'trigger--expanded': !inactive && this.open,\n            'trigger--bordered': this.hasBorder,\n            'trigger--placeholder': showPlaceholder && !this.multiple,\n            'trigger--has-value': this.hasSelection,\n            'trigger--label-placeholder':\n              !this.multiple && Boolean(this.triggerLabel) && this.triggerLabelPlaceholder,\n            'wrapper--error': this.hasBorder && this.error,\n            [`ds-control--${this.size}`]: true,\n            ...choiceBackgroundClassMap(this.background),\n          }}\n          disabled={inactive}\n          role=\"combobox\"\n          aria-haspopup={this.popupRole}\n          aria-expanded={String(this.open)}\n          aria-controls={this.open ? this.listboxId : undefined}\n          aria-activedescendant={this.open && !this.searchable ? this.activeOptionId : undefined}\n          aria-label={this.ariaLabel ?? (this.captionIconOnly ? label : undefined)}\n          aria-labelledby={this.ariaLabelledby}\n          aria-describedby={describedBy}\n          aria-invalid={this.error ? 'true' : undefined}\n          aria-required={this.required || undefined}\n          aria-busy={this.isLoading ? 'true' : undefined}\n          onClick={() => (this.open ? this.closePopup() : this.openPopup(false))}\n          onKeyDown={event => {\n            const action = this.visibleOptions[this.activeIndex]?.action;\n            if (\n              this.open &&\n              action &&\n              (event.key === 'ArrowRight' || (event.key === 'F10' && event.shiftKey))\n            ) {\n              event.preventDefault();\n              document\n                .getElementById(`${this.generatedId}-option-action-${this.activeIndex}`)\n                ?.querySelector<HTMLElement>('button')\n                ?.focus();\n              return;\n            }\n            this.handleTriggerKeyDown(event);\n          }}\n        >\n          {(this.icon || this.isLoading) && (\n            <span\n              class=\"trigger__prefix ds-control-icon-box ds-interaction-fill__content\"\n              aria-hidden=\"true\"\n            >\n              {this.isLoading ? (\n                <ds-loader size={iconSize} color=\"inherit\" />\n              ) : (\n                <ds-icon name={this.icon} size={iconSize} color=\"inherit\" />\n              )}\n            </span>\n          )}\n          {this.captionIconOnly ? null : (\n            <span\n              class={{\n                'trigger__label-box': true,\n                'ds-interaction-fill__content': true,\n              }}\n            >\n              <span\n                class={{\n                  'trigger__label-content': true,\n                  'ds-control-label-box': true,\n                  'ds-control-label-dot': this.dot,\n                  'trigger__label-content--dot': this.dot,\n                }}\n              >\n                <ds-text\n                  class=\"trigger__label\"\n                  as=\"span\"\n                  variant={textVariant}\n                  color=\"inherit\"\n                  lineTruncation={1}\n                >\n                  {label}\n                </ds-text>\n                {this.dot && (\n                  <ds-badge\n                    class=\"trigger__dot ds-control-label-dot__badge\"\n                    variant=\"dot\"\n                    hasRing={false}\n                    label=\"\"\n                    aria-hidden=\"true\"\n                  />\n                )}\n              </span>\n            </span>\n          )}\n          {this.captionIconOnly ? null : (\n            <span\n              class=\"trigger__chevron ds-control-icon-box ds-interaction-fill__content\"\n              aria-hidden=\"true\"\n            >\n              <ds-icon\n                name={this.indicator === 'up-down' ? 'ChevronUpDown' : 'ChevronDown'}\n                size={iconSize}\n                color=\"inherit\"\n              />\n            </span>\n          )}\n        </button>\n\n        {this.open && (\n          <div\n            popover=\"manual\"\n            ref={element => {\n              this.controller.setPopupElement((element as HTMLDivElement) ?? null);\n            }}\n            class=\"select-popup ds-choice-popup\"\n            style={popupStyle}\n          >\n            {this.searchable && (\n              <ChoiceSearch\n                size={this.size}\n                value={this.searchTerm}\n                placeholder={this.searchPlaceholder}\n                controls={this.listboxId}\n                activeDescendant={this.activeOptionId}\n                inputRef={this.controller.setSearchElement}\n                clearLabel={this.clearLabel}\n                onValueChange={value => {\n                  this.searchTerm = value;\n                }}\n                onKeyDown={event => this.handleListKeyDown(event)}\n              />\n            )}\n            <div\n              id={this.listboxId}\n              class=\"ds-choice-list\"\n              role={this.popupRole}\n              aria-multiselectable={this.multiple ? 'true' : undefined}\n              aria-label={this.ariaLabel ?? this.placeholder}\n              aria-busy={this.isLoading ? 'true' : undefined}\n            >\n              {this.isLoading ? (\n                <div\n                  class=\"ds-choice-loading ds-empty-region\"\n                  role={this.popupRole === 'grid' ? 'row' : 'option'}\n                  aria-selected=\"false\"\n                  aria-disabled=\"true\"\n                  aria-label={this.loadingLabel}\n                  aria-live=\"polite\"\n                >\n                  {this.popupRole === 'grid' ? (\n                    <span role=\"gridcell\">\n                      <ds-loader size={this.size} color=\"inherit\" />\n                    </span>\n                  ) : (\n                    <ds-loader size={this.size} color=\"inherit\" />\n                  )}\n                </div>\n              ) : this.visibleOptions.length === 0 ? (\n                <div\n                  class=\"ds-choice-empty ds-empty-region\"\n                  role={this.popupRole === 'grid' ? 'row' : 'option'}\n                  aria-selected=\"false\"\n                  aria-disabled=\"true\"\n                  aria-label={this.noResultsText}\n                  aria-live=\"polite\"\n                >\n                  {this.popupRole === 'grid' ? (\n                    <span role=\"gridcell\">\n                      <ds-empty-state body={this.noResultsText} />\n                    </span>\n                  ) : (\n                    <ds-empty-state body={this.noResultsText} />\n                  )}\n                </div>\n              ) : (\n                this.visibleSections.map((section, sectionIndex) => (\n                  <div\n                    class={{\n                      'ds-choice-section': true,\n                      'ds-chrome-column': true,\n                      'ds-chrome-space--sm': true,\n                      'ds-choice-section--divided': !!section.divider,\n                      'ds-choice-section--headed-after-first':\n                        sectionIndex > 0 && Boolean(section.header),\n                    }}\n                    role={\n                      this.popupRole === 'grid' ? 'rowgroup' : section.header ? 'group' : undefined\n                    }\n                    aria-label={section.header}\n                  >\n                    {section.header && this.popupRole === 'grid' ? (\n                      <ds-text\n                        class={`ds-choice-section__header ds-control-section-heading ds-control--${this.size}`}\n                        as=\"span\"\n                        variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n                        emphasis\n                        color=\"primary\"\n                        role=\"row\"\n                      >\n                        <span class=\"ds-choice-section__header-label\" role=\"gridcell\">\n                          {section.header}\n                        </span>\n                      </ds-text>\n                    ) : section.header ? (\n                      <ds-text\n                        class={`ds-choice-section__header ds-control-section-heading ds-control--${this.size}`}\n                        as=\"span\"\n                        variant={CONTROL_SUPPORTING_TEXT_VARIANT[this.size]}\n                        emphasis\n                        color=\"primary\"\n                        aria-hidden=\"true\"\n                      >\n                        <span class=\"ds-choice-section__header-label\">{section.header}</span>\n                      </ds-text>\n                    ) : null}\n                    {section.options.map(option =>\n                      this.renderOption(option, flatIndex++, usesOptionIcons, usesOptionSubtext)\n                    )}\n                  </div>\n                ))\n              )}\n            </div>\n            {this.footerActionLabel && !this.isLoading ? (\n              <ChoiceActionFooter\n                size={this.size}\n                label={this.footerActionLabel}\n                onAction={this.activateFooterAction}\n              />\n            ) : this.allowClear && this.hasSelection && !this.isLoading ? (\n              <ChoiceFooter\n                size={this.size}\n                summary={this.multiple ? `${count} ${this.selectedLabel}` : undefined}\n                compactSummary={this.multiple ? String(count) : undefined}\n                useCompactSummary={this.compactFooterSummary}\n                contentRef={element => {\n                  this.footerContentElement = element ?? undefined;\n                }}\n                summaryMeasureRef={element => {\n                  this.footerSummaryMeasureElement = element ?? undefined;\n                }}\n                clearRef={element => {\n                  this.footerClearElement = element ?? undefined;\n                }}\n                clearLabel={this.clearLabel}\n                onClear={this.clearSelection}\n              />\n            ) : null}\n          </div>\n        )}\n\n        {showError && (\n          <ds-text\n            class=\"error-text\"\n            as=\"div\"\n            variant=\"text-body-small\"\n            color=\"negative\"\n            textId={this.errorId}\n            role=\"alert\"\n          >\n            {this.errorMessage}\n          </ds-text>\n        )}\n      </Host>\n    );\n  }\n}\n",
      "type": "registry:ui"
    }
  ]
}
