@qijenchen/design-system 0.1.0-beta.111 → 0.1.0-beta.113
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.
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +25 -6
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +7 -0
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +12 -5
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +4 -1
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +21 -3
- package/dist/components/Select/select.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +1 -1
- package/ds-canonical/fork/consumer/lock.json +6 -6
- package/ds-canonical/fork/consumer/managed-files/0235cf6b3fd402a3717881dc205b5bf77aed7632e7d7d6f1811a40e3033bd816.blob +2 -2
- package/ds-canonical/fork/consumer/managed-files/9791c85388d2e2e2bd564e2550fadc3351c4c960a47ce838098afcba7544eb90.blob +11 -2
- package/ds-canonical/fork/governance.lock +3 -3
- package/ds-canonical/fork/manifest.json +10 -10
- package/ds-story-manifest.json +5 -1
- package/package.json +2 -2
- package/src/components/Combobox/combobox.spec.md +2 -0
- package/src/components/Combobox/combobox.tsx +42 -7
- package/src/components/Field/field.spec.md +10 -0
- package/src/components/FileUpload/file-upload.stories.tsx +15 -4
- package/src/components/PeoplePicker/people-picker.spec.md +24 -0
- package/src/components/PeoplePicker/people-picker.stories.tsx +51 -2
- package/src/components/PeoplePicker/people-picker.tsx +32 -5
- package/src/components/PeoplePicker/person-display.tsx +4 -1
- package/src/components/Select/select.spec.md +3 -1
- package/src/components/Select/select.tsx +26 -2
- package/src/components/Steps/steps.stories.tsx +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","sources":["../../../src/components/Select/select.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — Select 含 3 子元件(NativeSelect/CustomSelect/ReadonlyDisplay)+ helpers + 4-mode renderer + Field SSOT consumption,split-into-files 會破壞 file-local helper closure\n// @renderer-symmetry-allow: 2026-07-08 WM 戰役 A 案回歸修正 — ReadonlyDisplay 現已消費 selectedItemRenderer(view bare-span / D-path / readonly / disabled 四分支),對齊 field-controls.spec.md 共享 contract (a)「view/readonly/disabled/edit 4 mode 共享同一 renderer」。前 note「display→edit unify deferred」已兌現(值內容層);chrome 結構 unify(D-path)仍為 opt-in showDisplayEndIcon。\nimport * as React from 'react'\nimport { X, ChevronDown } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { Tag } from '@/design-system/components/Tag/tag'\nimport { ItemInlineAction, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { TruncatedText } from '@/design-system/patterns/element-anatomy/truncated-text'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'\nimport { useTruncated } from '@/design-system/hooks/use-truncated'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { SelectMenu, forwardKeyToListbox, useActiveDescendant, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'\nimport { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n// ── Tag padding per size ────────────────────────────────────────────────────\nconst tagPadding: Record<string, string> = {\n sm: 'px-[calc((var(--field-height-sm)_-_1.25rem)_/_2)]',\n md: 'px-[calc((var(--field-height-md)_-_1.5rem)_/_2)]',\n lg: 'px-[calc((var(--field-height-lg)_-_1.5rem)_/_2)]',\n}\n\n// ── Display ─────────────────────────────────────────────────────────────────\n\n/**\n * Select 用的 option schema(2026-05-10 Issue 4 + post-prune unify):**explicit extends\n * SelectMenuOption(primitive SSOT)** — 任何 SelectMenuOption 加 field 都自動繼承,不會 drift。\n *\n * Why `extends SelectMenuOption`(per user 「全盤檢查避免下次又改壞或是偏移」要求):\n * - **schema SSOT 機械強制**:TypeScript inheritance 跟著 primitive 走,wrapper consumer 永遠\n * 拿得到 primitive 所有 surface field\n * - **Hook lint**(M30 `check_wrapper_primitive_schema_drift.sh`):grep `interface .*Option`\n * 未 `extends` SelectMenuOption / 同名重複 declare 直接 BLOCK\n *\n * Wrapper-only field(`tagVariant`)— Select 獨有 `display='tag'` 用,SelectMenu primitive 不該知道\n * 此 wrapper-only concern,所以 wrapper 層 extend 加上,不污染 primitive。\n *\n * 對齊 Polaris ChoiceList / Material Autocomplete / Carbon Dropdown 的 wrapper-vs-primitive\n * schema-extension idiom。\n */\nexport interface SelectOption extends SelectMenuOption {\n /** Tag 模式的顏色。只在 display='tag' 時生效,對應 Tag 的 variant。Wrapper-only。 */\n tagVariant?: string\n}\n\n/** 分組設定 — 對齊 SelectMenuGroupConfig SSOT */\nexport interface SelectGroupConfig {\n key: string\n label: string\n}\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * 原生 `<select>` 屬性 allowlist(2026-07-14 API 策展 D,user 拍板「全部收窄」)。\n *\n * 舊契約 `extends Omit<SelectHTMLAttributes, ...>` 型別承諾**全部**原生 select 屬性,但桌機\n * 自訂 combobox(div trigger + hidden input mirror)只有部分真生效 — 其餘 silent no-op /\n * 被元件內部覆寫,「型別承諾 ≠ 實作出口」= footgun。改 `Pick<>` 顯式 allowlist,**只承諾\n * 兩路徑(desktop CustomSelect / mobile NativeSelect)皆有實作出口的屬性**,盤點以 code 為準:\n *\n * - `id` — 桌機 trigger div / mobile `<select>`,皆 `idProp ?? fieldCtx?.id`\n * - `name` / `form` / `required` — 桌機經 hidden input mirror(D2 2026-07-13,見 hiddenInputEl\n * docblock)/ mobile 原生 `<select>` spread\n * - `disabled` — 兩路徑 `useResolvedFieldDisabled` cascade → mode 分流\n * - `className` / `style` — 兩路徑 field wrapper\n * - `onFocus` / `onBlur` — 桌機 rest spread 到 focusable trigger(tabIndex=0)/ mobile spread\n * - `onKeyDown` — 桌機 consumer handler 先跑再走元件導覽(dim-9)/ mobile 原生\n * - `aria-label` / `aria-describedby` / `aria-errormessage` — 兩路徑顯式接線(fieldCtx fallback)\n * - `autoFocus` / `autoComplete` — **mobile-only**:NativeSelect 原生 `<select>` 生效;桌機\n * custom combobox 無原生對應 = documented no-op(桌機 autofill 由 hidden input mirror 的\n * option-match onChange guard 承擔)\n * - `data-*` — 兩路徑 spread passthrough(dim-9 契約;index signature 顯式承諾,見下)\n *\n * 被砍者(原 extends 承諾但從未生效 / 被元件覆寫,不再進型別):`multiple` / `children` /\n * `tabIndex`(桌機恆 0)/ `aria-labelledby`(桌機由 fieldCtx.labelId / aria-label 管理,\n * consumer 值會被覆寫)/ 其餘 generic HTMLAttributes(title / dir / onClick 等)。\n * 需要時先接線(兩路徑等效)再入表,**不回 extends 全開**。\n *\n * 世界級對照:自訂 combobox 皆走 curated 原生屬性子集,無人 extends 全部 SelectHTMLAttributes —\n * Radix Select.Root 顯式列舉 name/disabled/required/form/autoComplete\n * (radix-ui.com/primitives/docs/components/select#root)/ Headless UI Listbox name/form/disabled\n * (headlessui.com/react/listbox#using-with-html-forms)/ React Aria Select name/autoComplete/\n * isDisabled/isRequired(react-spectrum.adobe.com/react-aria/Select.html)。\n */\nexport interface SelectProps\n extends Pick<\n React.SelectHTMLAttributes<HTMLSelectElement>,\n // ── 兩路徑皆真生效 ──\n | 'id' | 'className' | 'style'\n | 'name' | 'form' | 'required' | 'disabled'\n | 'onFocus' | 'onBlur' | 'onKeyDown'\n | 'aria-label' | 'aria-describedby' | 'aria-errormessage'\n // ── mobile-only(NativeSelect 原生 <select> 生效;桌機 custom combobox documented no-op)──\n | 'autoFocus' | 'autoComplete'\n > {\n /** `data-*` passthrough(桌機 trigger div rest spread / mobile `<select>` spread;dim-9 契約)。 */\n [dataAttribute: `data-${string}`]: unknown\n mode?: FieldMode\n /** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */\n variant?: FieldVariant\n /** 寬度軸 — `fill` 填滿容器(default)/ `hug` 依內容收縮:value 寬 + slot 元素寬 + gap +\n * field 內 padding;chrome 與互動不變。場景 = detail pane inline metadata。\n * SSOT → field-controls.spec.md「寬度軸(width: fill / hug)」。 */\n width?: FieldWidth\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n options: SelectOption[]\n /** 分組顯示(對齊 SelectMenu groups SSOT)。option.group 對應 groups[].key */\n groups?: SelectGroupConfig[]\n /** Controlled value(consumer 自管 state)。傳 `value` + `onChange` 表示 controlled mode。 */\n value?: string | null\n /** Uncontrolled 初始值(2026-05-21 D3 audit add per user verbatim「決策三照妳建議」+「都給我做到好」)。\n * 不傳 `value` 時 Select 自管 internal state,以 `defaultValue` 為初始值,選變更時 fire `onChange`\n * callback 通知 consumer(但 state 仍歸 Select)。對齊 Radix Select(`defaultValue`)+ shadcn Input\n * (`defaultValue`)+ React `<input>` dual-mode canonical。\n * 互斥規則:同時傳 `value` + `defaultValue` 走 controlled(value 勝),`defaultValue` 僅 first-mount 用。 */\n defaultValue?: string | null\n onChange?: (value: string) => void\n placeholder?: string\n clearable?: boolean\n display?: 'plain' | 'tag'\n startIcon?: LucideIcon\n /** 啟用搜尋(desktop 時 field 變 input,打字即篩選) */\n searchable?: boolean\n /** Loading state(2026-05-15 audit B fix;2026-07-04 Q3 拍板措辭修訂)。\n * Forward 給 SelectMenu primitive SSOT;spinner 只在**無可顯示選項時**佔 empty slot(cmdk CommandEmpty\n * 機制)— 已有 stale options 時保留顯示不清空(對齊 MUI Autocomplete「only if there are no suggestions」)。\n * Trigger 不變(chevron 保留 user 隨時可點開)。*/\n loading?: boolean\n\n /** Menu list 最小列數(空狀態 / 選項少時的視覺一致 reserve)。預設 3 — 選項 < 3 時顯式縮(如 And/Or 兩選項) */\n minRows?: number\n /** Initial open state(uncontrolled)。對齊 Radix Popover defaultOpen canonical;DataTable cell-as-input\n * click → 1 step open menu(Airtable / Notion canonical),consumer pass `defaultOpen` 達成。\n * Note:Native Select(mobile)無 popover 概念,此 prop 僅 Custom path 生效。 */\n defaultOpen?: boolean\n /** open state 變更 callback(對齊 Radix Popover onOpenChange canonical)。\n * DataTable cell-as-input 用:open=false 時 cell 自動 exit edit mode(避免 dismiss 後卡住)。 */\n onOpenChange?: (open: boolean) => void\n /**\n * View mode 顯 picker intrinsic end icon(2026-05-08 D path Phase 1)。\n * 預設 false:`mode=\"view\"` 純展示 bare span(向後相容)。\n * `variant=\"naked\" && mode=\"view\"` 場景(DataTable cell)opt-in 設 true → wrap 進\n * Field naked-view + 渲 ChevronDown ItemSuffix。**只 view mode 生效**;readonly /\n * disabled / edit 已有 Field wrapper + suffix(不受此 prop 影響)。\n * Authority:`data-table.spec.md:204` + `inline-action.spec.md:157`「Field family endAction(自動繼承)」。\n * @default false\n */\n showDisplayEndIcon?: boolean\n /**\n * 「已選項目」客製 render(2026-05-07 v15.5;2026-07-08 A 案回歸修正擴及 4 mode)。\n *\n * 設了 → 已選值不走純文字 / Tag 預設 path,改用 consumer 提供的 ReactNode(收 selectedOpt)。\n * **view / readonly / disabled / edit 4 mode 共享同一 renderer**(field-controls.spec.md\n * 共享 contract (a),禁 edit-only)— renderer 輸出屬「值內容」(status icon+text 等語意呈現),\n * view 態照常渲染;A 案撤的是 affordance(chevron/outline),非值內容(field.spec.md L6 分層)。\n * Searchable+open 仍走 input(搜尋優先)。Empty value(no selection)仍走 placeholder。\n *\n * 用例:PeoplePicker 用此 slot 把 single 選中的 person render 成 PersonDisplay\n * (avatar + name)而非純文字 label;DataTable status 欄 icon+text。對齊 PeoplePicker = Select wrapper SSOT。\n */\n selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode\n /** 搜尋無結果提示(2026-07-04 Q4 拍板接線 — 原 spec 宣稱可覆寫但 prop 從未轉發)。\n * Forward 給 SelectMenu primitive SSOT(default 在 SelectMenu 層「沒有符合的選項」);\n * 僅 Custom path(searchable dropdown)生效,Native path 無搜尋空狀態。\n * 對齊 MUI noOptionsText / Ant notFoundContent / Polaris emptyState 三家公開覆寫點共識。 */\n emptyText?: string\n /**\n * 可建立新選項(creatable,2026-07-18 決策11 user 拍板 forward 到 Select)。**需搭配 `searchable`**\n * ——搜尋非空且無完全同名既有選項時,dropdown 顯 create row(Plus + `createLabel`)。\n * 邏輯/顯示/互動 SSOT 住在 SelectMenu(Select 的 trigger 搜尋以受控 `search` 驅動 SelectMenu creatable\n * 顯隱)。與 Combobox.creatable 同一底層。非 searchable 時無 create 通道(無輸入處)。 */\n creatable?: boolean\n /** 建立新選項 callback(收使用者輸入的搜尋字串)。 */\n onCreate?: (value: string) => void\n /** create row 的 label 格式,預設 '直接使用「{query}」'(forward SelectMenu SSOT)。 */\n createLabel?: (query: string) => string\n}\n\n// ── Icon / size helpers ─────────────────────────────────────────────────────\n// 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反)\nconst getIconSize = (size: string) => ICON_SIZE[size as 'sm' | 'md' | 'lg']\n\n// ── Shared sub-components ───────────────────────────────────────────────────\n\n/**\n * Inline clear button for Select trigger.\n * 共用 SSOT — Native + Custom 兩變體統一消費。差別僅 onClick 內是否 stopPropagation\n * (Custom trigger 是 combobox `<div>`,點 clear 不可冒泡到打開 menu;Native `<select>` 自有原生\n * 行為,不需 stopPropagation)。\n *\n * 消費的 SSOT:\n * - patterns/element-anatomy/item-anatomy.spec.md → ItemInlineAction(canonical row inline action)\n */\nfunction SelectClearButton({\n size,\n onClear,\n stopPropagation = false,\n}: {\n size: 'sm' | 'md' | 'lg'\n onClear: () => void\n stopPropagation?: boolean\n}) {\n return (\n <span className=\"relative z-10\">\n <ItemInlineAction\n size={size}\n action={{\n icon: X,\n label: '清除選取', // i18n-allow: DS default inline-action label\n onClick: stopPropagation ? (e) => { e?.stopPropagation(); onClear() } : () => onClear(),\n }}\n />\n </span>\n )\n}\nSelectClearButton.displayName = 'SelectClearButton'\n\n/**\n * Trigger content for CustomSelect — 三種顯示模式分支(searchable+open / text / tag)\n * 抽出降低 `CustomSelect` forwardRef body 長度;邏輯本質是純展示分流,無 hook / ref。\n */\nfunction CustomSelectTriggerContent({\n searchable,\n open,\n isTextDisplay,\n size,\n value,\n selectedLabel,\n selectedOpt,\n SelectedIcon,\n StartIcon,\n iconSize,\n placeholder,\n search,\n setSearch,\n inputRef,\n activeDescendantId,\n ariaLabel,\n labelId,\n selectedItemRenderer,\n truncationRef,\n}: {\n searchable: boolean\n open: boolean\n isTextDisplay: boolean\n size: 'sm' | 'md' | 'lg'\n value?: string | null\n selectedLabel: string\n selectedOpt?: SelectOption\n SelectedIcon?: LucideIcon\n StartIcon?: LucideIcon\n iconSize: number\n placeholder?: string\n search: string\n setSearch: (v: string) => void\n inputRef: React.RefObject<HTMLInputElement | null>\n activeDescendantId?: string\n ariaLabel?: string\n labelId?: string\n selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode\n /** 截斷 tooltip 量測點(text display 的值 span;tooltip trigger 在外層 combobox host,見 CustomSelect triggerTruncationRef 註解)*/\n truncationRef?: React.Ref<HTMLSpanElement>\n}): React.ReactNode {\n // Searchable + open: 顯示搜尋 input\n // 2026-05-15 Bug 2 fix(Claude+Codex Step 5 比稿 consensus,user verbatim「就 A」):\n // 撤掉 native `<input placeholder=selectedLabel>` 不可靠 ellipsis renderer(browser-specific\n // placeholder painting,user 抓「placeholder 直接被截掉沒 ellipsis」)。改 span overlay:\n // - input native placeholder 限「搜尋…」/「請選擇人員」trigger empty hint(無 selectedLabel)\n // - sibling `<span aria-hidden pointer-events-none absolute inset-0 truncate>` 在 search='' 且\n // 有 selectedLabel 時 overlay 顯該人名(memory aid,truncate-with-ellipsis 可控)\n // 對齊 spec.md §B row 4「open + inline-search + 選 1 人 → input cursor + placeholder = 該人名 + ellipsis」。\n // a11y(2026-07-05 backlog 補接):搜尋 input 是 open 時實際聚焦的元素,accessible name 必接在\n // input 本身(原 codex Q2 guard 註解宣稱「來自 field/label/aria-label」,但接線全在外層 trigger\n // div,焦點所在的 input 無 name)— aria-labelledby 接 FieldLabel labelId、consumer aria-label\n // 優先(對齊 trigger div 同款 guard),兩者皆無時 fallback aria-label「搜尋選項」(對齊\n // combobox.tsx searchAriaLabel canonical default);**不**依賴 placeholder 當 label;overlay\n // span aria-hidden + pointer-events-none。APG「combobox role 移到 input 本身」屬中期重構另案。\n if (searchable && open) {\n const triggerEmptyPlaceholder = placeholder || '搜尋…' // i18n-allow: DS fallback\n const showSelectedOverlay = !search && selectedLabel\n return (\n <span className=\"relative flex-1 min-w-0 inline-flex items-center\">\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n <input\n ref={inputRef as React.RefObject<HTMLInputElement>}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n // a11y(2026-07-05 D4):cmdk active item id(useActiveDescendant)→ SR 播報方向鍵導覽中的 option\n aria-activedescendant={activeDescendantId}\n // accessible name:aria-labelledby(Field label)> consumer aria-label > DS default(accname 優先序)\n aria-label={ariaLabel ?? '搜尋選項'} // i18n-allow: DS default(對齊 combobox.tsx searchAriaLabel canonical)\n aria-labelledby={ariaLabel ? undefined : labelId}\n // Native placeholder 限 trigger empty hint(無 selectedLabel 時);若已 selected,留空交給 overlay span\n placeholder={showSelectedOverlay ? '' : triggerEmptyPlaceholder}\n className={cn(bareInputStyles, 'cursor-text')}\n autoFocus\n />\n {showSelectedOverlay && (\n // 2026-05-16 Bug B 真 root cause fix(Claude+Codex M31 Step 5 比稿 consensus,user verbatim\n // 「修了一百次還沒好」+ codex cite W3C CSS Overflow / MDN / Mozilla Bug 972664#c1):\n // 原 `inline-flex items-center truncate` 套同一 span,text 變 anonymous flex item →\n // `text-overflow:ellipsis` 對 anonymous item 不 styleable → ellipsis dots 不可見(text 純 clip)。\n // 對齊 `person-display.tsx:148` 既有 DS canonical:outer flex container + inner truncate 真實 box。\n // DS-wide grep 29 個 truncate 都遵此 pattern,只本處違反 — 修齊。\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 flex items-center text-fg-muted\"\n >\n <span className=\"min-w-0 flex-1 truncate\">{selectedLabel}</span>\n </span>\n )}\n </span>\n )\n }\n // **selectedItemRenderer slot**(2026-05-07 v15.5):consumer 客製 selected display(e.g.\n // PeoplePicker 接 PersonDisplay)。優先於 isTextDisplay / Tag 預設 path,但 empty value\n // 仍走 placeholder。對齊 PeoplePicker = Select wrapper SSOT。\n if (selectedItemRenderer && value && selectedOpt) {\n return (\n <>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n {/* 2026-05-14 item-anatomy SSOT fix(per codex H2 propagation 斷點):加 nakedCellRowModeAlign\n → autoRowHeight cell 內 selected renderer 也對齊 first-line,不再 vertical-center 整 row。 */}\n <span className={cn(\"flex-1 min-w-0 inline-flex items-center\", nakedCellRowModeAlign)}>{selectedItemRenderer(selectedOpt)}</span>\n </>\n )\n }\n // Text display: 純文字 + optional value icon\n if (isTextDisplay) {\n return (\n <>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n {!StartIcon && SelectedIcon && value && <ItemPrefix><SelectedIcon size={iconSize} className=\"pointer-events-none\" aria-hidden /></ItemPrefix>}\n <span ref={truncationRef} className={cn('flex-1 min-w-0 truncate', !value && 'text-fg-muted')}>\n {value ? selectedLabel : (placeholder ?? '選擇…')}\n </span>\n </>\n )\n }\n // Tag display: 用 option 的 tagVariant\n return (\n <>\n {value && selectedOpt?.tagVariant\n ? <Tag size={size} color={selectedOpt.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral'} className=\"shrink-0 pointer-events-none\">{selectedLabel}</Tag>\n : value\n ? <Tag size={size} className=\"shrink-0 pointer-events-none\">{selectedLabel}</Tag>\n : <span className=\"text-fg-muted\">{placeholder ?? '選擇…'}</span>\n }\n <span className=\"flex-1\" />\n </>\n )\n}\nCustomSelectTriggerContent.displayName = 'CustomSelectTriggerContent'\n\n// ── Shared readonly/disabled/view render ─────────────────────────────────\nfunction ReadonlyDisplay({\n mode, variant: variantProp, width, size, options, value, display, startIcon: StartIcon, className, placeholder, showDisplayEndIcon, selectedItemRenderer,\n}: Pick<SelectProps, 'mode' | 'width' | 'size' | 'options' | 'value' | 'display' | 'startIcon' | 'className' | 'placeholder' | 'showDisplayEndIcon' | 'selectedItemRenderer'> & {\n /** @internal 2026-07-14 API 策展 E:內部 render helper 吃 FieldVariantInternal(naked 由 cell-registry 通道傳入)*/\n variant?: FieldVariantInternal\n}) {\n const resolvedMode = mode ?? 'readonly'\n const emptyDisplay = useFieldEmptyDisplay()\n const variant = variantProp ?? 'default'\n const sz = size ?? 'md'\n const iconSize = getIconSize(sz)\n // selectedOpt 提前 hoist(2026-07-08 A 案回歸修正):view / D-path / readonly / disabled\n // 四分支都需要 — renderer 值內容(field-controls.spec.md contract (a) 4-mode 共享)+ tagVariant 查找。\n const selectedOpt = options?.find(o => o.value === value)\n const label = selectedOpt?.label ?? value\n const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'\n const isTextDisplay = display !== 'tag'\n // K10+K14 fix(2026-05-04):disabled mode placeholder/empty 顯示色 → fg-disabled(neutral-6),非 fg-muted(neutral-7)\n // user canonical:disabled 顯著性優於 muted。同時 plain mode 必須 respect placeholder prop(之前忽略 = bug)\n const emptyColorCls = fieldEmptyColorClass(resolvedMode)\n const emptyText = placeholder ?? emptyDisplay\n\n // mode='view':2 path(2026-05-08 D path Phase 1 Select canary)\n // ❌ 預設(無 showDisplayEndIcon):純內容輸出 bare span/Tag(原行為,backward compat)\n // 對齊原 SelectDisplay sub-component(retired)。readonly / disabled 仍走下方 fieldWrapperStyles。\n // ✅ showDisplayEndIcon=true(DataTable cell opt-in):Field naked-view wrapper +\n // ChevronDown ItemSuffix。SSOT canonical 跟 readonly/edit/disabled mode 同 DOM 結構。\n // Authority: data-table.spec.md:204 + inline-action.spec.md:157「Field family endAction」\n if (resolvedMode === 'view') {\n if (!showDisplayEndIcon) {\n // 2026-05-14 I2 fix(spec contract (e) view typography canonical):bare span 必套\n // `fieldDisplayTextClass(sz)`(sm/md→text-body,lg→text-body-lg)— 對齊跨 Field\n // family view 視覺尺寸統一。\n if (!value) return <span className={cn(fieldDisplayTextClass(sz), emptyColorCls, className)}>{emptyText}</span>\n // 2026-07-08 A 案回歸修正:selectedItemRenderer(值內容:status icon+text 等語意呈現)\n // view 態照常渲染 — 無 chrome 無 chevron,只有值內容本身。分層原則(field.spec.md L6):\n // 本 renderer 歸「值內容」,恆顯;A 案撤的是 affordance(chevron/outline),非值內容。\n // 對齊 field-controls.spec.md 共享 contract (a)「4 mode 共享同一 renderer(禁 edit-only)」。\n if (selectedItemRenderer && selectedOpt) {\n return (\n <span className={cn(fieldDisplayTextClass(sz), 'inline-flex items-center min-w-0', nakedCellRowModeAlign, className)}>\n {selectedItemRenderer(selectedOpt)}\n </span>\n )\n }\n // 截斷必附 tooltip(tooltip.spec.md:32)— 改消費 truncated-text primitive(同 input.tsx:196 同型修;\n // display='tag' 路徑由 Tag 內建 useTruncated 自管,text 路徑原本裸 truncate = gap)\n if (isTextDisplay) return <TruncatedText className={cn(fieldDisplayTextClass(sz), className)}>{label}</TruncatedText>\n const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined\n return <Tag size={sz} color={tVariant} className={className}>{label}</Tag>\n }\n // D path opt-in: Field naked-view wrapper + ItemSuffix ChevronDown\n const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'view', variant, width, size: sz }), value && !isTextDisplay && tagPadding[sz], className)}\n data-field-mode=\"view\"\n >\n {selectedItemRenderer && value && selectedOpt ? (\n // renderer 優先(同 CustomSelectTriggerContent 優先序):值內容歸 renderer,chevron 歸 D-path chrome\n <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign)}>\n {selectedItemRenderer(selectedOpt)}\n </span>\n ) : isTextDisplay ? (\n // 截斷必附 tooltip(tooltip.spec.md:32;data-table.spec.md:183 view 態元件自管 truncation)\n <TruncatedText className={cn(bareInputStyles, 'flex-1 min-w-0', !value && emptyColorCls)}>\n {value ? label : emptyText}\n </TruncatedText>\n ) : value ? (\n <Tag size={sz} color={tVariant}>{label}</Tag>\n ) : (\n <span className={cn('flex-1 min-w-0', emptyColorCls)}>{emptyText}</span>\n )}\n <ItemSuffix><ChevronDown size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemSuffix>\n </div>\n )\n }\n\n // 2026-06-26 類型身份 indicator 規則:edit 顯示 / readonly 不顯示(純值、不可開下拉,箭頭會誤導) /\n // disabled 保留(fg-disabled,對齊原生 <select disabled> 灰示箭頭 + Accordion M24 precedent)。\n // naked cell 情境依 showDisplayEndIcon = isEditable,維持 2026-05-10 cell canonical「非可編欄不顯」。\n // aria-disabled:styled-disabled(非 native disabled 元素)需明告 AT「inactive」,同時讓 axe 正確\n // 豁免 disabled 文字的 color-contrast(WCAG 1.4.3 inactive UI 例外)。\n const showIndicator = variant === 'naked' ? !!showDisplayEndIcon : resolvedMode === 'disabled'\n const ariaDisabled = resolvedMode === 'disabled' ? true : undefined\n\n // 2026-07-08 A 案回歸修正:readonly / disabled 同樣消費 selectedItemRenderer(值內容 4-mode\n // 共享,field-controls.spec.md contract (a);對照 PeoplePicker readonly/disabled 渲 PersonDisplay\n // 先例)。renderer 優先於 isTextDisplay / Tag 預設 path(同 CustomSelectTriggerContent 優先序);\n // disabled 時 wrapper span 帶 text-fg-disabled 供未自帶色的文字繼承(M24 disabled > muted)。\n if (selectedItemRenderer && value && selectedOpt) {\n return (\n <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}\n <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign, resolvedMode === 'disabled' && 'text-fg-disabled')}>\n {selectedItemRenderer(selectedOpt)}\n </span>\n {showIndicator && <ItemSuffix className=\"pointer-events-none\"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}\n </div>\n )\n }\n\n if (isTextDisplay) {\n return (\n <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}\n {/* 截斷必附 tooltip(tooltip.spec.md:32)— readonly/disabled 無下拉入口,tooltip 是唯一補救路徑 */}\n <TruncatedText className={cn('flex-1', resolvedMode === 'disabled' && 'text-fg-disabled')}>\n {value ? label : <span className={emptyColorCls}>{emptyText}</span>}\n </TruncatedText>\n {showIndicator && <ItemSuffix className=\"pointer-events-none\"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}\n </div>\n )\n }\n\n const tagVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined\n\n return (\n <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), value && tagPadding[sz], className)} style={{ paddingRight: 'var(--field-px)' }} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>\n {value ? <Tag size={sz} color={tagVariant}>{label}</Tag> : <span className={emptyColorCls}>{emptyText}</span>}\n {showIndicator && <ItemSuffix className=\"pointer-events-none\"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}\n </div>\n )\n}\n\n// ── Native Select (mobile) ─────────────────────────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(\n ({ mode, variant: variantProp, width, error: errorProp = false, size: sizeProp, options, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp,\n // 2026-07-04:Custom-path-only props 顯式 destructure 丟棄(不 spread 到原生 <select> 消 React\n // unknown-prop warning);各 prop docblock 已註「僅 Custom path 生效」語意\n // 2026-07-08 A 案回歸修正:selectedItemRenderer 從丟棄名單移出 — Native path 的\n // ReadonlyDisplay(view/readonly/disabled)同樣消費值內容 renderer(contract (a) 4-mode\n // 共享,值內容不因 pointer type 而異);native <select> edit 路徑仍不消費(原生 option 無法客製 render)。\n searchable: _searchable, groups: _groups, loading: _loading, minRows: _minRows, emptyText: _emptyText, creatable: _creatable, onCreate: _onCreate, createLabel: _createLabel, defaultOpen: _defaultOpen, onOpenChange: _onOpenChange, selectedItemRenderer,\n ...props }, ref) => {\n const fieldCtx = useFieldContext()\n const error = useResolvedFieldInvalid(errorProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-05-31 #11:size 從 Field context cascade(對齊 Input/NumberInput + MUI FormControl)\n const size = useResolvedFieldSize(sizeProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode(prop > 有效 disabled > fieldCtx.mode > 'edit');修 <Field mode=\"view\"> 漏 cascade\n const resolvedMode = useResolvedFieldMode({ mode, disabled })\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n const iconSize = getIconSize(size)\n // 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(同 CustomSelect)\n const [value, setValue] = useControllable<string | null>({\n value: valueProp,\n defaultValue: defaultValue ?? null,\n onChange: onChange ? (next) => onChange(next ?? '') : undefined,\n })\n const handleNativeChange = (v: string) => setValue(v)\n const showClear = clearable && value && resolvedMode === 'edit'\n const isTextDisplay = display === 'plain'\n const selectRef = React.useRef<HTMLSelectElement | null>(null)\n const setSelectRef = React.useCallback((el: HTMLSelectElement | null) => {\n selectRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLSelectElement | null>).current = el\n }, [ref])\n\n if (resolvedMode !== 'edit') {\n return <ReadonlyDisplay mode={resolvedMode} variant={variant} width={width} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} selectedItemRenderer={selectedItemRenderer} />\n }\n\n const selectEl = (\n <select\n ref={setSelectRef}\n id={idProp ?? fieldCtx?.id}\n value={value ?? ''}\n onChange={(e) => handleNativeChange(e.target.value)}\n disabled={disabled}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n className={cn(bareInputStyles, 'cursor-pointer appearance-none', !value && 'text-fg-muted', !isTextDisplay && value && 'absolute inset-0 w-full h-full opacity-0 z-0')}\n {...props}\n >\n {placeholder && <option value=\"\" disabled>{placeholder}</option>}\n {/* disabled forward(2026-07-04):對齊桌機 path menuOptions disabled forward — 觸控原生 option 同樣不可選(spec「disabled 選項」邊界案例) */}\n {options.map(opt => <option key={opt.value} value={opt.value} disabled={opt.disabled}>{opt.label}</option>)}\n </select>\n )\n\n const clearEl = showClear ? (\n <SelectClearButton size={size ?? 'md'} onClear={() => handleNativeChange('')} />\n ) : null\n\n const chevronEl = (\n <ItemSuffix className=\"relative z-10 pointer-events-none\">\n <ChevronDown size={iconSize} className=\"text-fg-muted\" aria-hidden />\n </ItemSuffix>\n )\n const selectedOpt = options?.find(o => o.value === value)\n const label = selectedOpt?.label ?? value\n const nativeTagVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined\n const SelectedOptIcon = selectedOpt?.icon\n\n if (!isTextDisplay) {\n return (\n <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }), value && tagPadding[size], 'relative',\n className)}\n style={{ paddingRight: 'var(--field-px)' }} data-field-mode=\"edit\" data-error={error ? '' : undefined}>\n {value ? <Tag size={size} color={nativeTagVariant} className=\"shrink-0 relative z-10 pointer-events-none\">{label}</Tag> : <span className=\"text-fg-muted\">{placeholder ?? '選擇...'}</span>}\n {selectEl}\n <span className=\"flex-1\" />\n {clearEl}\n {chevronEl}\n </div>\n )\n }\n\n return (\n <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }),\n className)}\n data-field-mode=\"edit\" data-error={error ? '' : undefined}>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n {!StartIcon && SelectedOptIcon && value && <ItemPrefix><SelectedOptIcon size={iconSize} className=\"pointer-events-none\" aria-hidden /></ItemPrefix>}\n {selectEl}\n {clearEl}\n {chevronEl}\n </div>\n )\n }\n)\nNativeSelect.displayName = 'NativeSelect'\n\n// ── Custom Select (desktop — consumes SelectMenu) ────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(\n ({ mode, variant: variantProp, width, error: errorProp = false, size: sizeProp, options, groups, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, name, required, form, clearable = false, display = 'plain', startIcon: StartIcon, searchable = false, loading, minRows, emptyText, creatable = false, onCreate, createLabel, defaultOpen = false, onOpenChange, selectedItemRenderer, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp, 'aria-label': ariaLabel, onKeyDown: onKeyDownProp, style: styleProp,\n // 2026-07-14 API 策展 D:mobile-only props(allowlist 註記)desktop 顯式丟棄 — div trigger 無原生\n // 對應,不 spread 進 DOM(對稱 NativeSelect 丟棄 custom-path-only props 的既有 pattern)\n autoFocus: _autoFocus, autoComplete: _autoComplete, ...rest }, ref) => {\n const fieldCtx = useFieldContext()\n const error = useResolvedFieldInvalid(errorProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-05-31 #11:size 從 Field context cascade(對齊 Input/NumberInput + MUI FormControl)\n const size = useResolvedFieldSize(sizeProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode(prop > 有效 disabled > fieldCtx.mode > 'edit');修 <Field mode=\"view\"> 漏 cascade\n const resolvedMode = useResolvedFieldMode({ mode, disabled })\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n const iconSize = getIconSize(size)\n // 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(M17 對齊,取代自刻 isControlled pattern)。\n // Phase B codex 抓:之前 Custom clear 走 `onChange?.('')` 沒 setInternalValue → uncontrolled clear 失效。useControllable 統一 setter 修。\n // onChange forward coerce null → ''(consumer 簽名 `(value: string) => void`,null 是 internal empty signal)。\n const [value, setValue] = useControllable<string | null>({\n value: valueProp,\n defaultValue: defaultValue ?? null,\n onChange: onChange ? (next) => onChange(next ?? '') : undefined,\n })\n const showClear = clearable && value && resolvedMode === 'edit'\n const isTextDisplay = display === 'plain'\n\n const [open, setOpen] = React.useState(defaultOpen)\n const [search, setSearch] = React.useState('')\n const inputRef = React.useRef<HTMLInputElement>(null)\n // a11y(2026-07-04):listbox 容器 id——trigger aria-controls 指向 SelectMenu PopoverContent\n // (對齊姊妹元件 combobox.tsx:677 既有 canonical;React.useId SSR/CSR 穩定)。\n const listboxId = React.useId()\n // a11y(2026-07-05 D4):追蹤 cmdk active item id → searchable trigger input 綁 aria-activedescendant\n // (機制詳 select-menu.tsx useActiveDescendant docblock;必在 early return 前呼叫 — React #310 hook 順序)。\n const activeOptionId = useActiveDescendant(listboxId, open)\n\n // 關閉時清搜尋\n React.useEffect(() => { if (!open) setSearch('') }, [open])\n\n // **React #310 fix(2026-05-04)**:所有 hooks 必在任何 early return 前 call,\n // 否則 disabled→edit 切換時 hook count 變動 → React 死亡。\n // 原本 useMemo(L280, L291) 在 early return 之後 = latent bug,K13 觸發(filter Op 從 disabled\n // 變 edit 當 user 選欄位)。修法:把所有 useMemo 提到 early return 之前。\n const selectedOpt = options?.find(o => o.value === value)\n // 2026-05-06 v9.1:value 不在 options 也要顯示原值(不沉默丟失)。原 fallback `''` 致\n // SelectCell 開 edit 時若 cell value 不在當前 options(e.g. 上游資料漂移 / options async\n // 後到 / 跨 dataset),trigger 顯示空白 — user 報「value 不見」。對齊 ReadonlyDisplay 同\n // 級 fallback `?? value`。\n const selectedLabel = selectedOpt?.label ?? value ?? ''\n const SelectedIcon = selectedOpt?.icon\n // 截斷 tooltip(tooltip.spec.md:32 截斷 → 顯完整內容,僅實際截斷時):closed trigger text\n // display 的值 span 是截斷元素;trigger = 整個 combobox host(interactive host,hover 直達)、\n // 量測內層值 span — useTruncated 自組 pattern(truncated-text.spec.md「trigger 需自控」指定解;\n // 範本 inline-edit.tsx:251。tag display 由 Tag 內建 useTruncated 自管,不經此路)。\n // deps 含 open:searchable && open 切搜尋 input branch 時值 span 會 unmount,關閉重掛後\n // effect 需重跑重新 observe(Tag always-wrap 教訓:RO 卡在 detached node → isTruncated 凍結)。\n // hook 必在下方 resolvedMode early return 之前(React #310 hook-count 鐵律,見上方註解)。\n const { ref: triggerTruncationRef, isTruncated: triggerTruncated } = useTruncated<HTMLSpanElement>({\n deps: [open, isTextDisplay && value ? selectedLabel : ''],\n })\n const triggerTooltipActive = isTextDisplay && !!value && triggerTruncated && !open\n // ── 過濾選項 ──\n // 2026-07-18:filter 用 trim 過的 search,對齊 SelectMenu creatable 的 `search.trim()` create-row 判定 —\n // 否則尾隨空白(如 \"Bug \")會讓 filter 漏掉完全同名選項、SelectMenu 卻誤判「無同名」提議重複建立。\n const trimmedSearch = search.trim()\n const filteredOptions = searchable && trimmedSearch\n ? options.filter(o => o.label.toLowerCase().includes(trimmedSearch.toLowerCase()))\n : options\n // ── 轉換 SelectOption → SelectMenuOption(必在 early return 前) ──\n // Issue 4(2026-05-10):forward avatar / description / disabled SSOT(per SelectMenuOption schema)。\n const menuOptions: SelectMenuOption[] = React.useMemo(\n () => filteredOptions.map(opt => ({\n value: opt.value,\n label: opt.label,\n icon: isTextDisplay ? opt.icon : undefined,\n avatar: opt.avatar,\n description: opt.description,\n disabled: opt.disabled,\n group: opt.group,\n })),\n [filteredOptions, isTextDisplay]\n )\n // ── Tag display 自訂 label 渲染(必在 early return 前) ──\n const renderLabel = React.useMemo(() => {\n if (isTextDisplay) return undefined\n return (menuOpt: SelectMenuOption) => {\n const srcOpt = options.find(o => o.value === menuOpt.value)\n if (srcOpt?.tagVariant) {\n return <Tag size={size} color={srcOpt.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral'}>{menuOpt.label}</Tag>\n }\n return menuOpt.label\n }\n }, [isTextDisplay, options, size])\n\n // **React #310 fix v2(2026-05-04)**:`handleValueChange` useCallback 也必在 early return 前\n // 原本 L306(early return 後)→ disabled→edit 切換時 hook count 仍變 → #310 持續\n // 2026-05-21 D3:`useControllable` 統一 controlled / uncontrolled state + onChange forward,不再手動 if-branch。\n const handleValueChange = React.useCallback(\n (newValue: string | string[]) => {\n setValue(Array.isArray(newValue) ? newValue[0] : newValue)\n },\n [setValue]\n )\n\n // Early return AFTER all hooks(disabled / readonly / view mode 走 ReadonlyDisplay)\n if (resolvedMode !== 'edit') {\n return <ReadonlyDisplay mode={resolvedMode} variant={variant} width={width} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} selectedItemRenderer={selectedItemRenderer} />\n }\n\n // 2026-05-21 D3 Phase B codex 抓:Custom clear 用 setValue 不直接 onChange,uncontrolled clear 才能真清 internal state。\n const clearEl = showClear ? (\n <SelectClearButton size={size ?? 'md'} onClear={() => setValue('')} stopPropagation />\n ) : null\n\n const chevronEl = (\n <ItemSuffix>\n <ChevronDown size={iconSize} className={cn('text-fg-muted transition-transform motion-reduce:duration-0', open && 'rotate-180')} aria-hidden />\n </ItemSuffix>\n )\n\n // ── Hidden input form mirror(2026-07-13 D2 拍板 Option A;MUI SelectInput idiom)──\n // 桌機自訂 combobox 無原生 form control,`name` / `required` / `form` 歷史上被靜默丟棄\n // (mobile NativeSelect 卻經 {...props} 生效 = 雙路徑提交語義不對稱 footgun)。\n // 補 visually-hidden `<input>` 鏡像,讓桌機也參與原生表單(裸 <form> submit / FormData):\n // - value **單向鏡射**受控 state(useControllable)= projection,非第二 SSOT(M17);\n // 空值時 value=''(不採 Radix hidden <select> 完整 option tree — 其無空 option 時\n // default 第一項,radix-ui/primitives issue #3521 documented footgun)\n // - 原生 required 僅收顯式 `required` prop(裸表單 opt-in);fieldCtx.required 續走\n // aria-required + useFormValidation(RHF)方法論(form-validation.spec.md 規則 1-9),\n // 不對既有 Field+RHF consumer 注入瀏覽器原生 bubble\n // - onChange 唯一用途 = 瀏覽器 autofill:match 既有非 disabled option 才經 canonical\n // setter 寫回(對齊 MUI SelectInput handleChange 同款 guard),其餘輸入忽略\n // - aria-hidden + tabIndex={-1}:AT 與 Tab 序皆不見(semantics 由 role=combobox trigger\n // own);form-validation 規則 8 focus-first-error 以 DOM name 定位 → 可聚焦 mirror →\n // trigger focus-within 顯示 focus 邊框(桌機 Select 脫離「非 native 控件略過」fallback)\n // 世界級對照:MUI Select 非原生模式 opacity-0 hidden input 攜 name/required\n // (github.com/mui/material-ui .../Select/SelectInput.js;Select.test.js 官方測試涵蓋\n // required 阻止提交 + FormData 值)+ React Spectrum HiddenSelect(github.com/adobe/\n // react-spectrum .../@react-aria/select/src/HiddenSelect.tsx)+ Headless UI Listbox\n // name → hidden input kept in sync(headlessui.com/react/listbox#using-with-html-forms)。\n const hiddenInputEl = (\n <input\n type=\"text\"\n name={name}\n form={form}\n value={value ?? ''}\n onChange={(e) => {\n const matched = options.find((o) => o.value === e.target.value && !o.disabled)\n if (matched) setValue(matched.value)\n }}\n required={required || undefined}\n disabled={disabled}\n aria-hidden=\"true\"\n tabIndex={-1}\n className=\"absolute bottom-0 left-0 w-full opacity-0 pointer-events-none\"\n />\n )\n\n const triggerContent = (\n <CustomSelectTriggerContent\n searchable={searchable}\n open={open}\n isTextDisplay={isTextDisplay}\n size={size}\n value={value}\n selectedLabel={selectedLabel}\n selectedOpt={selectedOpt}\n SelectedIcon={SelectedIcon}\n StartIcon={StartIcon}\n iconSize={iconSize}\n placeholder={placeholder}\n search={search}\n setSearch={setSearch}\n inputRef={inputRef}\n activeDescendantId={activeOptionId}\n ariaLabel={ariaLabel}\n labelId={fieldCtx?.labelId}\n selectedItemRenderer={selectedItemRenderer}\n truncationRef={triggerTruncationRef}\n />\n )\n\n // hooks(filteredOptions / menuOptions / renderLabel / handleValueChange)已全 hoist(React #310 fix v2)\n\n const trigger = (\n <div\n // passthrough(2026-07-14 dim-9 修;同日 API 策展 D 收窄):SelectProps allowlist\n // (Pick<SelectHTMLAttributes>,見 SelectProps docblock)承諾的 data-* / onFocus /\n // onBlur 等 attrs,mobile NativeSelect 經 {...props} spread 到 <select> 生效,desktop\n // 原本完整列舉全丟 → 同一 <Select> API 隨 pointer type 靜默失效(consumer data-testid /\n // onBlur mobile 有、desktop 無)。rest 先 spread —— component 契約 attrs(role / aria-* /\n // id / tabIndex / onKeyDown / className)在後,不可被覆寫;consumer onKeyDown 經\n // onKeyDownProp 在 handler 內先跑(見下)。mobile-only attrs(autoFocus / autoComplete)\n // 已在 destructure 層丟棄不進 rest(div trigger 無原生語意;desktop 表單語意由 hidden\n // input mirror 承擔,見 D2 docblock)。\n {...(rest as unknown as React.HTMLAttributes<HTMLDivElement>)}\n ref={ref}\n id={idProp ?? fieldCtx?.id}\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-controls={listboxId}\n aria-label={ariaLabel}\n // a11y(2026-07-04):div-based role=combobox 的 <label for> 無效,接 FieldLabel labelId\n // (field-context.ts labelId jsDoc 明文);consumer aria-label 優先(對齊 slider.tsx:166 guard canonical)。\n aria-labelledby={ariaLabel ? undefined : fieldCtx?.labelId}\n aria-invalid={error || undefined}\n // D2(2026-07-13):顯式 required prop 同步進 aria-required(mirror 本身 aria-hidden,\n // AT 看不到其 required;語意由 trigger 播報 — 對齊 mobile 原生 <select required> 行為)\n aria-required={(required || fieldCtx?.required) || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }),\n !isTextDisplay && value && !searchable && tagPadding[size],\n // 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default\n // state machine `data-[state=open]:border-border-hover`(灰深)處理 open。\n // 2026-07-04 Q1:error border 同樣收進 fieldWrapperStyles error variant(error 勝 focus 色)。\n // D2(2026-07-13):`relative` = hidden input mirror 的 absolute 定位容器\n // (fieldWrapperStyles base 無 relative;對齊 NativeSelect tag 分支同款顯式加法)。\n 'relative cursor-pointer',\n className,\n )}\n style={styleProp || !isTextDisplay ? { ...styleProp, ...(!isTextDisplay ? { paddingRight: 'var(--field-px)' } : undefined) } : undefined}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n onKeyDown={(e) => {\n // passthrough(dim-9):consumer onKeyDown 先跑 —— 對齊 native path(<select> 上\n // consumer handler 同樣最先收到);component 導覽邏輯照舊在後。\n onKeyDownProp?.(e as unknown as React.KeyboardEvent<HTMLSelectElement>)\n // 2026-07-14 dim-10 修:內層清除按鈕(SelectClearButton → ItemInlineAction <button>)\n // 的 Enter/Space 會 bubble 到本 handler 被 preventDefault 吞掉 → 鍵盤無法清除。\n // 事件源自 descendant button 時不執行 trigger 鍵盤邏輯(保留原生 activation)。\n // 注意:不能一刀切 e.target !== e.currentTarget — searchable inline <input> 的\n // 鍵盤事件仍需下方 forwardKeyToListbox 轉送選單導覽。\n if (e.target !== e.currentTarget && (e.target as HTMLElement).closest?.('button')) return\n // 2026-07-05 D4 P0:open 後 ArrowUp/Down/Enter 轉送 cmdk root(見 select-menu.tsx\n // forwardKeyToListbox docblock — 原「open 後不攔讓方向鍵導覽」在跨 DOM 子樹機制上不成立)\n if (open && forwardKeyToListbox(listboxId, e)) return\n if (e.key === 'Enter' || e.key === ' ') {\n // 2026-06-11 P0 a11y(R2 deep-audit):原 guard `!searchable` 連「關閉時的鍵盤開啟」一起擋\n // → searchable Select / PeoplePicker single 鍵盤打不開(WCAG 2.1.1)。原意只是開啟後\n // 別吃掉搜尋框的 Space/Enter → 正確 guard = !open(關閉才攔;開啟後不干擾 input 輸入)。\n if (!open) { e.preventDefault(); setOpen(true) }\n }\n // APG combobox 展開鍵:ArrowDown 也可開(對齊 combobox.tsx 同 pattern;open 後不攔讓方向鍵導覽選單)\n if (e.key === 'ArrowDown' && !open) { e.preventDefault(); setOpen(true) }\n if (e.key === 'Escape') setOpen(false)\n }}\n >\n {triggerContent}\n {clearEl}\n {chevronEl}\n {hiddenInputEl}\n </div>\n )\n\n return (\n // 截斷 tooltip:恆 wrap(truncated-text.spec.md always-wrap),未截斷或選單開啟時 open={false}\n // 靜默。TooltipTrigger 放 SelectMenu 的 PopoverTrigger asChild 內側(緊貼 trigger div):\n // PopoverTrigger 注入的 data-state(Field open 邊框樣式依賴)在 TooltipTrigger 內以\n // triggerProps 後 spread 勝出;反序會被 tooltip 的 data-state 蓋掉。Tooltip root / Content\n // 都不產 DOM(Content portal),SelectMenu children 結構與佈局不變。\n <Tooltip open={triggerTooltipActive ? undefined : false}>\n <SelectMenu\n options={menuOptions}\n groups={groups}\n value={value ?? null}\n onValueChange={handleValueChange}\n searchable={false}\n creatable={creatable}\n onCreate={onCreate}\n createLabel={createLabel}\n search={searchable ? search : undefined}\n onSearchChange={searchable ? setSearch : undefined}\n loading={loading}\n emptyText={emptyText}\n size={size}\n minRows={minRows}\n open={open}\n onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}\n contentId={listboxId}\n renderLabel={renderLabel}\n onOpenAutoFocus={searchable ? (e) => { e.preventDefault(); inputRef.current?.focus() } : undefined}\n >\n <TooltipTrigger asChild>\n {trigger}\n </TooltipTrigger>\n </SelectMenu>\n <TooltipContent>{triggerTooltipActive ? selectedLabel : null}</TooltipContent>\n </Tooltip>\n )\n }\n)\nCustomSelect.displayName = 'CustomSelect'\n\n// ── Public component(自動偵測 mobile / desktop)──────────────────────────────\n\nconst Select = React.forwardRef<HTMLSelectElement | HTMLDivElement, SelectProps>(\n (props, ref) => {\n const isMobile = useIsTouchDevice()\n\n if (isMobile) {\n return <NativeSelect ref={ref as React.Ref<HTMLSelectElement>} {...props} />\n }\n\n return <CustomSelect ref={ref as React.Ref<HTMLDivElement>} {...props} />\n }\n)\nSelect.displayName = 'Select'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const selectMeta = {\n component: 'Select',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n // 'active' 移除 — trigger 走 Field chrome、menu row 走 MenuItem(其 meta 無 active)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { Select }\n"],"names":["tVariant"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"select.js","sources":["../../../src/components/Select/select.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — Select 含 3 子元件(NativeSelect/CustomSelect/ReadonlyDisplay)+ helpers + 4-mode renderer + Field SSOT consumption,split-into-files 會破壞 file-local helper closure\n// @renderer-symmetry-allow: 2026-07-08 WM 戰役 A 案回歸修正 — ReadonlyDisplay 現已消費 selectedItemRenderer(view bare-span / D-path / readonly / disabled 四分支),對齊 field-controls.spec.md 共享 contract (a)「view/readonly/disabled/edit 4 mode 共享同一 renderer」。前 note「display→edit unify deferred」已兌現(值內容層);chrome 結構 unify(D-path)仍為 opt-in showDisplayEndIcon。\nimport * as React from 'react'\nimport { X, ChevronDown } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { Tag } from '@/design-system/components/Tag/tag'\nimport { ItemInlineAction, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { TruncatedText } from '@/design-system/patterns/element-anatomy/truncated-text'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'\nimport { useTruncated } from '@/design-system/hooks/use-truncated'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { SelectMenu, forwardKeyToListbox, useActiveDescendant, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'\nimport { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n// ── Tag padding per size ────────────────────────────────────────────────────\nconst tagPadding: Record<string, string> = {\n sm: 'px-[calc((var(--field-height-sm)_-_1.25rem)_/_2)]',\n md: 'px-[calc((var(--field-height-md)_-_1.5rem)_/_2)]',\n lg: 'px-[calc((var(--field-height-lg)_-_1.5rem)_/_2)]',\n}\n\n// ── Display ─────────────────────────────────────────────────────────────────\n\n/**\n * Select 用的 option schema(2026-05-10 Issue 4 + post-prune unify):**explicit extends\n * SelectMenuOption(primitive SSOT)** — 任何 SelectMenuOption 加 field 都自動繼承,不會 drift。\n *\n * Why `extends SelectMenuOption`(per user 「全盤檢查避免下次又改壞或是偏移」要求):\n * - **schema SSOT 機械強制**:TypeScript inheritance 跟著 primitive 走,wrapper consumer 永遠\n * 拿得到 primitive 所有 surface field\n * - **Hook lint**(M30 `check_wrapper_primitive_schema_drift.sh`):grep `interface .*Option`\n * 未 `extends` SelectMenuOption / 同名重複 declare 直接 BLOCK\n *\n * Wrapper-only field(`tagVariant`)— Select 獨有 `display='tag'` 用,SelectMenu primitive 不該知道\n * 此 wrapper-only concern,所以 wrapper 層 extend 加上,不污染 primitive。\n *\n * 對齊 Polaris ChoiceList / Material Autocomplete / Carbon Dropdown 的 wrapper-vs-primitive\n * schema-extension idiom。\n */\nexport interface SelectOption extends SelectMenuOption {\n /** Tag 模式的顏色。只在 display='tag' 時生效,對應 Tag 的 variant。Wrapper-only。 */\n tagVariant?: string\n}\n\n/** 分組設定 — 對齊 SelectMenuGroupConfig SSOT */\nexport interface SelectGroupConfig {\n key: string\n label: string\n}\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * 原生 `<select>` 屬性 allowlist(2026-07-14 API 策展 D,user 拍板「全部收窄」)。\n *\n * 舊契約 `extends Omit<SelectHTMLAttributes, ...>` 型別承諾**全部**原生 select 屬性,但桌機\n * 自訂 combobox(div trigger + hidden input mirror)只有部分真生效 — 其餘 silent no-op /\n * 被元件內部覆寫,「型別承諾 ≠ 實作出口」= footgun。改 `Pick<>` 顯式 allowlist,**只承諾\n * 兩路徑(desktop CustomSelect / mobile NativeSelect)皆有實作出口的屬性**,盤點以 code 為準:\n *\n * - `id` — 桌機 trigger div / mobile `<select>`,皆 `idProp ?? fieldCtx?.id`\n * - `name` / `form` / `required` — 桌機經 hidden input mirror(D2 2026-07-13,見 hiddenInputEl\n * docblock)/ mobile 原生 `<select>` spread\n * - `disabled` — 兩路徑 `useResolvedFieldDisabled` cascade → mode 分流\n * - `className` / `style` — 兩路徑 field wrapper\n * - `onFocus` / `onBlur` — 桌機 rest spread 到 focusable trigger(tabIndex=0)/ mobile spread\n * - `onKeyDown` — 桌機 consumer handler 先跑再走元件導覽(dim-9)/ mobile 原生\n * - `aria-label` / `aria-describedby` / `aria-errormessage` — 兩路徑顯式接線(fieldCtx fallback)\n * - `autoFocus` / `autoComplete` — **mobile-only**:NativeSelect 原生 `<select>` 生效;桌機\n * custom combobox 無原生對應 = documented no-op(桌機 autofill 由 hidden input mirror 的\n * option-match onChange guard 承擔)\n * - `data-*` — 兩路徑 spread passthrough(dim-9 契約;index signature 顯式承諾,見下)\n *\n * 被砍者(原 extends 承諾但從未生效 / 被元件覆寫,不再進型別):`multiple` / `children` /\n * `tabIndex`(桌機恆 0)/ `aria-labelledby`(桌機由 fieldCtx.labelId / aria-label 管理,\n * consumer 值會被覆寫)/ 其餘 generic HTMLAttributes(title / dir / onClick 等)。\n * 需要時先接線(兩路徑等效)再入表,**不回 extends 全開**。\n *\n * 世界級對照:自訂 combobox 皆走 curated 原生屬性子集,無人 extends 全部 SelectHTMLAttributes —\n * Radix Select.Root 顯式列舉 name/disabled/required/form/autoComplete\n * (radix-ui.com/primitives/docs/components/select#root)/ Headless UI Listbox name/form/disabled\n * (headlessui.com/react/listbox#using-with-html-forms)/ React Aria Select name/autoComplete/\n * isDisabled/isRequired(react-spectrum.adobe.com/react-aria/Select.html)。\n */\nexport interface SelectProps\n extends Pick<\n React.SelectHTMLAttributes<HTMLSelectElement>,\n // ── 兩路徑皆真生效 ──\n | 'id' | 'className' | 'style'\n | 'name' | 'form' | 'required' | 'disabled'\n | 'onFocus' | 'onBlur' | 'onKeyDown'\n | 'aria-label' | 'aria-describedby' | 'aria-errormessage'\n // ── mobile-only(NativeSelect 原生 <select> 生效;桌機 custom combobox documented no-op)──\n | 'autoFocus' | 'autoComplete'\n > {\n /** `data-*` passthrough(桌機 trigger div rest spread / mobile `<select>` spread;dim-9 契約)。 */\n [dataAttribute: `data-${string}`]: unknown\n mode?: FieldMode\n /** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */\n variant?: FieldVariant\n /** 寬度軸 — `fill` 填滿容器(default)/ `hug` 依內容收縮:value 寬 + slot 元素寬 + gap +\n * field 內 padding;chrome 與互動不變。場景 = detail pane inline metadata。\n * SSOT → field-controls.spec.md「寬度軸(width: fill / hug)」。 */\n width?: FieldWidth\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n options: SelectOption[]\n /** 分組顯示(對齊 SelectMenu groups SSOT)。option.group 對應 groups[].key */\n groups?: SelectGroupConfig[]\n /** Controlled value(consumer 自管 state)。傳 `value` + `onChange` 表示 controlled mode。 */\n value?: string | null\n /** Uncontrolled 初始值(2026-05-21 D3 audit add per user verbatim「決策三照妳建議」+「都給我做到好」)。\n * 不傳 `value` 時 Select 自管 internal state,以 `defaultValue` 為初始值,選變更時 fire `onChange`\n * callback 通知 consumer(但 state 仍歸 Select)。對齊 Radix Select(`defaultValue`)+ shadcn Input\n * (`defaultValue`)+ React `<input>` dual-mode canonical。\n * 互斥規則:同時傳 `value` + `defaultValue` 走 controlled(value 勝),`defaultValue` 僅 first-mount 用。 */\n defaultValue?: string | null\n onChange?: (value: string) => void\n placeholder?: string\n clearable?: boolean\n display?: 'plain' | 'tag'\n startIcon?: LucideIcon\n /** 啟用搜尋(desktop 時 field 變 input,打字即篩選) */\n searchable?: boolean\n /** Loading state(2026-05-15 audit B fix;2026-07-04 Q3 拍板措辭修訂)。\n * Forward 給 SelectMenu primitive SSOT;spinner 只在**無可顯示選項時**佔 empty slot(cmdk CommandEmpty\n * 機制)— 已有 stale options 時保留顯示不清空(對齊 MUI Autocomplete「only if there are no suggestions」)。\n * Trigger 不變(chevron 保留 user 隨時可點開)。*/\n loading?: boolean\n\n /** Menu list 最小列數(空狀態 / 選項少時的視覺一致 reserve)。預設 3 — 選項 < 3 時顯式縮(如 And/Or 兩選項) */\n minRows?: number\n /** Initial open state(uncontrolled)。對齊 Radix Popover defaultOpen canonical;DataTable cell-as-input\n * click → 1 step open menu(Airtable / Notion canonical),consumer pass `defaultOpen` 達成。\n * Note:Native Select(mobile)無 popover 概念,此 prop 僅 Custom path 生效。 */\n defaultOpen?: boolean\n /** open state 變更 callback(對齊 Radix Popover onOpenChange canonical)。\n * DataTable cell-as-input 用:open=false 時 cell 自動 exit edit mode(避免 dismiss 後卡住)。 */\n onOpenChange?: (open: boolean) => void\n /**\n * View mode 顯 picker intrinsic end icon(2026-05-08 D path Phase 1)。\n * 預設 false:`mode=\"view\"` 純展示 bare span(向後相容)。\n * `variant=\"naked\" && mode=\"view\"` 場景(DataTable cell)opt-in 設 true → wrap 進\n * Field naked-view + 渲 ChevronDown ItemSuffix。**只 view mode 生效**;readonly /\n * disabled / edit 已有 Field wrapper + suffix(不受此 prop 影響)。\n * Authority:`data-table.spec.md:204` + `inline-action.spec.md:157`「Field family endAction(自動繼承)」。\n * @default false\n */\n showDisplayEndIcon?: boolean\n /**\n * 「已選項目」客製 render(2026-05-07 v15.5;2026-07-08 A 案回歸修正擴及 4 mode)。\n *\n * 設了 → 已選值不走純文字 / Tag 預設 path,改用 consumer 提供的 ReactNode(收 selectedOpt)。\n * **view / readonly / disabled / edit 4 mode 共享同一 renderer**(field-controls.spec.md\n * 共享 contract (a),禁 edit-only)— renderer 輸出屬「值內容」(status icon+text 等語意呈現),\n * view 態照常渲染;A 案撤的是 affordance(chevron/outline),非值內容(field.spec.md L6 分層)。\n * Searchable+open 仍走 input(搜尋優先)。Empty value(no selection)仍走 placeholder。\n *\n * 用例:PeoplePicker 用此 slot 把 single 選中的 person render 成 PersonDisplay\n * (avatar + name)而非純文字 label;DataTable status 欄 icon+text。對齊 PeoplePicker = Select wrapper SSOT。\n */\n selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode\n /** 搜尋無結果提示(2026-07-04 Q4 拍板接線 — 原 spec 宣稱可覆寫但 prop 從未轉發)。\n * Forward 給 SelectMenu primitive SSOT(default 在 SelectMenu 層「沒有符合的選項」);\n * 僅 Custom path(searchable dropdown)生效,Native path 無搜尋空狀態。\n * 對齊 MUI noOptionsText / Ant notFoundContent / Polaris emptyState 三家公開覆寫點共識。 */\n emptyText?: string\n /**\n * 可建立新選項(creatable,2026-07-18 決策11 user 拍板 forward 到 Select)。**需搭配 `searchable`**\n * ——搜尋非空且無完全同名既有選項時,dropdown 顯 create row(Plus + `createLabel`)。\n * 邏輯/顯示/互動 SSOT 住在 SelectMenu(Select 的 trigger 搜尋以受控 `search` 驅動 SelectMenu creatable\n * 顯隱)。與 Combobox.creatable 同一底層。非 searchable 時無 create 通道(無輸入處)。 */\n creatable?: boolean\n /** 建立新選項 callback(收使用者輸入的搜尋字串)。 */\n onCreate?: (value: string) => void\n /** create row 的 label 格式,預設 '直接使用「{query}」'(forward SelectMenu SSOT)。 */\n createLabel?: (query: string) => string\n}\n\n// ── Icon / size helpers ─────────────────────────────────────────────────────\n// 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反)\nconst getIconSize = (size: string) => ICON_SIZE[size as 'sm' | 'md' | 'lg']\n\n// ── Shared sub-components ───────────────────────────────────────────────────\n\n/**\n * Inline clear button for Select trigger.\n * 共用 SSOT — Native + Custom 兩變體統一消費。差別僅 onClick 內是否 stopPropagation\n * (Custom trigger 是 combobox `<div>`,點 clear 不可冒泡到打開 menu;Native `<select>` 自有原生\n * 行為,不需 stopPropagation)。\n *\n * 消費的 SSOT:\n * - patterns/element-anatomy/item-anatomy.spec.md → ItemInlineAction(canonical row inline action)\n */\nfunction SelectClearButton({\n size,\n onClear,\n stopPropagation = false,\n}: {\n size: 'sm' | 'md' | 'lg'\n onClear: () => void\n stopPropagation?: boolean\n}) {\n return (\n <span className=\"relative z-10\">\n <ItemInlineAction\n size={size}\n action={{\n icon: X,\n label: '清除選取', // i18n-allow: DS default inline-action label\n onClick: stopPropagation ? (e) => { e?.stopPropagation(); onClear() } : () => onClear(),\n }}\n />\n </span>\n )\n}\nSelectClearButton.displayName = 'SelectClearButton'\n\n/**\n * Trigger content for CustomSelect — 三種顯示模式分支(searchable+open / text / tag)\n * 抽出降低 `CustomSelect` forwardRef body 長度;邏輯本質是純展示分流,無 hook / ref。\n */\nfunction CustomSelectTriggerContent({\n searchable,\n open,\n isTextDisplay,\n size,\n value,\n selectedLabel,\n selectedOpt,\n SelectedIcon,\n StartIcon,\n iconSize,\n placeholder,\n search,\n setSearch,\n inputRef,\n activeDescendantId,\n ariaLabel,\n labelId,\n selectedItemRenderer,\n truncationRef,\n}: {\n searchable: boolean\n open: boolean\n isTextDisplay: boolean\n size: 'sm' | 'md' | 'lg'\n value?: string | null\n selectedLabel: string\n selectedOpt?: SelectOption\n SelectedIcon?: LucideIcon\n StartIcon?: LucideIcon\n iconSize: number\n placeholder?: string\n search: string\n setSearch: (v: string) => void\n inputRef: React.RefObject<HTMLInputElement | null>\n activeDescendantId?: string\n ariaLabel?: string\n labelId?: string\n selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode\n /** 截斷 tooltip 量測點(text display 的值 span;tooltip trigger 在外層 combobox host,見 CustomSelect triggerTruncationRef 註解)*/\n truncationRef?: React.Ref<HTMLSpanElement>\n}): React.ReactNode {\n // Searchable + open: 顯示搜尋 input\n // 2026-05-15 Bug 2 fix(Claude+Codex Step 5 比稿 consensus,user verbatim「就 A」):\n // 撤掉 native `<input placeholder=selectedLabel>` 不可靠 ellipsis renderer(browser-specific\n // placeholder painting,user 抓「placeholder 直接被截掉沒 ellipsis」)。改 span overlay:\n // - input native placeholder 限「搜尋…」/「請選擇人員」trigger empty hint(無 selectedLabel)\n // - sibling `<span aria-hidden pointer-events-none absolute inset-0 truncate>` 在 search='' 且\n // 有 selectedLabel 時 overlay 顯該人名(memory aid,truncate-with-ellipsis 可控)\n // 對齊 spec.md §B row 4「open + inline-search + 選 1 人 → input cursor + placeholder = 該人名 + ellipsis」。\n // a11y(2026-07-05 backlog 補接):搜尋 input 是 open 時實際聚焦的元素,accessible name 必接在\n // input 本身(原 codex Q2 guard 註解宣稱「來自 field/label/aria-label」,但接線全在外層 trigger\n // div,焦點所在的 input 無 name)— aria-labelledby 接 FieldLabel labelId、consumer aria-label\n // 優先(對齊 trigger div 同款 guard),兩者皆無時 fallback aria-label「搜尋選項」(對齊\n // combobox.tsx searchAriaLabel canonical default);**不**依賴 placeholder 當 label;overlay\n // span aria-hidden + pointer-events-none。APG「combobox role 移到 input 本身」屬中期重構另案。\n if (searchable && open) {\n const triggerEmptyPlaceholder = placeholder || '搜尋…' // i18n-allow: DS fallback\n const showSelectedOverlay = !search && selectedLabel\n return (\n <span className=\"relative flex-1 min-w-0 inline-flex items-center\">\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n <input\n ref={inputRef as React.RefObject<HTMLInputElement>}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n // a11y(2026-07-05 D4):cmdk active item id(useActiveDescendant)→ SR 播報方向鍵導覽中的 option\n aria-activedescendant={activeDescendantId}\n // accessible name:aria-labelledby(Field label)> consumer aria-label > DS default(accname 優先序)\n aria-label={ariaLabel ?? '搜尋選項'} // i18n-allow: DS default(對齊 combobox.tsx searchAriaLabel canonical)\n aria-labelledby={ariaLabel ? undefined : labelId}\n // Native placeholder 限 trigger empty hint(無 selectedLabel 時);若已 selected,留空交給 overlay span\n placeholder={showSelectedOverlay ? '' : triggerEmptyPlaceholder}\n className={cn(bareInputStyles, 'cursor-text')}\n autoFocus\n />\n {showSelectedOverlay && (\n // 2026-05-16 Bug B 真 root cause fix(Claude+Codex M31 Step 5 比稿 consensus,user verbatim\n // 「修了一百次還沒好」+ codex cite W3C CSS Overflow / MDN / Mozilla Bug 972664#c1):\n // 原 `inline-flex items-center truncate` 套同一 span,text 變 anonymous flex item →\n // `text-overflow:ellipsis` 對 anonymous item 不 styleable → ellipsis dots 不可見(text 純 clip)。\n // 對齊 `person-display.tsx:148` 既有 DS canonical:outer flex container + inner truncate 真實 box。\n // DS-wide grep 29 個 truncate 都遵此 pattern,只本處違反 — 修齊。\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 flex items-center text-fg-muted\"\n >\n <span className=\"min-w-0 flex-1 truncate\">{selectedLabel}</span>\n </span>\n )}\n </span>\n )\n }\n // **selectedItemRenderer slot**(2026-05-07 v15.5):consumer 客製 selected display(e.g.\n // PeoplePicker 接 PersonDisplay)。優先於 isTextDisplay / Tag 預設 path,但 empty value\n // 仍走 placeholder。對齊 PeoplePicker = Select wrapper SSOT。\n if (selectedItemRenderer && value && selectedOpt) {\n return (\n <>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n {/* 2026-05-14 item-anatomy SSOT fix(per codex H2 propagation 斷點):加 nakedCellRowModeAlign\n → autoRowHeight cell 內 selected renderer 也對齊 first-line,不再 vertical-center 整 row。 */}\n <span className={cn(\"flex-1 min-w-0 inline-flex items-center\", nakedCellRowModeAlign)}>{selectedItemRenderer(selectedOpt)}</span>\n </>\n )\n }\n // Text display: 純文字 + optional value icon\n if (isTextDisplay) {\n return (\n <>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n {!StartIcon && SelectedIcon && value && <ItemPrefix><SelectedIcon size={iconSize} className=\"pointer-events-none\" aria-hidden /></ItemPrefix>}\n <span ref={truncationRef} className={cn('flex-1 min-w-0 truncate', !value && 'text-fg-muted')}>\n {value ? selectedLabel : (placeholder ?? '選擇…')}\n </span>\n </>\n )\n }\n // Tag display: 用 option 的 tagVariant\n return (\n <>\n {value && selectedOpt?.tagVariant\n ? <Tag size={size} color={selectedOpt.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral'} className=\"shrink-0 pointer-events-none\">{selectedLabel}</Tag>\n : value\n ? <Tag size={size} className=\"shrink-0 pointer-events-none\">{selectedLabel}</Tag>\n : <span className=\"text-fg-muted\">{placeholder ?? '選擇…'}</span>\n }\n <span className=\"flex-1\" />\n </>\n )\n}\nCustomSelectTriggerContent.displayName = 'CustomSelectTriggerContent'\n\n// ── Shared readonly/disabled/view render ─────────────────────────────────\nfunction ReadonlyDisplay({\n mode, variant: variantProp, width, size, options, value, display, startIcon: StartIcon, className, placeholder, showDisplayEndIcon, selectedItemRenderer,\n}: Pick<SelectProps, 'mode' | 'width' | 'size' | 'options' | 'value' | 'display' | 'startIcon' | 'className' | 'placeholder' | 'showDisplayEndIcon' | 'selectedItemRenderer'> & {\n /** @internal 2026-07-14 API 策展 E:內部 render helper 吃 FieldVariantInternal(naked 由 cell-registry 通道傳入)*/\n variant?: FieldVariantInternal\n}) {\n const resolvedMode = mode ?? 'readonly'\n const emptyDisplay = useFieldEmptyDisplay()\n const variant = variantProp ?? 'default'\n const sz = size ?? 'md'\n const iconSize = getIconSize(sz)\n // selectedOpt 提前 hoist(2026-07-08 A 案回歸修正):view / D-path / readonly / disabled\n // 四分支都需要 — renderer 值內容(field-controls.spec.md contract (a) 4-mode 共享)+ tagVariant 查找。\n const selectedOpt = options?.find(o => o.value === value)\n const label = selectedOpt?.label ?? value\n const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'\n const isTextDisplay = display !== 'tag'\n // K10+K14 fix(2026-05-04):disabled mode placeholder/empty 顯示色 → fg-disabled(neutral-6),非 fg-muted(neutral-7)\n // user canonical:disabled 顯著性優於 muted。同時 plain mode 必須 respect placeholder prop(之前忽略 = bug)\n const emptyColorCls = fieldEmptyColorClass(resolvedMode)\n const emptyText = placeholder ?? emptyDisplay\n\n // mode='view':2 path(2026-05-08 D path Phase 1 Select canary)\n // ❌ 預設(無 showDisplayEndIcon):純內容輸出 bare span/Tag(原行為,backward compat)\n // 對齊原 SelectDisplay sub-component(retired)。readonly / disabled 仍走下方 fieldWrapperStyles。\n // ✅ showDisplayEndIcon=true(DataTable cell opt-in):Field naked-view wrapper +\n // ChevronDown ItemSuffix。SSOT canonical 跟 readonly/edit/disabled mode 同 DOM 結構。\n // Authority: data-table.spec.md:204 + inline-action.spec.md:157「Field family endAction」\n if (resolvedMode === 'view') {\n if (!showDisplayEndIcon) {\n // 2026-05-14 I2 fix(spec contract (e) view typography canonical):bare span 必套\n // `fieldDisplayTextClass(sz)`(sm/md→text-body,lg→text-body-lg)— 對齊跨 Field\n // family view 視覺尺寸統一。\n if (!value) return <span className={cn(fieldDisplayTextClass(sz), emptyColorCls, className)}>{emptyText}</span>\n // 2026-07-08 A 案回歸修正:selectedItemRenderer(值內容:status icon+text 等語意呈現)\n // view 態照常渲染 — 無 chrome 無 chevron,只有值內容本身。分層原則(field.spec.md L6):\n // 本 renderer 歸「值內容」,恆顯;A 案撤的是 affordance(chevron/outline),非值內容。\n // 對齊 field-controls.spec.md 共享 contract (a)「4 mode 共享同一 renderer(禁 edit-only)」。\n if (selectedItemRenderer && selectedOpt) {\n return (\n <span className={cn(fieldDisplayTextClass(sz), 'inline-flex items-center min-w-0', nakedCellRowModeAlign, className)}>\n {selectedItemRenderer(selectedOpt)}\n </span>\n )\n }\n // 截斷必附 tooltip(tooltip.spec.md:32)— 改消費 truncated-text primitive(同 input.tsx:196 同型修;\n // display='tag' 路徑由 Tag 內建 useTruncated 自管,text 路徑原本裸 truncate = gap)\n if (isTextDisplay) return <TruncatedText className={cn(fieldDisplayTextClass(sz), className)}>{label}</TruncatedText>\n const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined\n return <Tag size={sz} color={tVariant} className={className}>{label}</Tag>\n }\n // D path opt-in: Field naked-view wrapper + ItemSuffix ChevronDown\n const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'view', variant, width, size: sz }), value && !isTextDisplay && tagPadding[sz], className)}\n data-field-mode=\"view\"\n >\n {selectedItemRenderer && value && selectedOpt ? (\n // renderer 優先(同 CustomSelectTriggerContent 優先序):值內容歸 renderer,chevron 歸 D-path chrome\n <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign)}>\n {selectedItemRenderer(selectedOpt)}\n </span>\n ) : isTextDisplay ? (\n // 截斷必附 tooltip(tooltip.spec.md:32;data-table.spec.md:183 view 態元件自管 truncation)\n <TruncatedText className={cn(bareInputStyles, 'flex-1 min-w-0', !value && emptyColorCls)}>\n {value ? label : emptyText}\n </TruncatedText>\n ) : value ? (\n <Tag size={sz} color={tVariant}>{label}</Tag>\n ) : (\n <span className={cn('flex-1 min-w-0', emptyColorCls)}>{emptyText}</span>\n )}\n <ItemSuffix><ChevronDown size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemSuffix>\n </div>\n )\n }\n\n // 2026-06-26 類型身份 indicator 規則:edit 顯示 / readonly 不顯示(純值、不可開下拉,箭頭會誤導) /\n // disabled 保留(fg-disabled,對齊原生 <select disabled> 灰示箭頭 + Accordion M24 precedent)。\n // naked cell 情境依 showDisplayEndIcon = isEditable,維持 2026-05-10 cell canonical「非可編欄不顯」。\n // aria-disabled:styled-disabled(非 native disabled 元素)需明告 AT「inactive」,同時讓 axe 正確\n // 豁免 disabled 文字的 color-contrast(WCAG 1.4.3 inactive UI 例外)。\n const showIndicator = variant === 'naked' ? !!showDisplayEndIcon : resolvedMode === 'disabled'\n const ariaDisabled = resolvedMode === 'disabled' ? true : undefined\n\n // 2026-07-08 A 案回歸修正:readonly / disabled 同樣消費 selectedItemRenderer(值內容 4-mode\n // 共享,field-controls.spec.md contract (a);對照 PeoplePicker readonly/disabled 渲 PersonDisplay\n // 先例)。renderer 優先於 isTextDisplay / Tag 預設 path(同 CustomSelectTriggerContent 優先序);\n // disabled 時 wrapper span 帶 text-fg-disabled 供未自帶色的文字繼承(M24 disabled > muted)。\n if (selectedItemRenderer && value && selectedOpt) {\n return (\n <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}\n <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign, resolvedMode === 'disabled' && 'text-fg-disabled')}>\n {selectedItemRenderer(selectedOpt)}\n </span>\n {showIndicator && <ItemSuffix className=\"pointer-events-none\"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}\n </div>\n )\n }\n\n if (isTextDisplay) {\n return (\n <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}\n {/* 截斷必附 tooltip(tooltip.spec.md:32)— readonly/disabled 無下拉入口,tooltip 是唯一補救路徑 */}\n <TruncatedText className={cn('flex-1', resolvedMode === 'disabled' && 'text-fg-disabled')}>\n {value ? label : <span className={emptyColorCls}>{emptyText}</span>}\n </TruncatedText>\n {showIndicator && <ItemSuffix className=\"pointer-events-none\"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}\n </div>\n )\n }\n\n const tagVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined\n\n return (\n <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), value && tagPadding[sz], className)} style={{ paddingRight: 'var(--field-px)' }} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>\n {value ? <Tag size={sz} color={tagVariant}>{label}</Tag> : <span className={emptyColorCls}>{emptyText}</span>}\n {showIndicator && <ItemSuffix className=\"pointer-events-none\"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}\n </div>\n )\n}\n\n// ── Native Select (mobile) ─────────────────────────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(\n ({ mode, variant: variantProp, width, error: errorProp = false, size: sizeProp, options, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp,\n // 2026-07-04:Custom-path-only props 顯式 destructure 丟棄(不 spread 到原生 <select> 消 React\n // unknown-prop warning);各 prop docblock 已註「僅 Custom path 生效」語意\n // 2026-07-08 A 案回歸修正:selectedItemRenderer 從丟棄名單移出 — Native path 的\n // ReadonlyDisplay(view/readonly/disabled)同樣消費值內容 renderer(contract (a) 4-mode\n // 共享,值內容不因 pointer type 而異);native <select> edit 路徑仍不消費(原生 option 無法客製 render)。\n searchable: _searchable, groups: _groups, loading: _loading, minRows: _minRows, emptyText: _emptyText, creatable: _creatable, onCreate: _onCreate, createLabel: _createLabel, defaultOpen: _defaultOpen, onOpenChange: _onOpenChange, selectedItemRenderer,\n ...props }, ref) => {\n const fieldCtx = useFieldContext()\n const error = useResolvedFieldInvalid(errorProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-05-31 #11:size 從 Field context cascade(對齊 Input/NumberInput + MUI FormControl)\n const size = useResolvedFieldSize(sizeProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode(prop > 有效 disabled > fieldCtx.mode > 'edit');修 <Field mode=\"view\"> 漏 cascade\n const resolvedMode = useResolvedFieldMode({ mode, disabled })\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n const iconSize = getIconSize(size)\n // 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(同 CustomSelect)\n const [value, setValue] = useControllable<string | null>({\n value: valueProp,\n defaultValue: defaultValue ?? null,\n onChange: onChange ? (next) => onChange(next ?? '') : undefined,\n })\n const handleNativeChange = (v: string) => setValue(v)\n const showClear = clearable && value && resolvedMode === 'edit'\n const isTextDisplay = display === 'plain'\n const selectRef = React.useRef<HTMLSelectElement | null>(null)\n const setSelectRef = React.useCallback((el: HTMLSelectElement | null) => {\n selectRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLSelectElement | null>).current = el\n }, [ref])\n\n if (resolvedMode !== 'edit') {\n return <ReadonlyDisplay mode={resolvedMode} variant={variant} width={width} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} selectedItemRenderer={selectedItemRenderer} />\n }\n\n const selectedOpt = options?.find(o => o.value === value)\n const selectEl = (\n <select\n ref={setSelectRef}\n id={idProp ?? fieldCtx?.id}\n value={value ?? ''}\n onChange={(e) => handleNativeChange(e.target.value)}\n disabled={disabled}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n className={cn(bareInputStyles, 'cursor-pointer appearance-none', !value && 'text-fg-muted', value && (!isTextDisplay || (selectedItemRenderer && selectedOpt)) && 'absolute inset-0 w-full h-full opacity-0 z-0')}\n {...props}\n >\n {placeholder && <option value=\"\" disabled>{placeholder}</option>}\n {/* disabled forward(2026-07-04):對齊桌機 path menuOptions disabled forward — 觸控原生 option 同樣不可選(spec「disabled 選項」邊界案例) */}\n {options.map(opt => <option key={opt.value} value={opt.value} disabled={opt.disabled}>{opt.label}</option>)}\n </select>\n )\n\n const clearEl = showClear ? (\n <SelectClearButton size={size ?? 'md'} onClear={() => handleNativeChange('')} />\n ) : null\n\n const chevronEl = (\n <ItemSuffix className=\"relative z-10 pointer-events-none\">\n <ChevronDown size={iconSize} className=\"text-fg-muted\" aria-hidden />\n </ItemSuffix>\n )\n const label = selectedOpt?.label ?? value\n const nativeTagVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined\n const SelectedOptIcon = selectedOpt?.icon\n\n // Display-layer parity(2026-08-05 user 拍板):selectedItemRenderer 有值 → 渲 renderer 輸出為\n // 可見層(PeoplePicker 單人手機 = avatar+人名,與桌機同 SSOT),native <select> 沿 tag 模式既有\n // pattern 作透明 overlay(tap 開原生 picker)。原 native edit 只顯 option 純文字 = touch 分支\n // 從未接 display 層的病根。空值 fall through 原分支(native placeholder)。selectedOpt 查無\n // (async options 未到)→ fallback {value,label:value},renderer 端自行降級(initials)。\n if (selectedItemRenderer && value && selectedOpt) {\n return (\n <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }), 'relative', className)}\n style={{ paddingRight: 'var(--field-px)' }} data-field-mode=\"edit\" data-error={error ? '' : undefined}>\n {/* Review fix(2026-08-05 F5/F6):StartIcon + nakedCellRowModeAlign 與桌機 renderer 分支\n (CustomSelect,gate 同為 selectedOpt truthy — renderer 公開契約只收 options 真實 entry,\n async options 未到 fall through 原生文字分支,與桌機 fallback 對稱)逐項對齊;\n pointer-events-none → tap 穿透 select overlay 開原生 picker。 */}\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n <span className={cn('relative z-10 pointer-events-none flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign)}>\n {selectedItemRenderer(selectedOpt)}\n </span>\n {selectEl}\n {clearEl}\n {chevronEl}\n </div>\n )\n }\n\n if (!isTextDisplay) {\n return (\n <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }), value && tagPadding[size], 'relative',\n className)}\n style={{ paddingRight: 'var(--field-px)' }} data-field-mode=\"edit\" data-error={error ? '' : undefined}>\n {value ? <Tag size={size} color={nativeTagVariant} className=\"shrink-0 relative z-10 pointer-events-none\">{label}</Tag> : <span className=\"text-fg-muted\">{placeholder ?? '選擇...'}</span>}\n {selectEl}\n <span className=\"flex-1\" />\n {clearEl}\n {chevronEl}\n </div>\n )\n }\n\n return (\n <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }),\n className)}\n data-field-mode=\"edit\" data-error={error ? '' : undefined}>\n {StartIcon && <ItemPrefix><StartIcon size={iconSize} className=\"text-fg-muted pointer-events-none\" aria-hidden /></ItemPrefix>}\n {!StartIcon && SelectedOptIcon && value && <ItemPrefix><SelectedOptIcon size={iconSize} className=\"pointer-events-none\" aria-hidden /></ItemPrefix>}\n {selectEl}\n {clearEl}\n {chevronEl}\n </div>\n )\n }\n)\nNativeSelect.displayName = 'NativeSelect'\n\n// ── Custom Select (desktop — consumes SelectMenu) ────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(\n ({ mode, variant: variantProp, width, error: errorProp = false, size: sizeProp, options, groups, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, name, required, form, clearable = false, display = 'plain', startIcon: StartIcon, searchable = false, loading, minRows, emptyText, creatable = false, onCreate, createLabel, defaultOpen = false, onOpenChange, selectedItemRenderer, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp, 'aria-label': ariaLabel, onKeyDown: onKeyDownProp, style: styleProp,\n // 2026-07-14 API 策展 D:mobile-only props(allowlist 註記)desktop 顯式丟棄 — div trigger 無原生\n // 對應,不 spread 進 DOM(對稱 NativeSelect 丟棄 custom-path-only props 的既有 pattern)\n autoFocus: _autoFocus, autoComplete: _autoComplete, ...rest }, ref) => {\n const fieldCtx = useFieldContext()\n const error = useResolvedFieldInvalid(errorProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-05-31 #11:size 從 Field context cascade(對齊 Input/NumberInput + MUI FormControl)\n const size = useResolvedFieldSize(sizeProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode(prop > 有效 disabled > fieldCtx.mode > 'edit');修 <Field mode=\"view\"> 漏 cascade\n const resolvedMode = useResolvedFieldMode({ mode, disabled })\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n const iconSize = getIconSize(size)\n // 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(M17 對齊,取代自刻 isControlled pattern)。\n // Phase B codex 抓:之前 Custom clear 走 `onChange?.('')` 沒 setInternalValue → uncontrolled clear 失效。useControllable 統一 setter 修。\n // onChange forward coerce null → ''(consumer 簽名 `(value: string) => void`,null 是 internal empty signal)。\n const [value, setValue] = useControllable<string | null>({\n value: valueProp,\n defaultValue: defaultValue ?? null,\n onChange: onChange ? (next) => onChange(next ?? '') : undefined,\n })\n const showClear = clearable && value && resolvedMode === 'edit'\n const isTextDisplay = display === 'plain'\n\n const [open, setOpen] = React.useState(defaultOpen)\n const [search, setSearch] = React.useState('')\n const inputRef = React.useRef<HTMLInputElement>(null)\n // a11y(2026-07-04):listbox 容器 id——trigger aria-controls 指向 SelectMenu PopoverContent\n // (對齊姊妹元件 combobox.tsx:677 既有 canonical;React.useId SSR/CSR 穩定)。\n const listboxId = React.useId()\n // a11y(2026-07-05 D4):追蹤 cmdk active item id → searchable trigger input 綁 aria-activedescendant\n // (機制詳 select-menu.tsx useActiveDescendant docblock;必在 early return 前呼叫 — React #310 hook 順序)。\n const activeOptionId = useActiveDescendant(listboxId, open)\n\n // 關閉時清搜尋\n React.useEffect(() => { if (!open) setSearch('') }, [open])\n\n // **React #310 fix(2026-05-04)**:所有 hooks 必在任何 early return 前 call,\n // 否則 disabled→edit 切換時 hook count 變動 → React 死亡。\n // 原本 useMemo(L280, L291) 在 early return 之後 = latent bug,K13 觸發(filter Op 從 disabled\n // 變 edit 當 user 選欄位)。修法:把所有 useMemo 提到 early return 之前。\n const selectedOpt = options?.find(o => o.value === value)\n // 2026-05-06 v9.1:value 不在 options 也要顯示原值(不沉默丟失)。原 fallback `''` 致\n // SelectCell 開 edit 時若 cell value 不在當前 options(e.g. 上游資料漂移 / options async\n // 後到 / 跨 dataset),trigger 顯示空白 — user 報「value 不見」。對齊 ReadonlyDisplay 同\n // 級 fallback `?? value`。\n const selectedLabel = selectedOpt?.label ?? value ?? ''\n const SelectedIcon = selectedOpt?.icon\n // 截斷 tooltip(tooltip.spec.md:32 截斷 → 顯完整內容,僅實際截斷時):closed trigger text\n // display 的值 span 是截斷元素;trigger = 整個 combobox host(interactive host,hover 直達)、\n // 量測內層值 span — useTruncated 自組 pattern(truncated-text.spec.md「trigger 需自控」指定解;\n // 範本 inline-edit.tsx:251。tag display 由 Tag 內建 useTruncated 自管,不經此路)。\n // deps 含 open:searchable && open 切搜尋 input branch 時值 span 會 unmount,關閉重掛後\n // effect 需重跑重新 observe(Tag always-wrap 教訓:RO 卡在 detached node → isTruncated 凍結)。\n // hook 必在下方 resolvedMode early return 之前(React #310 hook-count 鐵律,見上方註解)。\n const { ref: triggerTruncationRef, isTruncated: triggerTruncated } = useTruncated<HTMLSpanElement>({\n deps: [open, isTextDisplay && value ? selectedLabel : ''],\n })\n const triggerTooltipActive = isTextDisplay && !!value && triggerTruncated && !open\n // ── 過濾選項 ──\n // 2026-07-18:filter 用 trim 過的 search,對齊 SelectMenu creatable 的 `search.trim()` create-row 判定 —\n // 否則尾隨空白(如 \"Bug \")會讓 filter 漏掉完全同名選項、SelectMenu 卻誤判「無同名」提議重複建立。\n const trimmedSearch = search.trim()\n const filteredOptions = searchable && trimmedSearch\n ? options.filter(o => o.label.toLowerCase().includes(trimmedSearch.toLowerCase()))\n : options\n // ── 轉換 SelectOption → SelectMenuOption(必在 early return 前) ──\n // Issue 4(2026-05-10):forward avatar / description / disabled SSOT(per SelectMenuOption schema)。\n const menuOptions: SelectMenuOption[] = React.useMemo(\n () => filteredOptions.map(opt => ({\n value: opt.value,\n label: opt.label,\n icon: isTextDisplay ? opt.icon : undefined,\n avatar: opt.avatar,\n description: opt.description,\n disabled: opt.disabled,\n group: opt.group,\n })),\n [filteredOptions, isTextDisplay]\n )\n // ── Tag display 自訂 label 渲染(必在 early return 前) ──\n const renderLabel = React.useMemo(() => {\n if (isTextDisplay) return undefined\n return (menuOpt: SelectMenuOption) => {\n const srcOpt = options.find(o => o.value === menuOpt.value)\n if (srcOpt?.tagVariant) {\n return <Tag size={size} color={srcOpt.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral'}>{menuOpt.label}</Tag>\n }\n return menuOpt.label\n }\n }, [isTextDisplay, options, size])\n\n // **React #310 fix v2(2026-05-04)**:`handleValueChange` useCallback 也必在 early return 前\n // 原本 L306(early return 後)→ disabled→edit 切換時 hook count 仍變 → #310 持續\n // 2026-05-21 D3:`useControllable` 統一 controlled / uncontrolled state + onChange forward,不再手動 if-branch。\n const handleValueChange = React.useCallback(\n (newValue: string | string[]) => {\n setValue(Array.isArray(newValue) ? newValue[0] : newValue)\n },\n [setValue]\n )\n\n // Early return AFTER all hooks(disabled / readonly / view mode 走 ReadonlyDisplay)\n if (resolvedMode !== 'edit') {\n return <ReadonlyDisplay mode={resolvedMode} variant={variant} width={width} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} selectedItemRenderer={selectedItemRenderer} />\n }\n\n // 2026-05-21 D3 Phase B codex 抓:Custom clear 用 setValue 不直接 onChange,uncontrolled clear 才能真清 internal state。\n const clearEl = showClear ? (\n <SelectClearButton size={size ?? 'md'} onClear={() => setValue('')} stopPropagation />\n ) : null\n\n const chevronEl = (\n <ItemSuffix>\n <ChevronDown size={iconSize} className={cn('text-fg-muted transition-transform motion-reduce:duration-0', open && 'rotate-180')} aria-hidden />\n </ItemSuffix>\n )\n\n // ── Hidden input form mirror(2026-07-13 D2 拍板 Option A;MUI SelectInput idiom)──\n // 桌機自訂 combobox 無原生 form control,`name` / `required` / `form` 歷史上被靜默丟棄\n // (mobile NativeSelect 卻經 {...props} 生效 = 雙路徑提交語義不對稱 footgun)。\n // 補 visually-hidden `<input>` 鏡像,讓桌機也參與原生表單(裸 <form> submit / FormData):\n // - value **單向鏡射**受控 state(useControllable)= projection,非第二 SSOT(M17);\n // 空值時 value=''(不採 Radix hidden <select> 完整 option tree — 其無空 option 時\n // default 第一項,radix-ui/primitives issue #3521 documented footgun)\n // - 原生 required 僅收顯式 `required` prop(裸表單 opt-in);fieldCtx.required 續走\n // aria-required + useFormValidation(RHF)方法論(form-validation.spec.md 規則 1-9),\n // 不對既有 Field+RHF consumer 注入瀏覽器原生 bubble\n // - onChange 唯一用途 = 瀏覽器 autofill:match 既有非 disabled option 才經 canonical\n // setter 寫回(對齊 MUI SelectInput handleChange 同款 guard),其餘輸入忽略\n // - aria-hidden + tabIndex={-1}:AT 與 Tab 序皆不見(semantics 由 role=combobox trigger\n // own);form-validation 規則 8 focus-first-error 以 DOM name 定位 → 可聚焦 mirror →\n // trigger focus-within 顯示 focus 邊框(桌機 Select 脫離「非 native 控件略過」fallback)\n // 世界級對照:MUI Select 非原生模式 opacity-0 hidden input 攜 name/required\n // (github.com/mui/material-ui .../Select/SelectInput.js;Select.test.js 官方測試涵蓋\n // required 阻止提交 + FormData 值)+ React Spectrum HiddenSelect(github.com/adobe/\n // react-spectrum .../@react-aria/select/src/HiddenSelect.tsx)+ Headless UI Listbox\n // name → hidden input kept in sync(headlessui.com/react/listbox#using-with-html-forms)。\n const hiddenInputEl = (\n <input\n type=\"text\"\n name={name}\n form={form}\n value={value ?? ''}\n onChange={(e) => {\n const matched = options.find((o) => o.value === e.target.value && !o.disabled)\n if (matched) setValue(matched.value)\n }}\n required={required || undefined}\n disabled={disabled}\n aria-hidden=\"true\"\n tabIndex={-1}\n className=\"absolute bottom-0 left-0 w-full opacity-0 pointer-events-none\"\n />\n )\n\n const triggerContent = (\n <CustomSelectTriggerContent\n searchable={searchable}\n open={open}\n isTextDisplay={isTextDisplay}\n size={size}\n value={value}\n selectedLabel={selectedLabel}\n selectedOpt={selectedOpt}\n SelectedIcon={SelectedIcon}\n StartIcon={StartIcon}\n iconSize={iconSize}\n placeholder={placeholder}\n search={search}\n setSearch={setSearch}\n inputRef={inputRef}\n activeDescendantId={activeOptionId}\n ariaLabel={ariaLabel}\n labelId={fieldCtx?.labelId}\n selectedItemRenderer={selectedItemRenderer}\n truncationRef={triggerTruncationRef}\n />\n )\n\n // hooks(filteredOptions / menuOptions / renderLabel / handleValueChange)已全 hoist(React #310 fix v2)\n\n const trigger = (\n <div\n // passthrough(2026-07-14 dim-9 修;同日 API 策展 D 收窄):SelectProps allowlist\n // (Pick<SelectHTMLAttributes>,見 SelectProps docblock)承諾的 data-* / onFocus /\n // onBlur 等 attrs,mobile NativeSelect 經 {...props} spread 到 <select> 生效,desktop\n // 原本完整列舉全丟 → 同一 <Select> API 隨 pointer type 靜默失效(consumer data-testid /\n // onBlur mobile 有、desktop 無)。rest 先 spread —— component 契約 attrs(role / aria-* /\n // id / tabIndex / onKeyDown / className)在後,不可被覆寫;consumer onKeyDown 經\n // onKeyDownProp 在 handler 內先跑(見下)。mobile-only attrs(autoFocus / autoComplete)\n // 已在 destructure 層丟棄不進 rest(div trigger 無原生語意;desktop 表單語意由 hidden\n // input mirror 承擔,見 D2 docblock)。\n {...(rest as unknown as React.HTMLAttributes<HTMLDivElement>)}\n ref={ref}\n id={idProp ?? fieldCtx?.id}\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-controls={listboxId}\n aria-label={ariaLabel}\n // a11y(2026-07-04):div-based role=combobox 的 <label for> 無效,接 FieldLabel labelId\n // (field-context.ts labelId jsDoc 明文);consumer aria-label 優先(對齊 slider.tsx:166 guard canonical)。\n aria-labelledby={ariaLabel ? undefined : fieldCtx?.labelId}\n aria-invalid={error || undefined}\n // D2(2026-07-13):顯式 required prop 同步進 aria-required(mirror 本身 aria-hidden,\n // AT 看不到其 required;語意由 trigger 播報 — 對齊 mobile 原生 <select required> 行為)\n aria-required={(required || fieldCtx?.required) || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }),\n !isTextDisplay && value && !searchable && tagPadding[size],\n // 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default\n // state machine `data-[state=open]:border-border-hover`(灰深)處理 open。\n // 2026-07-04 Q1:error border 同樣收進 fieldWrapperStyles error variant(error 勝 focus 色)。\n // D2(2026-07-13):`relative` = hidden input mirror 的 absolute 定位容器\n // (fieldWrapperStyles base 無 relative;對齊 NativeSelect tag 分支同款顯式加法)。\n 'relative cursor-pointer',\n className,\n )}\n style={styleProp || !isTextDisplay ? { ...styleProp, ...(!isTextDisplay ? { paddingRight: 'var(--field-px)' } : undefined) } : undefined}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n onKeyDown={(e) => {\n // passthrough(dim-9):consumer onKeyDown 先跑 —— 對齊 native path(<select> 上\n // consumer handler 同樣最先收到);component 導覽邏輯照舊在後。\n onKeyDownProp?.(e as unknown as React.KeyboardEvent<HTMLSelectElement>)\n // 2026-07-14 dim-10 修:內層清除按鈕(SelectClearButton → ItemInlineAction <button>)\n // 的 Enter/Space 會 bubble 到本 handler 被 preventDefault 吞掉 → 鍵盤無法清除。\n // 事件源自 descendant button 時不執行 trigger 鍵盤邏輯(保留原生 activation)。\n // 注意:不能一刀切 e.target !== e.currentTarget — searchable inline <input> 的\n // 鍵盤事件仍需下方 forwardKeyToListbox 轉送選單導覽。\n if (e.target !== e.currentTarget && (e.target as HTMLElement).closest?.('button')) return\n // 2026-07-05 D4 P0:open 後 ArrowUp/Down/Enter 轉送 cmdk root(見 select-menu.tsx\n // forwardKeyToListbox docblock — 原「open 後不攔讓方向鍵導覽」在跨 DOM 子樹機制上不成立)\n if (open && forwardKeyToListbox(listboxId, e)) return\n if (e.key === 'Enter' || e.key === ' ') {\n // 2026-06-11 P0 a11y(R2 deep-audit):原 guard `!searchable` 連「關閉時的鍵盤開啟」一起擋\n // → searchable Select / PeoplePicker single 鍵盤打不開(WCAG 2.1.1)。原意只是開啟後\n // 別吃掉搜尋框的 Space/Enter → 正確 guard = !open(關閉才攔;開啟後不干擾 input 輸入)。\n if (!open) { e.preventDefault(); setOpen(true) }\n }\n // APG combobox 展開鍵:ArrowDown 也可開(對齊 combobox.tsx 同 pattern;open 後不攔讓方向鍵導覽選單)\n if (e.key === 'ArrowDown' && !open) { e.preventDefault(); setOpen(true) }\n if (e.key === 'Escape') setOpen(false)\n }}\n >\n {triggerContent}\n {clearEl}\n {chevronEl}\n {hiddenInputEl}\n </div>\n )\n\n return (\n // 截斷 tooltip:恆 wrap(truncated-text.spec.md always-wrap),未截斷或選單開啟時 open={false}\n // 靜默。TooltipTrigger 放 SelectMenu 的 PopoverTrigger asChild 內側(緊貼 trigger div):\n // PopoverTrigger 注入的 data-state(Field open 邊框樣式依賴)在 TooltipTrigger 內以\n // triggerProps 後 spread 勝出;反序會被 tooltip 的 data-state 蓋掉。Tooltip root / Content\n // 都不產 DOM(Content portal),SelectMenu children 結構與佈局不變。\n <Tooltip open={triggerTooltipActive ? undefined : false}>\n <SelectMenu\n options={menuOptions}\n groups={groups}\n value={value ?? null}\n onValueChange={handleValueChange}\n searchable={false}\n creatable={creatable}\n onCreate={onCreate}\n createLabel={createLabel}\n search={searchable ? search : undefined}\n onSearchChange={searchable ? setSearch : undefined}\n loading={loading}\n emptyText={emptyText}\n size={size}\n minRows={minRows}\n open={open}\n onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}\n contentId={listboxId}\n renderLabel={renderLabel}\n onOpenAutoFocus={searchable ? (e) => { e.preventDefault(); inputRef.current?.focus() } : undefined}\n >\n <TooltipTrigger asChild>\n {trigger}\n </TooltipTrigger>\n </SelectMenu>\n <TooltipContent>{triggerTooltipActive ? selectedLabel : null}</TooltipContent>\n </Tooltip>\n )\n }\n)\nCustomSelect.displayName = 'CustomSelect'\n\n// ── Public component(自動偵測 mobile / desktop)──────────────────────────────\n\nconst Select = React.forwardRef<HTMLSelectElement | HTMLDivElement, SelectProps>(\n (props, ref) => {\n const isMobile = useIsTouchDevice()\n\n if (isMobile) {\n return <NativeSelect ref={ref as React.Ref<HTMLSelectElement>} {...props} />\n }\n\n return <CustomSelect ref={ref as React.Ref<HTMLDivElement>} {...props} />\n }\n)\nSelect.displayName = 'Select'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const selectMeta = {\n component: 'Select',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n // 'active' 移除 — trigger 走 Field chrome、menu row 走 MenuItem(其 meta 無 active)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { Select }\n"],"names":["tVariant"],"mappings":";;;;;;;;;;;;;;AAqBA,MAAM,aAAqC;AAAA,EACzC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAkKA,MAAM,cAAc,CAAC,SAAiB,UAAU,IAA0B;AAa1E,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA;AAAA,EACA,kBAAkB;AACpB,GAIG;AACD,SACE,oBAAC,QAAA,EAAK,WAAU,iBACd,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,OAAO;AAAA;AAAA,QACP,SAAS,kBAAkB,CAAC,MAAM;AAAE,iCAAG;AAAmB,kBAAA;AAAA,QAAU,IAAI,MAAM,QAAA;AAAA,MAAQ;AAAA,IACxF;AAAA,EAAA,GAEJ;AAEJ;AACA,kBAAkB,cAAc;AAMhC,SAAS,2BAA2B;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAqBoB;AAelB,MAAI,cAAc,MAAM;AACtB,UAAM,0BAA0B,eAAe;AAC/C,UAAM,sBAAsB,CAAC,UAAU;AACvC,WACE,qBAAC,QAAA,EAAK,WAAU,oDACb,UAAA;AAAA,MAAA,aAAa,oBAAC,YAAA,EAAW,UAAA,oBAAC,WAAA,EAAU,MAAM,UAAU,WAAU,qCAAoC,eAAW,KAAA,CAAC,EAAA,CAAE;AAAA,MACjH;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,UAAU,EAAE,OAAO,KAAK;AAAA,UAEzC,yBAAuB;AAAA,UAEvB,cAAY,aAAa;AAAA,UACzB,mBAAiB,YAAY,SAAY;AAAA,UAEzC,aAAa,sBAAsB,KAAK;AAAA,UACxC,WAAW,GAAG,iBAAiB,aAAa;AAAA,UAC5C,WAAS;AAAA,QAAA;AAAA,MAAA;AAAA,MAEV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,eAAY;AAAA,UACZ,WAAU;AAAA,UAEV,UAAA,oBAAC,QAAA,EAAK,WAAU,2BAA2B,UAAA,cAAA,CAAc;AAAA,QAAA;AAAA,MAAA;AAAA,IAC3D,GAEJ;AAAA,EAEJ;AAIA,MAAI,wBAAwB,SAAS,aAAa;AAChD,WACE,qBAAA,UAAA,EACG,UAAA;AAAA,MAAA,aAAa,oBAAC,YAAA,EAAW,UAAA,oBAAC,WAAA,EAAU,MAAM,UAAU,WAAU,qCAAoC,eAAW,KAAA,CAAC,EAAA,CAAE;AAAA,MAGjH,oBAAC,UAAK,WAAW,GAAG,2CAA2C,qBAAqB,GAAI,UAAA,qBAAqB,WAAW,EAAA,CAAE;AAAA,IAAA,GAC5H;AAAA,EAEJ;AAEA,MAAI,eAAe;AACjB,WACE,qBAAA,UAAA,EACG,UAAA;AAAA,MAAA,aAAa,oBAAC,YAAA,EAAW,UAAA,oBAAC,WAAA,EAAU,MAAM,UAAU,WAAU,qCAAoC,eAAW,KAAA,CAAC,EAAA,CAAE;AAAA,MAChH,CAAC,aAAa,gBAAgB,6BAAU,YAAA,EAAW,UAAA,oBAAC,cAAA,EAAa,MAAM,UAAU,WAAU,uBAAsB,eAAW,MAAC,GAAE;AAAA,MAChI,oBAAC,QAAA,EAAK,KAAK,eAAe,WAAW,GAAG,2BAA2B,CAAC,SAAS,eAAe,GACzF,UAAA,QAAQ,gBAAiB,eAAe,MAAA,CAC3C;AAAA,IAAA,GACF;AAAA,EAEJ;AAEA,SACE,qBAAA,UAAA,EACG,UAAA;AAAA,IAAA,UAAS,2CAAa,cACnB,oBAAC,KAAA,EAAI,MAAY,OAAO,YAAY,YAA+D,WAAU,gCAAgC,UAAA,cAAA,CAAc,IAC3J,QACE,oBAAC,KAAA,EAAI,MAAY,WAAU,gCAAgC,UAAA,eAAc,IACzE,oBAAC,QAAA,EAAK,WAAU,iBAAiB,UAAA,eAAe,OAAM;AAAA,IAE5D,oBAAC,QAAA,EAAK,WAAU,SAAA,CAAS;AAAA,EAAA,GAC3B;AAEJ;AACA,2BAA2B,cAAc;AAGzC,SAAS,gBAAgB;AAAA,EACvB;AAAA,EAAM,SAAS;AAAA,EAAa;AAAA,EAAO;AAAA,EAAM;AAAA,EAAS;AAAA,EAAO;AAAA,EAAS,WAAW;AAAA,EAAW;AAAA,EAAW;AAAA,EAAa;AAAA,EAAoB;AACtI,GAGG;AACD,QAAM,eAAe,QAAQ;AAC7B,QAAM,eAAe,qBAAA;AACrB,QAAM,UAAU,eAAe;AAC/B,QAAM,KAAK,QAAQ;AACnB,QAAM,WAAW,YAAY,EAAE;AAG/B,QAAM,cAAc,mCAAS,KAAK,CAAA,MAAK,EAAE,UAAU;AACnD,QAAM,SAAQ,2CAAa,UAAS;AACpC,QAAM,YAAY,iBAAiB,aAAa,qBAAqB;AACrE,QAAM,gBAAgB,YAAY;AAGlC,QAAM,gBAAgB,qBAAqB,YAAY;AACvD,QAAM,YAAY,eAAe;AAQjC,MAAI,iBAAiB,QAAQ;AAC3B,QAAI,CAAC,oBAAoB;AAIvB,UAAI,CAAC,MAAO,QAAO,oBAAC,QAAA,EAAK,WAAW,GAAG,sBAAsB,EAAE,GAAG,eAAe,SAAS,GAAI,UAAA,UAAA,CAAU;AAKxG,UAAI,wBAAwB,aAAa;AACvC,eACE,oBAAC,QAAA,EAAK,WAAW,GAAG,sBAAsB,EAAE,GAAG,oCAAoC,uBAAuB,SAAS,GAChH,UAAA,qBAAqB,WAAW,GACnC;AAAA,MAEJ;AAGA,UAAI,cAAe,QAAO,oBAAC,eAAA,EAAc,WAAW,GAAG,sBAAsB,EAAE,GAAG,SAAS,GAAI,UAAA,MAAA,CAAM;AACrG,YAAMA,YAAW,2CAAa;AAC9B,iCAAQ,KAAA,EAAI,MAAM,IAAI,OAAOA,WAAU,WAAuB,UAAA,OAAM;AAAA,IACtE;AAEA,UAAM,WAAW,2CAAa;AAC9B,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,GAAG,mBAAmB,EAAE,MAAM,QAAQ,SAAS,OAAO,MAAM,GAAA,CAAI,GAAG,SAAS,CAAC,iBAAiB,WAAW,EAAE,GAAG,SAAS;AAAA,QAClI,mBAAgB;AAAA,QAEf,UAAA;AAAA,UAAA,wBAAwB,SAAS;AAAA;AAAA,YAEhC,oBAAC,UAAK,WAAW,GAAG,2CAA2C,qBAAqB,GACjF,UAAA,qBAAqB,WAAW,EAAA,CACnC;AAAA,cACE;AAAA;AAAA,YAEF,oBAAC,eAAA,EAAc,WAAW,GAAG,iBAAiB,kBAAkB,CAAC,SAAS,aAAa,GACpF,UAAA,QAAQ,QAAQ,UAAA,CACnB;AAAA,cACE,QACF,oBAAC,KAAA,EAAI,MAAM,IAAI,OAAO,UAAW,UAAA,MAAA,CAAM,IAEvC,oBAAC,UAAK,WAAW,GAAG,kBAAkB,aAAa,GAAI,UAAA,WAAU;AAAA,UAEnE,oBAAC,YAAA,EAAW,UAAA,oBAAC,aAAA,EAAY,MAAM,UAAU,WAAU,qCAAoC,eAAW,KAAA,CAAC,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAG3G;AAOA,QAAM,gBAAgB,YAAY,UAAU,CAAC,CAAC,qBAAqB,iBAAiB;AACpF,QAAM,eAAe,iBAAiB,aAAa,OAAO;AAM1D,MAAI,wBAAwB,SAAS,aAAa;AAChD,gCACG,OAAA,EAAI,WAAW,GAAG,mBAAmB,EAAE,MAAM,cAAc,SAAS,OAAO,MAAM,IAAI,GAAG,SAAS,GAAG,mBAAiB,cAAc,iBAAe,cAChJ,UAAA;AAAA,MAAA,aAAa,oBAAC,YAAA,EAAW,UAAA,oBAAC,WAAA,EAAU,MAAM,UAAU,WAAW,GAAG,uBAAuB,SAAS,GAAG,eAAW,MAAC,GAAE;AAAA,MACpH,oBAAC,QAAA,EAAK,WAAW,GAAG,2CAA2C,uBAAuB,iBAAiB,cAAc,kBAAkB,GACpI,UAAA,qBAAqB,WAAW,EAAA,CACnC;AAAA,MACC,iBAAiB,oBAAC,YAAA,EAAW,WAAU,uBAAsB,8BAAC,aAAA,EAAY,MAAM,UAAU,WAAW,GAAG,YAAY,SAAS,GAAG,eAAW,MAAC,EAAA,CAAE;AAAA,IAAA,GACjJ;AAAA,EAEJ;AAEA,MAAI,eAAe;AACjB,gCACG,OAAA,EAAI,WAAW,GAAG,mBAAmB,EAAE,MAAM,cAAc,SAAS,OAAO,MAAM,IAAI,GAAG,SAAS,GAAG,mBAAiB,cAAc,iBAAe,cAChJ,UAAA;AAAA,MAAA,aAAa,oBAAC,YAAA,EAAW,UAAA,oBAAC,WAAA,EAAU,MAAM,UAAU,WAAW,GAAG,uBAAuB,SAAS,GAAG,eAAW,MAAC,GAAE;AAAA,0BAEnH,eAAA,EAAc,WAAW,GAAG,UAAU,iBAAiB,cAAc,kBAAkB,GACrF,UAAA,QAAQ,QAAQ,oBAAC,QAAA,EAAK,WAAW,eAAgB,qBAAU,GAC9D;AAAA,MACC,iBAAiB,oBAAC,YAAA,EAAW,WAAU,uBAAsB,8BAAC,aAAA,EAAY,MAAM,UAAU,WAAW,GAAG,YAAY,SAAS,GAAG,eAAW,MAAC,EAAA,CAAE;AAAA,IAAA,GACjJ;AAAA,EAEJ;AAEA,QAAM,aAAa,2CAAa;AAEhC,SACE,qBAAC,OAAA,EAAI,WAAW,GAAG,mBAAmB,EAAE,MAAM,cAAc,SAAS,OAAO,MAAM,GAAA,CAAI,GAAG,SAAS,WAAW,EAAE,GAAG,SAAS,GAAG,OAAO,EAAE,cAAc,kBAAA,GAAqB,mBAAiB,cAAc,iBAAe,cACrN,UAAA;AAAA,IAAA,QAAQ,oBAAC,KAAA,EAAI,MAAM,IAAI,OAAO,YAAa,UAAA,MAAA,CAAM,IAAS,oBAAC,QAAA,EAAK,WAAW,eAAgB,UAAA,WAAU;AAAA,IACrG,iBAAiB,oBAAC,YAAA,EAAW,WAAU,uBAAsB,8BAAC,aAAA,EAAY,MAAM,UAAU,WAAW,GAAG,YAAY,SAAS,GAAG,eAAW,MAAC,EAAA,CAAE;AAAA,EAAA,GACjJ;AAEJ;AAKA,MAAM,eAAe,MAAM;AAAA,EACzB,CAAC;AAAA,IAAE;AAAA,IAAM,SAAS;AAAA,IAAa;AAAA,IAAO,OAAO,YAAY;AAAA,IAAO,MAAM;AAAA,IAAU;AAAA,IAAS,OAAO;AAAA,IAAW;AAAA,IAAc;AAAA,IAAU;AAAA,IAAa;AAAA,IAAW,UAAU;AAAA,IAAc,YAAY;AAAA,IAAO,UAAU;AAAA,IAAS,WAAW;AAAA,IAAW;AAAA,IAAoB,IAAI;AAAA,IAAQ,oBAAoB;AAAA,IAAqB,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAM3U,YAAY;AAAA,IAAa,QAAQ;AAAA,IAAS,SAAS;AAAA,IAAU,SAAS;AAAA,IAAU,WAAW;AAAA,IAAY,WAAW;AAAA,IAAY,UAAU;AAAA,IAAW,aAAa;AAAA,IAAc,aAAa;AAAA,IAAc,cAAc;AAAA,IAAe;AAAA,IACtO,GAAG;AAAA,EAAA,GAAS,QAAQ;AACpB,UAAM,WAAW,gBAAA;AACjB,UAAM,QAAQ,wBAAwB,SAAS;AAC/C,UAAM,WAAW,yBAAyB,YAAY;AAEtD,UAAM,OAAO,qBAAqB,QAAQ;AAE1C,UAAM,eAAe,qBAAqB,EAAE,MAAM,UAAU;AAC5D,UAAM,UAAgC,wBAAwB,WAAW;AACzE,UAAM,WAAW,YAAY,IAAI;AAEjC,UAAM,CAAC,OAAO,QAAQ,IAAI,gBAA+B;AAAA,MACvD,OAAO;AAAA,MACP,cAAc,gBAAgB;AAAA,MAC9B,UAAU,WAAW,CAAC,SAAS,SAAS,QAAQ,EAAE,IAAI;AAAA,IAAA,CACvD;AACD,UAAM,qBAAqB,CAAC,MAAc,SAAS,CAAC;AACpD,UAAM,YAAY,aAAa,SAAS,iBAAiB;AACzD,UAAM,gBAAgB,YAAY;AAClC,UAAM,YAAY,MAAM,OAAiC,IAAI;AAC7D,UAAM,eAAe,MAAM,YAAY,CAAC,OAAiC;AACvE,gBAAU,UAAU;AACpB,UAAI,OAAO,QAAQ,WAAY,KAAI,EAAE;AAAA,eAC5B,IAAM,KAAyD,UAAU;AAAA,IACpF,GAAG,CAAC,GAAG,CAAC;AAER,QAAI,iBAAiB,QAAQ;AAC3B,aAAO,oBAAC,iBAAA,EAAgB,MAAM,cAAc,SAAkB,OAAc,MAAY,SAAkB,OAAc,SAAkB,WAAW,WAAW,WAAsB,aAA0B,oBAAwC,sBAA4C;AAAA,IACtS;AAEA,UAAM,cAAc,mCAAS,KAAK,CAAA,MAAK,EAAE,UAAU;AACnD,UAAM,WACJ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,IAAI,WAAU,qCAAU;AAAA,QACxB,OAAO,SAAS;AAAA,QAChB,UAAU,CAAC,MAAM,mBAAmB,EAAE,OAAO,KAAK;AAAA,QAClD;AAAA,QACA,gBAAc,SAAS;AAAA,QACvB,kBAAe,qCAAU,aAAY;AAAA,QACrC,oBAAkB,wBAAuB,qCAAU;AAAA,QACnD,qBAAmB,yBAAyB,QAAQ,qCAAU,UAAU;AAAA,QACxE,WAAW,GAAG,iBAAiB,kCAAkC,CAAC,SAAS,iBAAiB,UAAU,CAAC,iBAAkB,wBAAwB,gBAAiB,8CAA8C;AAAA,QAC/M,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,mCAAgB,UAAA,EAAO,OAAM,IAAG,UAAQ,MAAE,UAAA,aAAY;AAAA,UAEtD,QAAQ,IAAI,CAAA,QAAO,oBAAC,YAAuB,OAAO,IAAI,OAAO,UAAU,IAAI,UAAW,UAAA,IAAI,MAAA,GAA1D,IAAI,KAA4D,CAAS;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAI9G,UAAM,UAAU,YACd,oBAAC,mBAAA,EAAkB,MAAM,QAAQ,MAAM,SAAS,MAAM,mBAAmB,EAAE,EAAA,CAAG,IAC5E;AAEJ,UAAM,YACJ,oBAAC,YAAA,EAAW,WAAU,qCACpB,UAAA,oBAAC,aAAA,EAAY,MAAM,UAAU,WAAU,iBAAgB,eAAW,MAAC,GACrE;AAEF,UAAM,SAAQ,2CAAa,UAAS;AACpC,UAAM,mBAAmB,2CAAa;AACtC,UAAM,kBAAkB,2CAAa;AAOrC,QAAI,wBAAwB,SAAS,aAAa;AAChD,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UAAI,WAAW,GAAG,mBAAmB,EAAE,MAAM,QAAQ,SAAkB,OAAO,MAAM,MAAA,CAAO,GAAG,YAAY,SAAS;AAAA,UAClH,OAAO,EAAE,cAAc,kBAAA;AAAA,UAAqB,mBAAgB;AAAA,UAAO,cAAY,QAAQ,KAAK;AAAA,UAK3F,UAAA;AAAA,YAAA,aAAa,oBAAC,YAAA,EAAW,UAAA,oBAAC,WAAA,EAAU,MAAM,UAAU,WAAU,qCAAoC,eAAW,KAAA,CAAC,EAAA,CAAE;AAAA,YACjH,oBAAC,UAAK,WAAW,GAAG,6EAA6E,qBAAqB,GACnH,UAAA,qBAAqB,WAAW,EAAA,CACnC;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAGP;AAEA,QAAI,CAAC,eAAe;AAClB,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UAAI,WAAW;AAAA,YAAG,mBAAmB,EAAE,MAAM,QAAQ,SAAkB,OAAO,MAAM,OAAO;AAAA,YAAG,SAAS,WAAW,IAAI;AAAA,YAAG;AAAA,YACxH;AAAA,UAAA;AAAA,UACA,OAAO,EAAE,cAAc,kBAAA;AAAA,UAAqB,mBAAgB;AAAA,UAAO,cAAY,QAAQ,KAAK;AAAA,UAC3F,UAAA;AAAA,YAAA,QAAQ,oBAAC,KAAA,EAAI,MAAY,OAAO,kBAAkB,WAAU,8CAA8C,UAAA,MAAA,CAAM,IAAS,oBAAC,QAAA,EAAK,WAAU,iBAAiB,yBAAe,SAAQ;AAAA,YACjL;AAAA,YACD,oBAAC,QAAA,EAAK,WAAU,SAAA,CAAS;AAAA,YACxB;AAAA,YACA;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAGP;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAAI,WAAW;AAAA,UAAG,mBAAmB,EAAE,MAAM,QAAQ,SAAkB,OAAO,MAAM,OAAO;AAAA,UAC1F;AAAA,QAAA;AAAA,QACA,mBAAgB;AAAA,QAAO,cAAY,QAAQ,KAAK;AAAA,QAC/C,UAAA;AAAA,UAAA,aAAa,oBAAC,YAAA,EAAW,UAAA,oBAAC,WAAA,EAAU,MAAM,UAAU,WAAU,qCAAoC,eAAW,KAAA,CAAC,EAAA,CAAE;AAAA,UAChH,CAAC,aAAa,mBAAmB,6BAAU,YAAA,EAAW,UAAA,oBAAC,iBAAA,EAAgB,MAAM,UAAU,WAAU,uBAAsB,eAAW,MAAC,GAAE;AAAA,UACrI;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AACA,aAAa,cAAc;AAK3B,MAAM,eAAe,MAAM;AAAA,EACzB,CAAC;AAAA,IAAE;AAAA,IAAM,SAAS;AAAA,IAAa;AAAA,IAAO,OAAO,YAAY;AAAA,IAAO,MAAM;AAAA,IAAU;AAAA,IAAS;AAAA,IAAQ,OAAO;AAAA,IAAW;AAAA,IAAc;AAAA,IAAU;AAAA,IAAa;AAAA,IAAW,UAAU;AAAA,IAAc;AAAA,IAAM;AAAA,IAAU;AAAA,IAAM,YAAY;AAAA,IAAO,UAAU;AAAA,IAAS,WAAW;AAAA,IAAW,aAAa;AAAA,IAAO;AAAA,IAAS;AAAA,IAAS;AAAA,IAAW,YAAY;AAAA,IAAO;AAAA,IAAU;AAAA,IAAa,cAAc;AAAA,IAAO;AAAA,IAAc;AAAA,IAAsB;AAAA,IAAoB,IAAI;AAAA,IAAQ,oBAAoB;AAAA,IAAqB,qBAAqB;AAAA,IAAsB,cAAc;AAAA,IAAW,WAAW;AAAA,IAAe,OAAO;AAAA;AAAA;AAAA,IAG7kB,WAAW;AAAA,IAAY,cAAc;AAAA,IAAe,GAAG;AAAA,EAAA,GAAQ,QAAQ;AACvE,UAAM,WAAW,gBAAA;AACjB,UAAM,QAAQ,wBAAwB,SAAS;AAC/C,UAAM,WAAW,yBAAyB,YAAY;AAEtD,UAAM,OAAO,qBAAqB,QAAQ;AAE1C,UAAM,eAAe,qBAAqB,EAAE,MAAM,UAAU;AAC5D,UAAM,UAAgC,wBAAwB,WAAW;AACzE,UAAM,WAAW,YAAY,IAAI;AAIjC,UAAM,CAAC,OAAO,QAAQ,IAAI,gBAA+B;AAAA,MACvD,OAAO;AAAA,MACP,cAAc,gBAAgB;AAAA,MAC9B,UAAU,WAAW,CAAC,SAAS,SAAS,QAAQ,EAAE,IAAI;AAAA,IAAA,CACvD;AACD,UAAM,YAAY,aAAa,SAAS,iBAAiB;AACzD,UAAM,gBAAgB,YAAY;AAElC,UAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,WAAW;AAClD,UAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,UAAM,WAAW,MAAM,OAAyB,IAAI;AAGpD,UAAM,YAAY,MAAM,MAAA;AAGxB,UAAM,iBAAiB,oBAAoB,WAAW,IAAI;AAG1D,UAAM,UAAU,MAAM;AAAE,UAAI,CAAC,KAAM,WAAU,EAAE;AAAA,IAAE,GAAG,CAAC,IAAI,CAAC;AAM1D,UAAM,cAAc,mCAAS,KAAK,CAAA,MAAK,EAAE,UAAU;AAKnD,UAAM,iBAAgB,2CAAa,UAAS,SAAS;AACrD,UAAM,eAAe,2CAAa;AAQlC,UAAM,EAAE,KAAK,sBAAsB,aAAa,iBAAA,IAAqB,aAA8B;AAAA,MACjG,MAAM,CAAC,MAAM,iBAAiB,QAAQ,gBAAgB,EAAE;AAAA,IAAA,CACzD;AACD,UAAM,uBAAuB,iBAAiB,CAAC,CAAC,SAAS,oBAAoB,CAAC;AAI9E,UAAM,gBAAgB,OAAO,KAAA;AAC7B,UAAM,kBAAkB,cAAc,gBAClC,QAAQ,OAAO,CAAA,MAAK,EAAE,MAAM,YAAA,EAAc,SAAS,cAAc,YAAA,CAAa,CAAC,IAC/E;AAGJ,UAAM,cAAkC,MAAM;AAAA,MAC5C,MAAM,gBAAgB,IAAI,CAAA,SAAQ;AAAA,QAChC,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,QACX,MAAM,gBAAgB,IAAI,OAAO;AAAA,QACjC,QAAQ,IAAI;AAAA,QACZ,aAAa,IAAI;AAAA,QACjB,UAAU,IAAI;AAAA,QACd,OAAO,IAAI;AAAA,MAAA,EACX;AAAA,MACF,CAAC,iBAAiB,aAAa;AAAA,IAAA;AAGjC,UAAM,cAAc,MAAM,QAAQ,MAAM;AACtC,UAAI,cAAe,QAAO;AAC1B,aAAO,CAAC,YAA8B;AACpC,cAAM,SAAS,QAAQ,KAAK,OAAK,EAAE,UAAU,QAAQ,KAAK;AAC1D,YAAI,iCAAQ,YAAY;AACtB,qCAAQ,KAAA,EAAI,MAAY,OAAO,OAAO,YAAgE,kBAAQ,OAAM;AAAA,QACtH;AACA,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF,GAAG,CAAC,eAAe,SAAS,IAAI,CAAC;AAKjC,UAAM,oBAAoB,MAAM;AAAA,MAC9B,CAAC,aAAgC;AAC/B,iBAAS,MAAM,QAAQ,QAAQ,IAAI,SAAS,CAAC,IAAI,QAAQ;AAAA,MAC3D;AAAA,MACA,CAAC,QAAQ;AAAA,IAAA;AAIX,QAAI,iBAAiB,QAAQ;AAC3B,aAAO,oBAAC,iBAAA,EAAgB,MAAM,cAAc,SAAkB,OAAc,MAAY,SAAkB,OAAc,SAAkB,WAAW,WAAW,WAAsB,aAA0B,oBAAwC,sBAA4C;AAAA,IACtS;AAGA,UAAM,UAAU,YACd,oBAAC,mBAAA,EAAkB,MAAM,QAAQ,MAAM,SAAS,MAAM,SAAS,EAAE,GAAG,iBAAe,MAAC,IAClF;AAEJ,UAAM,YACJ,oBAAC,YAAA,EACC,UAAA,oBAAC,eAAY,MAAM,UAAU,WAAW,GAAG,+DAA+D,QAAQ,YAAY,GAAG,eAAW,MAAC,GAC/I;AAuBF,UAAM,gBACJ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,OAAO,SAAS;AAAA,QAChB,UAAU,CAAC,MAAM;AACf,gBAAM,UAAU,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,SAAS,CAAC,EAAE,QAAQ;AAC7E,cAAI,QAAS,UAAS,QAAQ,KAAK;AAAA,QACrC;AAAA,QACA,UAAU,YAAY;AAAA,QACtB;AAAA,QACA,eAAY;AAAA,QACZ,UAAU;AAAA,QACV,WAAU;AAAA,MAAA;AAAA,IAAA;AAId,UAAM,iBACJ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,oBAAoB;AAAA,QACpB;AAAA,QACA,SAAS,qCAAU;AAAA,QACnB;AAAA,QACA,eAAe;AAAA,MAAA;AAAA,IAAA;AAMnB,UAAM,UACJ;AAAA,MAAC;AAAA,MAAA;AAAA,QAUE,GAAI;AAAA,QACL;AAAA,QACA,IAAI,WAAU,qCAAU;AAAA,QACxB,MAAK;AAAA,QACL,iBAAe;AAAA,QACf,iBAAc;AAAA,QACd,iBAAe;AAAA,QACf,cAAY;AAAA,QAGZ,mBAAiB,YAAY,SAAY,qCAAU;AAAA,QACnD,gBAAc,SAAS;AAAA,QAGvB,iBAAgB,aAAY,qCAAU,aAAa;AAAA,QACnD,oBAAkB,wBAAuB,qCAAU;AAAA,QACnD,qBAAmB,yBAAyB,QAAQ,qCAAU,UAAU;AAAA,QACxE,UAAU;AAAA,QACV,WAAW;AAAA,UACT,mBAAmB,EAAE,MAAM,QAAQ,SAAkB,OAAO,MAAM,OAAO;AAAA,UACzE,CAAC,iBAAiB,SAAS,CAAC,cAAc,WAAW,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMzD;AAAA,UACA;AAAA,QAAA;AAAA,QAEF,OAAO,aAAa,CAAC,gBAAgB,EAAE,GAAG,WAAW,GAAI,CAAC,gBAAgB,EAAE,cAAc,sBAAsB,WAAe;AAAA,QAC/H,mBAAgB;AAAA,QAChB,cAAY,QAAQ,KAAK;AAAA,QACzB,WAAW,CAAC,MAAM;;AAGhB,yDAAgB;AAMhB,cAAI,EAAE,WAAW,EAAE,mBAAkB,aAAE,QAAuB,YAAzB,4BAAmC,WAAW;AAGnF,cAAI,QAAQ,oBAAoB,WAAW,CAAC,EAAG;AAC/C,cAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AAItC,gBAAI,CAAC,MAAM;AAAE,gBAAE,eAAA;AAAkB,sBAAQ,IAAI;AAAA,YAAE;AAAA,UACjD;AAEA,cAAI,EAAE,QAAQ,eAAe,CAAC,MAAM;AAAE,cAAE,eAAA;AAAkB,oBAAQ,IAAI;AAAA,UAAE;AACxE,cAAI,EAAE,QAAQ,SAAU,SAAQ,KAAK;AAAA,QACvC;AAAA,QAEC,UAAA;AAAA,UAAA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAIL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAME,qBAAC,SAAA,EAAQ,MAAM,uBAAuB,SAAY,OAChD,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS;AAAA,YACT;AAAA,YACA,OAAO,SAAS;AAAA,YAChB,eAAe;AAAA,YACf,YAAY;AAAA,YACZ;AAAA,YACA;AAAA,YACA;AAAA,YACA,QAAQ,aAAa,SAAS;AAAA,YAC9B,gBAAgB,aAAa,YAAY;AAAA,YACzC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,cAAc,CAAC,MAAM;AAAE,sBAAQ,CAAC;AAAG,2DAAe;AAAA,YAAG;AAAA,YACrD,WAAW;AAAA,YACX;AAAA,YACA,iBAAiB,aAAa,CAAC,MAAM;;AAAE,gBAAE,eAAA;AAAkB,6BAAS,YAAT,mBAAkB;AAAA,YAAQ,IAAI;AAAA,YAEzF,UAAA,oBAAC,gBAAA,EAAe,SAAO,MACpB,UAAA,QAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,QAEF,oBAAC,gBAAA,EAAgB,UAAA,uBAAuB,gBAAgB,KAAA,CAAK;AAAA,MAAA,EAAA,CAC/D;AAAA;AAAA,EAEJ;AACF;AACA,aAAa,cAAc;AAI3B,MAAM,SAAS,MAAM;AAAA,EACnB,CAAC,OAAO,QAAQ;AACd,UAAM,WAAW,iBAAA;AAEjB,QAAI,UAAU;AACZ,aAAO,oBAAC,cAAA,EAAa,KAA2C,GAAG,MAAA,CAAO;AAAA,IAC5E;AAEA,WAAO,oBAAC,cAAA,EAAa,KAAwC,GAAG,MAAA,CAAO;AAAA,EACzE;AACF;AACA,OAAO,cAAc;AAId,MAAM,aAAa;AAAA,EACxB,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU,CAAA;AAAA,EAGV,OAAO,CAAA;AAAA;AAAA,EAIP,QAAQ,CAAC,WAAW,SAAS,iBAAiB,UAAU;AAAA,EACxD,QAAQ;AAAA,IACN,IAAI,CAAA;AAAA,IACJ,IAAI,CAAC,oBAAoB,eAAe;AAAA,IACxC,MAAM,CAAA;AAAA,EAAC;AAEX;"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
-
import {
|
|
4
|
+
import { cn, lineClampClass } from "../../lib/utils.js";
|
|
5
5
|
import { Avatar } from "../../components/Avatar/avatar.js";
|
|
6
6
|
import { Tooltip, TooltipTrigger, TooltipContent } from "../../components/Tooltip/tooltip.js";
|
|
7
7
|
import { useTruncated } from "../../hooks/use-truncated.js";
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"schemaVersion": 1,
|
|
4
4
|
"role": "template-consumer",
|
|
5
5
|
"release": {
|
|
6
|
-
"designSystem": "0.1.0-beta.
|
|
7
|
-
"storybookConfig": "0.1.0-beta.
|
|
6
|
+
"designSystem": "0.1.0-beta.113",
|
|
7
|
+
"storybookConfig": "0.1.0-beta.113"
|
|
8
8
|
},
|
|
9
9
|
"upgradeTrust": {
|
|
10
10
|
"schemaVersion": 1,
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"implementationSha256": "98b74118fb5b410b77ad2994aa9105fa7609c7353822f8c5213cce54d94f0af4"
|
|
43
43
|
},
|
|
44
44
|
"payload": {
|
|
45
|
-
"forkCorpusLockSha256": "
|
|
45
|
+
"forkCorpusLockSha256": "8b3f0a7b469265f6b666e319f3ee79fc2101a0d27596716bdfba0f813cd3c5d8",
|
|
46
46
|
"discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
|
|
47
47
|
"providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
|
|
48
|
-
"providerLifecycleSha256": "
|
|
48
|
+
"providerLifecycleSha256": "811b160b56b2c2128a26912e81f0956dd3f12a30c0de7b748429ade4a4811a38",
|
|
49
49
|
"providerInventorySha256": "00adf1c4f6b3367055a9cc3fdfe658660771b4c9931f696e12761a95076ad92c",
|
|
50
50
|
"executionRuntimeSha256": "d9377cc45308731f287d7b1711b7c38fc4e02ff8f84729bcbbf01bb52d136904",
|
|
51
51
|
"commonInstructionSha256": "6b9aa8747d9ddc9bd9a78de88a0c3d65f5b100ea917c4dd69be4d889b8299438",
|
|
@@ -120,14 +120,14 @@
|
|
|
120
120
|
"scripts/release-bom-contract.mjs": "75d736f86c7f695dc88a1982d547998d510e8050c4847abe387227b6662b87c2",
|
|
121
121
|
"scripts/schemas/product-template-scaffold-lock.schema.json": "1318af59f12546b3cda059a1d94923d9a969bef4c5a476ebb46e42f8c853d19a",
|
|
122
122
|
"scripts/schemas/release-bom.schema.json": "6969482588a25533dc1896be63c3c9629aced1cbeb745b99822db764682dc4a4",
|
|
123
|
-
"scripts/schemas/upgrade-evidence-receipt.schema.json": "
|
|
123
|
+
"scripts/schemas/upgrade-evidence-receipt.schema.json": "a464b3dcc8f3f94ca997f5771e14626cc400c101e4e7625ff416a89f2af37bbc",
|
|
124
124
|
"scripts/setup-governance.mjs": "2d5bf5ef149595854681c5744a431c2c231ead7d8699571a453783b39e556518",
|
|
125
125
|
"scripts/setup-provider-cli-toolchain.mjs": "b34e11d02a7aa71d001ef8f1a42af4cf97d1b8b82360f5779cb927370f5ee15a",
|
|
126
126
|
"scripts/setup-workspace.mjs": "95dc0fef1f7570efe277a66f7edf4640055ad6abbae0245615eceb9b041560e9",
|
|
127
127
|
"scripts/sync-all.mjs": "938c706cad79a4bd538359c9b74cb9253a4c74b5f1342cbfc1625600425d8bfd",
|
|
128
128
|
"scripts/sync-exact-workspace-dependencies.mjs": "29ce6aaab16fd7a939eff6e556e8548291fad28df88d578627153c9508bf6e31",
|
|
129
129
|
"scripts/verify-consumer-css-entry.mjs": "a16545ea8d4f5224d8133f0bb0b68d5489d8e106fe7594488a78e0e5718b99cc",
|
|
130
|
-
"scripts/verify-upgrade-evidence.mjs": "
|
|
130
|
+
"scripts/verify-upgrade-evidence.mjs": "2e4aa0050c39708929a96753dc639149b058b5b76fb281a1a6fe3c1ab09ebbae",
|
|
131
131
|
"scripts/verify-upgrade-provenance.mjs": "8a643a60eaf64bf131a2a4da197d5670a52e4705d11402b53fb32ae93302f6ef",
|
|
132
132
|
"scripts/workflow-static-validation.mjs": "8e12bf2d73fc5e1200ddc6cc8437b27433fcd71f7d85d79b56756dcaac667305"
|
|
133
133
|
}
|
|
@@ -109,8 +109,8 @@
|
|
|
109
109
|
"bomSha256": { "$ref": "#/$defs/sha256" },
|
|
110
110
|
"releaseAssetDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" },
|
|
111
111
|
"releaseSetSha256": { "$ref": "#/$defs/sha256" },
|
|
112
|
-
"finalizationReceiptSha256": { "$ref": "#/$defs/sha256" },
|
|
113
|
-
"releaseTrustEvidenceSha256": { "$ref": "#/$defs/sha256" },
|
|
112
|
+
"finalizationReceiptSha256": { "oneOf": [{ "$ref": "#/$defs/sha256" }, { "type": "null" }] },
|
|
113
|
+
"releaseTrustEvidenceSha256": { "oneOf": [{ "$ref": "#/$defs/sha256" }, { "type": "null" }] },
|
|
114
114
|
"packages": {
|
|
115
115
|
"type": "array",
|
|
116
116
|
"prefixItems": [
|
|
@@ -181,9 +181,18 @@ function canonicalProvenance(value, label = 'upgrade provenance') {
|
|
|
181
181
|
invariant(GIT_OBJECT.test(value.gitCommit || ''), `${label}.gitCommit is invalid`)
|
|
182
182
|
invariant(GIT_OBJECT.test(value.gitTree || ''), `${label}.gitTree is invalid`)
|
|
183
183
|
invariant(SHA256.test(value.bomSha256 || '') && value.releaseAssetDigest === `sha256:${value.bomSha256}`, `${label} immutable Release BOM binding is invalid`)
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
invariant(SHA256.test(value.releaseSetSha256 || ''), `${label}.releaseSetSha256 is invalid`)
|
|
185
|
+
// 兩個 finalizer evidence 資產屬 opt-in 高保證車道 —— 已 retired ceremony,標準 six-file
|
|
186
|
+
// release 不產生(producer 端 verify-upgrade-provenance.mjs:657-658 即輸出 null,並將其排除
|
|
187
|
+
// 出 releaseSetSha256)。契約:**有值 → 必為 sha256(present-but-wrong 仍 fail-closed);
|
|
188
|
+
// 無值 → 必為 null(key 不得缺席,exactKeys 守閉合 shape);且兩者必成對**(對齊 producer
|
|
189
|
+
// 配對律)。2026-08-05 修:原本無條件要求 sha256 → 標準 release 的 consumer 自動升級
|
|
190
|
+
// 永遠不可能通過(WM 長期只能手動同步的真因)。
|
|
191
|
+
for (const field of ['finalizationReceiptSha256', 'releaseTrustEvidenceSha256']) {
|
|
192
|
+
invariant(value[field] === null || SHA256.test(value[field] || ''), `${label}.${field} is invalid`)
|
|
186
193
|
}
|
|
194
|
+
invariant((value.finalizationReceiptSha256 === null) === (value.releaseTrustEvidenceSha256 === null),
|
|
195
|
+
`${label} finalization receipt requires its paired release trust evidence`)
|
|
187
196
|
return {
|
|
188
197
|
repository: value.repository,
|
|
189
198
|
workflow: value.workflow,
|
|
@@ -295,7 +295,7 @@
|
|
|
295
295
|
{
|
|
296
296
|
"schemaVersion": 1,
|
|
297
297
|
"file": "consumer/managed-files/0235cf6b3fd402a3717881dc205b5bf77aed7632e7d7d6f1811a40e3033bd816.blob",
|
|
298
|
-
"sha256": "
|
|
298
|
+
"sha256": "a464b3dcc8f3f94ca997f5771e14626cc400c101e4e7625ff416a89f2af37bbc",
|
|
299
299
|
"source": "scripts/schemas/upgrade-evidence-receipt.schema.json",
|
|
300
300
|
"classification": null,
|
|
301
301
|
"destination": "scripts/schemas/upgrade-evidence-receipt.schema.json"
|
|
@@ -511,7 +511,7 @@
|
|
|
511
511
|
{
|
|
512
512
|
"schemaVersion": 1,
|
|
513
513
|
"file": "consumer/managed-files/9791c85388d2e2e2bd564e2550fadc3351c4c960a47ce838098afcba7544eb90.blob",
|
|
514
|
-
"sha256": "
|
|
514
|
+
"sha256": "2e4aa0050c39708929a96753dc639149b058b5b76fb281a1a6fe3c1ab09ebbae",
|
|
515
515
|
"source": "scripts/verify-upgrade-evidence.mjs",
|
|
516
516
|
"classification": null,
|
|
517
517
|
"destination": "scripts/verify-upgrade-evidence.mjs"
|
|
@@ -919,7 +919,7 @@
|
|
|
919
919
|
{
|
|
920
920
|
"schemaVersion": 1,
|
|
921
921
|
"file": "manifest.json",
|
|
922
|
-
"sha256": "
|
|
922
|
+
"sha256": "6e1683ed50d036c1df2a8a52781d51de51f1d8f5046764b4ba2687ae286ab803",
|
|
923
923
|
"source": null,
|
|
924
924
|
"classification": null,
|
|
925
925
|
"destination": null
|
|
@@ -104,20 +104,20 @@
|
|
|
104
104
|
},
|
|
105
105
|
"providerLifecycle": {
|
|
106
106
|
"schemaVersion": 4,
|
|
107
|
-
"releaseVersion": "0.1.0-beta.
|
|
107
|
+
"releaseVersion": "0.1.0-beta.113",
|
|
108
108
|
"immutableHead": {
|
|
109
109
|
"providerInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
|
|
110
|
-
"releaseVersion": "0.1.0-beta.
|
|
111
|
-
"snapshotSha256": "
|
|
110
|
+
"releaseVersion": "0.1.0-beta.112",
|
|
111
|
+
"snapshotSha256": "bba31a8b1f894176c688475802ed513e492fdd5dec332cbc5418698cdacb51c7"
|
|
112
112
|
},
|
|
113
|
-
"ledgerSha256": "
|
|
114
|
-
"previousSnapshotSha256": "
|
|
115
|
-
"currentSnapshotSha256": "
|
|
113
|
+
"ledgerSha256": "3bee1b57f6d6b716394f0622e6ad052b1e6f9884e824b907820eae4f4676246d",
|
|
114
|
+
"previousSnapshotSha256": "bba31a8b1f894176c688475802ed513e492fdd5dec332cbc5418698cdacb51c7",
|
|
115
|
+
"currentSnapshotSha256": "09c8ab9ca6621b843991c68b3de1aa07ba402b95be514285241826084fbc9391",
|
|
116
116
|
"currentProviderInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
|
|
117
117
|
"retiredProviders": [],
|
|
118
118
|
"currentSnapshot": {
|
|
119
|
-
"releaseVersion": "0.1.0-beta.
|
|
120
|
-
"previousSnapshotSha256": "
|
|
119
|
+
"releaseVersion": "0.1.0-beta.113",
|
|
120
|
+
"previousSnapshotSha256": "bba31a8b1f894176c688475802ed513e492fdd5dec332cbc5418698cdacb51c7",
|
|
121
121
|
"providers": [
|
|
122
122
|
{
|
|
123
123
|
"id": "claude",
|
|
@@ -193,8 +193,8 @@
|
|
|
193
193
|
"retiredProviders": []
|
|
194
194
|
},
|
|
195
195
|
"immutableHeadSnapshot": {
|
|
196
|
-
"releaseVersion": "0.1.0-beta.
|
|
197
|
-
"previousSnapshotSha256": "
|
|
196
|
+
"releaseVersion": "0.1.0-beta.112",
|
|
197
|
+
"previousSnapshotSha256": "185564b0f73305abe0a1604537f20af8cd1f372ca1ac8d9fe062b5c8c226fc00",
|
|
198
198
|
"providers": [
|
|
199
199
|
{
|
|
200
200
|
"id": "claude",
|
package/ds-story-manifest.json
CHANGED
|
@@ -821,6 +821,7 @@
|
|
|
821
821
|
"design-system-components-fileupload-展示--resume-upload",
|
|
822
822
|
"design-system-components-fileupload-展示--bulk-image-upload",
|
|
823
823
|
"design-system-components-fileupload-展示--with-file-list",
|
|
824
|
+
"design-system-components-fileupload-展示--file-list-remove-focus-contract",
|
|
824
825
|
"design-system-components-fileupload-展示--remove-hover-state",
|
|
825
826
|
"design-system-components-fileupload-展示--custom-children",
|
|
826
827
|
"design-system-components-fileupload-展示--button-variant"
|
|
@@ -1113,6 +1114,9 @@
|
|
|
1113
1114
|
"design-system-components-peoplepicker-展示--single",
|
|
1114
1115
|
"design-system-components-peoplepicker-展示--with-error",
|
|
1115
1116
|
"design-system-components-peoplepicker-展示--multi",
|
|
1117
|
+
"design-system-components-peoplepicker-展示--multi-remove-focus-contract",
|
|
1118
|
+
"design-system-components-peoplepicker-展示--stack-remove-overlay-probe",
|
|
1119
|
+
"design-system-components-peoplepicker-展示--clearable",
|
|
1116
1120
|
"design-system-components-peoplepicker-展示--size-alignment",
|
|
1117
1121
|
"design-system-components-peoplepicker-展示--async-directory-load"
|
|
1118
1122
|
],
|
|
@@ -1752,5 +1756,5 @@
|
|
|
1752
1756
|
}
|
|
1753
1757
|
},
|
|
1754
1758
|
"totalComponents": 65,
|
|
1755
|
-
"totalStories":
|
|
1759
|
+
"totalStories": 947
|
|
1756
1760
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "git",
|
|
5
5
|
"url": "git+https://github.com/ajenchen/design-system.git"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.1.0-beta.
|
|
7
|
+
"version": "0.1.0-beta.113",
|
|
8
8
|
"private": false,
|
|
9
9
|
"description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
|
|
10
10
|
"type": "module",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"peerDependencies": {
|
|
122
122
|
"react": ">=18.0.0",
|
|
123
123
|
"react-dom": ">=18.0.0",
|
|
124
|
-
"tailwindcss": ">=4.
|
|
124
|
+
"tailwindcss": ">=4.1.0"
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|
|
127
127
|
"tsc-alias": "^1.8.17"
|
|
@@ -179,6 +179,8 @@ Combobox 依裝置走兩條不同實作(觸控偵測自動切換):
|
|
|
179
179
|
|
|
180
180
|
**手機 / 觸控裝置**:改用隱藏的原生 `<select>`(tab-focusable,方向鍵導覽,原生 picker),以保留行動裝置 screen reader / 語音輸入 / OS-level 整合。有值時這個 `<select>` 以全幅透明 overlay 蓋住整個欄位,點擊任何位置都會喚起原生 picker。
|
|
181
181
|
|
|
182
|
+
**Display 層雙分支同 SSOT(2026-08-05 修)**:NativeCombobox 的 OverflowTagList 消費與 CustomCombobox **同一組** renderer / overflow props(`tagRenderer` / `renderHiddenTag` / `tagWrapperClassName` / `overflowWrapperClassName` / `overflowShape` / `visibleCountOverride` / `tagAreaGapPx` / `tagAreaPaddingLeftPx`)——`tagRenderer` 存在(如 PeoplePicker avatar stack)時手機 edit 與桌機同視覺;未傳維持原 `<Tag>` 文字 pill 預設。先前 native 硬編碼 `<Tag>`,PeoplePicker 手機 edit 掉回文字 pill = touch 分支漏接 display 層(canonical 違反)。**Root invariant:新增 renderer-affecting prop 必同步 NativeCombobox destructure。** 疊層契約:renderer 輸出經「relative z-10(抬到透明 `<select>` overlay 之上)+ pointer-events-none(非互動區 tap 穿透喚起原生 picker)+ `[&_button]`:pointer-events-auto(remove 鈕回收點擊)」三件套包裝;+N wrapper 同抬升且 pointer 穿透(touch 的 overflow 檢視/增刪走原生 picker 本身)。
|
|
183
|
+
|
|
182
184
|
兩條路徑共通點:欄位內多個 `<div>` / `<Tag>` 上的 `onClick` 是 mouse 優化的點擊區,**不是鍵盤介面**——鍵盤使用者不經過它們,但兩條路徑都已各自提供完整鍵盤可達性。
|
|
183
185
|
|
|
184
186
|
**為什麼這些 click target 不加 `role="button" tabIndex`**: 若每個點擊區都加 `tabIndex={0}` 會搶走真正聚焦目標(桌機的 combobox 容器 / 手機的原生 select)的 tab focus,反而破壞鍵盤體驗。「單一鍵盤聚焦點 + 多個滑鼠點擊區」是混合型控制項的世界級 canonical pattern(Material / Atlassian / GitHub issue filter 共識)。
|