@qijenchen/design-system 0.1.0-beta.75 → 0.1.0-beta.76
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/CLAUDE.md +1 -1
- package/dist/components/AppShell/app-shell.d.ts +2 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Chart/chart.d.ts +1 -1
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +1 -1
- package/dist/components/Checkbox/checkbox.js +1 -1
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +13 -10
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +1 -1
- package/dist/components/Command/command.js +3 -3
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +2 -2
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +2 -2
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts +1 -1
- package/dist/components/DescriptionList/description-list.js +2 -2
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Empty/empty.d.ts +2 -0
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field-wrapper.js +4 -4
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -2
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +2 -2
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +1 -1
- package/dist/components/ProfileCard/profile-card.js +2 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/Select/select.js +4 -4
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +1 -1
- package/dist/components/Textarea/textarea.js +2 -2
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +14 -23
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +1 -1
- package/dist/components/TreeView/tree-view.js +1 -1
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/ds-canonical/fork/governance.lock +1 -1
- package/ds-canonical/fork/preamble.md +2 -2
- package/ds-canonical/hooks/check_field_controls_contracts.sh +30 -3
- package/ds-canonical/references/props-naming.md +15 -1
- package/ds-canonical/rules/ui-development.md +2 -2
- package/llms-full.txt +7 -2
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/components/Alert/alert.anatomy.stories.tsx +4 -4
- package/src/components/AppShell/app-shell.spec.md +4 -4
- package/src/components/AppShell/app-shell.tsx +2 -2
- package/src/components/Avatar/avatar.tsx +1 -1
- package/src/components/Breadcrumb/breadcrumb.spec.md +11 -1
- package/src/components/Button/button.tsx +0 -10
- package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
- package/src/components/Chart/chart.tsx +1 -1
- package/src/components/Checkbox/checkbox.tsx +1 -1
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.spec.md +2 -2
- package/src/components/Combobox/combobox.anatomy.stories.tsx +12 -12
- package/src/components/Combobox/combobox.principles.stories.tsx +1 -1
- package/src/components/Combobox/combobox.spec.md +1 -1
- package/src/components/Combobox/combobox.tsx +25 -14
- package/src/components/Command/command.anatomy.stories.tsx +2 -0
- package/src/components/Command/command.tsx +2 -2
- package/src/components/DataTable/cell-registry.tsx +6 -2
- package/src/components/DataTable/data-table-filter-panel.tsx +3 -3
- package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
- package/src/components/DataTable/data-table.css +1 -1
- package/src/components/DataTable/data-table.spec.md +2 -2
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +15 -11
- package/src/components/DatePicker/date-picker.spec.md +1 -1
- package/src/components/DatePicker/date-picker.tsx +9 -6
- package/src/components/DescriptionList/description-list.tsx +1 -1
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.spec.md +1 -1
- package/src/components/Empty/empty.tsx +2 -0
- package/src/components/Field/field-controls.spec.md +9 -6
- package/src/components/Field/field-wrapper.tsx +4 -4
- package/src/components/FileItem/file-item.principles.stories.tsx +1 -0
- package/src/components/FileUpload/file-upload.principles.stories.tsx +2 -2
- package/src/components/FileUpload/file-upload.spec.md +1 -1
- package/src/components/HoverCard/hover-card.principles.stories.tsx +1 -1
- package/src/components/Input/input.anatomy.stories.tsx +3 -3
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +3 -3
- package/src/components/Notice/notice.anatomy.stories.tsx +1 -1
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +8 -7
- package/src/components/NumberInput/number-input.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +3 -3
- package/src/components/PeoplePicker/people-picker.tsx +6 -6
- package/src/components/Popover/popover.principles.stories.tsx +4 -4
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.spec.md +1 -1
- package/src/components/Rating/rating.anatomy.stories.tsx +2 -2
- package/src/components/Rating/rating.spec.md +1 -1
- package/src/components/Rating/rating.tsx +1 -1
- package/src/components/Select/select.anatomy.stories.tsx +18 -18
- package/src/components/Select/select.spec.md +1 -1
- package/src/components/Select/select.tsx +7 -7
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
- package/src/components/Sidebar/sidebar.spec.md +2 -2
- package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +2 -2
- package/src/components/Tabs/tabs.principles.stories.tsx +1 -1
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Textarea/textarea.tsx +3 -3
- package/src/components/TimePicker/time-picker.spec.md +1 -1
- package/src/components/TimePicker/time-picker.tsx +11 -12
- package/src/components/TreeView/tree-view.tsx +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -1
- package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -0
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/tokens/color/semantic.css +1 -1
- package/src/tokens/uiSize/uiSize.css +5 -0
- package/src/tokens/uiSize/uiSize.spec.md +17 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.js","sources":["../../../src/components/Checkbox/checkbox.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.\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check, Minus } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { FieldMode, FieldVariant } from \"@/design-system/components/Field/field-types\"\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from \"@/design-system/components/Field/field-context\"\nimport { fieldWrapperStyles } from \"@/design-system/components/Field/field-wrapper\"\nimport { SelectionItem } from \"@/design-system/components/SelectionControl/selection-item\"\nimport type { LucideIcon } from \"lucide-react\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { CheckboxGroupContext } from \"./checkbox-group\"\n\n// ── Variants ────────────────────────────────────────────────────────────────\n// 三種尺寸(sm/md=16px, lg=20px),對齊 icon 系統與 SelectionItem。\n\nconst checkboxVariants = cva(\n [\n 'grid place-content-center shrink-0 rounded-md',\n 'border border-border bg-surface',\n 'transition-colors duration-150',\n 'hover:border-border-hover',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'data-[state=checked]:bg-primary data-[state=checked]:text-on-emphasis data-[state=checked]:border-primary',\n 'data-[state=checked]:hover:bg-primary-hover data-[state=checked]:hover:border-primary-hover',\n 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-on-emphasis data-[state=indeterminate]:border-primary',\n 'data-[state=indeterminate]:hover:bg-primary-hover data-[state=indeterminate]:hover:border-primary-hover',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:border-transparent disabled:hover:border-transparent',\n 'disabled:data-[state=checked]:bg-disabled disabled:data-[state=checked]:text-fg-disabled disabled:data-[state=checked]:border-transparent',\n 'disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent',\n // readOnly:鎖定互動但維持 checked/unchecked 視覺\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n 'data-[readonly=true]:hover:border-border',\n ],\n {\n variants: {\n size: {\n sm: 'h-4 w-4',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Check Icon Size ─────────────────────────────────────────────────────────\nconst checkIconSize: Record<string, number> = { sm: 12, md: 12, lg: 16 }\n\n// ── Check Icon Stroke Weight ────────────────────────────────────────────────\n// 16px 以下 icon 視覺不夠顯眼 → 用較粗 stroke 補償。Lucide 預設 strokeWidth=2 在\n// 12px 下 render 約 1px 線寬,視覺偏細;加粗到 3.5(render ≈ 1.75px)才有足夠視覺權重。\n// 16px 用 2.5(render ≈ 1.67px)讓 checked 態的 check icon 夠顯眼。\n//\n// 為什麼不是 3 / 2:本 session 實測 3 / 2 在 storybook 上兩個 size 的 render 線寬差僅\n// 0.17px(1.5 vs 1.33),使用者肉眼看不出差異(image #64 回報)。改為 3.5 / 2.5:\n// - md 12px × 3.5 → 1.75px 線寬\n// - lg 16px × 2.5 → 1.67px 線寬\n// 兩者仍接近但 md 的線寬 **絕對值** 跟 16px 預設(1.33)有更明顯差異,視覺上「小 check 更粗」。\n//\n// 世界級對照:iOS HIG / Material 3 / Polaris 的 checkmark 在 <16px 下皆加粗 compensate。\n// 為什麼不用 Lucide absoluteStrokeWidth:那保持「絕對 px 粗細」,我們反而要「小尺寸比例更粗」。\n//\n// Check 與 Minus(indeterminate)共用此規則;Switch 的 SPECS.checkStroke 採同樣值。\n// 2026-05-18 簡化 per user 視覺證「sm/md 3.5 vs lg 2.5 看不出差別」(image #64 + 2nd round\n// 圖一 video proof)+「做完」approval:\n// - 原 {3.5, 3.5, 2.5} → effective render thickness 1.75 / 1.75 / 1.67 = 跨 size 差 0.08px(視覺看不出)\n// - 改 {3, 3, 2.5} 保留 sm/md 小尺寸 legibility insurance(per iOS HIG / Material 3 cite)\n// + lg 仍稍粗於 Lucide default 2(保留 compensation 主旨,但不過度差異化)\n// ⚠️ 2026-06-12:發現 base.css `.lucide{stroke-width:1.75}` 全域規則自 2026-04-08 起\n// 無條件蓋掉本 prop(CSS class > SVG attribute)→ 本表從未真渲染過,上述 05-18 視覺\n// 測試兩者實為 0.875px(證據污染)。base.css 已改 `[stroke-width='2']` 限定,本表自此\n// 真實生效(pixel 驗證 1.50px)。SSOT → .claude/references/ui-dev-rules.md「小尺寸 icon stroke 補償」\nconst checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CheckboxProps\n extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,\n VariantProps<typeof checkboxVariants> {\n /**\n * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldLabel 接管)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用。\n * 套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldDescription 接管)。\n */\n description?: React.ReactNode\n /** 可選左側 icon(label 前)— 2026-06-12 M30 修:轉發 SelectionItem 既有 canonical 槽(selection-item.tsx jsDoc 為 SSOT;與 avatar 互斥)*/\n icon?: LucideIcon\n /** 可選左側 avatar(label 前)— 同上,SelectionItem 槽轉發 */\n avatar?: AvatarData\n /**\n * readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * 用於表單 readonly 呈現、DataTable cell 非編輯態。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 checkbox(預設)\n * display — **純展示**:渲染 ✓ / —(無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。\n * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal\n * disabled — 同 disabled prop:降色 + 鎖互動\n */\n mode?: FieldMode\n /**\n * Visual chrome — checkbox 本體無 input wrapper variant,本 prop 對 checkbox 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(\n (\n {\n className,\n size,\n label,\n icon,\n avatar,\n description,\n readOnly = false,\n disabled,\n mode,\n // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const iconPx = checkIconSize[sizeKey]\n const iconStrokeWidth = checkStrokeWidth[sizeKey]\n\n // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管\n // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='display' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n //\n // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox\n // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,\n // 不能被 Field context 吞掉。AR50 的根因就是這個 branch 之前誤把 group 內的 checkbox\n // label 全清空,導致 sheet 內 3 個 checkbox 沒 label。\n const fieldCtx = useFieldContext()\n const checkboxGroupCtx = React.useContext(CheckboxGroupContext)\n const insideField = fieldCtx?.hasFieldWrapper === true\n const insideGroup = checkboxGroupCtx?.inGroup === true\n const shouldSuppressLabel = insideField && !insideGroup\n const effectiveLabel = shouldSuppressLabel ? undefined : label\n const effectiveDescription = shouldSuppressLabel ? undefined : description\n\n // Id 連結\n //\n // ── 2026-04-21 bug fix ──\n // 原本:`idProp ?? fieldCtx?.id ?? generatedId`。\n // 在 Field 內 fieldCtx.id 存在,CheckboxGroup 所有 children 共用同一個 id →\n // 每個 checkbox 的 `<label htmlFor={sameId}>` 全指向第一個 checkbox →\n // **點任何 label 都只開關第一個 checkbox(real bug)**。\n //\n // 修法:group 內的 checkbox 強制用 generatedId(唯一),不沿用 Field id;\n // solo in Field(binary toggle)才沿用 fieldCtx.id 讓 FieldLabel htmlFor 生效。\n const generatedId = React.useId()\n const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))\n\n // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode=\"display\"> 漏 cascade)\n const resolvedDisabled = useResolvedFieldDisabled(disabled)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n // mode='disabled'(如 DataTable disabled cell 直傳)必須落到真 disabled chrome —\n // 2026-06-12 修:原本只看 resolvedDisabled(prop/fieldCtx),mode='disabled' 無人消費\n // → disabled boolean cell 渲出可 focus 的正常外觀 checkbox(違 field-controls.spec L286)\n const effectiveDisabled = resolvedDisabled || resolvedMode === 'disabled'\n // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)\n const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'\n\n // ── mode='display'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 primitive、渲染 ✓ / —(checked=true → ✓ / 其他 → —)。取代 BooleanDisplay。\n if (resolvedMode === 'display') {\n const isChecked = props.checked === true\n return isChecked\n ? <span className=\"text-foreground\">✓</span>\n : <span className=\"text-fg-muted\">—</span>\n }\n\n // ── mode='readonly' in Field(2026-06-12 user 拍板「灰框 + ✓/—」)─────────────\n // Field 內 readonly boolean = readonly 灰框 chrome + display 同款值語言 ✓/—。\n // 灰框消費 fieldWrapperStyles 同一 cva = 與 Input readonly 字面同源(SSOT,改一處全動)。\n // 理由:同一張 readonly 表單裡文字控件有 bg-readonly 灰框鎖定訊號,boolean 保留全彩\n // 控件會誤導「仍可操作」(世界級 0/4 用原樣鎖互動:Salesforce=✓ 靜態 glyph /\n // SAP=靜態文字 / Atlassian=readView / Ant Pro=文字)。\n // Scope:僅 Field 內且無 inline label(FieldLabel 接管 label 的表單欄位場景);\n // standalone readOnly(settings list / SelectionItem row)維持原樣鎖互動不變。\n if (effectiveReadOnly && insideField && effectiveLabel == null) {\n const isChecked = (props.checked ?? props.defaultChecked) === true\n const boxSize = resolvedBoxSize\n return (\n <div\n role=\"checkbox\"\n aria-checked={isChecked}\n aria-readonly=\"true\"\n aria-labelledby={fieldCtx?.labelId}\n aria-invalid={fieldCtx?.invalid || undefined}\n data-readonly=\"true\"\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ size: boxSize, mode: 'readonly', variant: 'default' }),\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n className,\n )}\n >\n {isChecked ? <span className=\"text-foreground\">✓</span> : <span className=\"text-fg-muted\">—</span>}\n </div>\n )\n }\n\n const rootEl = (\n <CheckboxPrimitive.Root\n id={inputId}\n ref={ref}\n disabled={effectiveDisabled}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"grid place-content-center text-current\">\n {props.checked === 'indeterminate'\n ? <Minus style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n : <Check style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n }\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n\n // 無 label → 只渲染 checkbox 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 透過 SelectionItem 包裝(control 在左、label+description 在右)\n return (\n <SelectionItem\n control={rootEl}\n label={effectiveLabel}\n description={effectiveDescription}\n icon={icon}\n avatar={avatar}\n htmlFor={inputId}\n disabled={effectiveDisabled}\n size={sizeKey}\n />\n )\n }\n)\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\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 checkboxMeta = {\n component: 'Checkbox',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body' },\n },\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-primary', 'bg-primary-hover', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Checkbox, checkboxVariants }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checkbox.js","sources":["../../../src/components/Checkbox/checkbox.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.\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check, Minus } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { FieldMode, FieldVariant } from \"@/design-system/components/Field/field-types\"\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from \"@/design-system/components/Field/field-context\"\nimport { fieldWrapperStyles } from \"@/design-system/components/Field/field-wrapper\"\nimport { SelectionItem } from \"@/design-system/components/SelectionControl/selection-item\"\nimport type { LucideIcon } from \"lucide-react\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { CheckboxGroupContext } from \"./checkbox-group\"\n\n// ── Variants ────────────────────────────────────────────────────────────────\n// 三種尺寸(sm/md=16px, lg=20px),對齊 icon 系統與 SelectionItem。\n\nconst checkboxVariants = cva(\n [\n 'grid place-content-center shrink-0 rounded-md',\n 'border border-border bg-surface',\n 'transition-colors duration-150',\n 'hover:border-border-hover',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'data-[state=checked]:bg-primary data-[state=checked]:text-on-emphasis data-[state=checked]:border-primary',\n 'data-[state=checked]:hover:bg-primary-hover data-[state=checked]:hover:border-primary-hover',\n 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-on-emphasis data-[state=indeterminate]:border-primary',\n 'data-[state=indeterminate]:hover:bg-primary-hover data-[state=indeterminate]:hover:border-primary-hover',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:border-transparent disabled:hover:border-transparent',\n 'disabled:data-[state=checked]:bg-disabled disabled:data-[state=checked]:text-fg-disabled disabled:data-[state=checked]:border-transparent',\n 'disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent',\n // readOnly:鎖定互動但維持 checked/unchecked 視覺\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n 'data-[readonly=true]:hover:border-border',\n ],\n {\n variants: {\n size: {\n sm: 'h-4 w-4',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Check Icon Size ─────────────────────────────────────────────────────────\nconst checkIconSize: Record<string, number> = { sm: 12, md: 12, lg: 16 }\n\n// ── Check Icon Stroke Weight ────────────────────────────────────────────────\n// 16px 以下 icon 視覺不夠顯眼 → 用較粗 stroke 補償。Lucide 預設 strokeWidth=2 在\n// 12px 下 render 約 1px 線寬,視覺偏細;加粗到 3.5(render ≈ 1.75px)才有足夠視覺權重。\n// 16px 用 2.5(render ≈ 1.67px)讓 checked 態的 check icon 夠顯眼。\n//\n// 為什麼不是 3 / 2:本 session 實測 3 / 2 在 storybook 上兩個 size 的 render 線寬差僅\n// 0.17px(1.5 vs 1.33),使用者肉眼看不出差異(image #64 回報)。改為 3.5 / 2.5:\n// - md 12px × 3.5 → 1.75px 線寬\n// - lg 16px × 2.5 → 1.67px 線寬\n// 兩者仍接近但 md 的線寬 **絕對值** 跟 16px 預設(1.33)有更明顯差異,視覺上「小 check 更粗」。\n//\n// 世界級對照:iOS HIG / Material 3 / Polaris 的 checkmark 在 <16px 下皆加粗 compensate。\n// 為什麼不用 Lucide absoluteStrokeWidth:那保持「絕對 px 粗細」,我們反而要「小尺寸比例更粗」。\n//\n// Check 與 Minus(indeterminate)共用此規則;Switch 的 SPECS.checkStroke 採同樣值。\n// 2026-05-18 簡化 per user 視覺證「sm/md 3.5 vs lg 2.5 看不出差別」(image #64 + 2nd round\n// 圖一 video proof)+「做完」approval:\n// - 原 {3.5, 3.5, 2.5} → effective render thickness 1.75 / 1.75 / 1.67 = 跨 size 差 0.08px(視覺看不出)\n// - 改 {3, 3, 2.5} 保留 sm/md 小尺寸 legibility insurance(per iOS HIG / Material 3 cite)\n// + lg 仍稍粗於 Lucide default 2(保留 compensation 主旨,但不過度差異化)\n// ⚠️ 2026-06-12:發現 base.css `.lucide{stroke-width:1.75}` 全域規則自 2026-04-08 起\n// 無條件蓋掉本 prop(CSS class > SVG attribute)→ 本表從未真渲染過,上述 05-18 視覺\n// 測試兩者實為 0.875px(證據污染)。base.css 已改 `[stroke-width='2']` 限定,本表自此\n// 真實生效(pixel 驗證 1.50px)。SSOT → .claude/references/ui-dev-rules.md「小尺寸 icon stroke 補償」\nconst checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CheckboxProps\n extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,\n VariantProps<typeof checkboxVariants> {\n /**\n * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldLabel 接管)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用。\n * 套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldDescription 接管)。\n */\n description?: React.ReactNode\n /** 可選左側 icon(label 前)— 2026-06-12 M30 修:轉發 SelectionItem 既有 canonical 槽(selection-item.tsx jsDoc 為 SSOT;與 avatar 互斥)*/\n icon?: LucideIcon\n /** 可選左側 avatar(label 前)— 同上,SelectionItem 槽轉發 */\n avatar?: AvatarData\n /**\n * readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * 用於表單 readonly 呈現、DataTable cell 非編輯態。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 checkbox(預設)\n * display — **純展示**:渲染 ✓ / —(無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。\n * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal\n * disabled — 同 disabled prop:降色 + 鎖互動\n */\n mode?: FieldMode\n /**\n * Visual chrome — checkbox 本體無 input wrapper variant,本 prop 對 checkbox 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(\n (\n {\n className,\n size,\n label,\n icon,\n avatar,\n description,\n readOnly = false,\n disabled,\n mode,\n // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const iconPx = checkIconSize[sizeKey]\n const iconStrokeWidth = checkStrokeWidth[sizeKey]\n\n // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管\n // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='display' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n //\n // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox\n // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,\n // 不能被 Field context 吞掉。AR50 的根因就是這個 branch 之前誤把 group 內的 checkbox\n // label 全清空,導致 sheet 內 3 個 checkbox 沒 label。\n const fieldCtx = useFieldContext()\n const checkboxGroupCtx = React.useContext(CheckboxGroupContext)\n const insideField = fieldCtx?.hasFieldWrapper === true\n const insideGroup = checkboxGroupCtx?.inGroup === true\n const shouldSuppressLabel = insideField && !insideGroup\n const effectiveLabel = shouldSuppressLabel ? undefined : label\n const effectiveDescription = shouldSuppressLabel ? undefined : description\n\n // Id 連結\n //\n // ── 2026-04-21 bug fix ──\n // 原本:`idProp ?? fieldCtx?.id ?? generatedId`。\n // 在 Field 內 fieldCtx.id 存在,CheckboxGroup 所有 children 共用同一個 id →\n // 每個 checkbox 的 `<label htmlFor={sameId}>` 全指向第一個 checkbox →\n // **點任何 label 都只開關第一個 checkbox(real bug)**。\n //\n // 修法:group 內的 checkbox 強制用 generatedId(唯一),不沿用 Field id;\n // solo in Field(binary toggle)才沿用 fieldCtx.id 讓 FieldLabel htmlFor 生效。\n const generatedId = React.useId()\n const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))\n\n // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode=\"display\"> 漏 cascade)\n const resolvedDisabled = useResolvedFieldDisabled(disabled)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n // mode='disabled'(如 DataTable disabled cell 直傳)必須落到真 disabled chrome —\n // 2026-06-12 修:原本只看 resolvedDisabled(prop/fieldCtx),mode='disabled' 無人消費\n // → disabled boolean cell 渲出可 focus 的正常外觀 checkbox(違 field-controls.spec L286)\n const effectiveDisabled = resolvedDisabled || resolvedMode === 'disabled'\n // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)\n const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'\n\n // ── mode='display'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 primitive、渲染 ✓ / —(checked=true → ✓ / 其他 → —)。取代 BooleanDisplay。\n if (resolvedMode === 'display') {\n const isChecked = props.checked === true\n return isChecked\n ? <span className=\"text-foreground\">✓</span>\n : <span className=\"text-fg-muted\">—</span>\n }\n\n // ── mode='readonly' in Field(2026-06-12 user 拍板「灰框 + ✓/—」)─────────────\n // Field 內 readonly boolean = readonly 灰框 chrome + display 同款值語言 ✓/—。\n // 灰框消費 fieldWrapperStyles 同一 cva = 與 Input readonly 字面同源(SSOT,改一處全動)。\n // 理由:同一張 readonly 表單裡文字控件有 bg-readonly 灰框鎖定訊號,boolean 保留全彩\n // 控件會誤導「仍可操作」(世界級 0/4 用原樣鎖互動:Salesforce=✓ 靜態 glyph /\n // SAP=靜態文字 / Atlassian=readView / Ant Pro=文字)。\n // Scope:僅 Field 內且無 inline label(FieldLabel 接管 label 的表單欄位場景);\n // standalone readOnly(settings list / SelectionItem row)維持原樣鎖互動不變。\n if (effectiveReadOnly && insideField && effectiveLabel == null) {\n const isChecked = (props.checked ?? props.defaultChecked) === true\n const boxSize = resolvedBoxSize\n return (\n <div\n role=\"checkbox\"\n aria-checked={isChecked}\n aria-readonly=\"true\"\n aria-labelledby={fieldCtx?.labelId}\n aria-invalid={fieldCtx?.invalid || undefined}\n data-readonly=\"true\"\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ size: boxSize, mode: 'readonly', variant: 'default' }),\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n className,\n )}\n >\n {isChecked ? <span className=\"text-foreground\">✓</span> : <span className=\"text-fg-muted\">—</span>}\n </div>\n )\n }\n\n const rootEl = (\n <CheckboxPrimitive.Root\n id={inputId}\n ref={ref}\n disabled={effectiveDisabled}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"grid place-content-center text-current\">\n {props.checked === 'indeterminate'\n ? <Minus style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n : <Check style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n }\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n\n // 無 label → 只渲染 checkbox 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 透過 SelectionItem 包裝(control 在左、label+description 在右)\n return (\n <SelectionItem\n control={rootEl}\n label={effectiveLabel}\n description={effectiveDescription}\n icon={icon}\n avatar={avatar}\n htmlFor={inputId}\n disabled={effectiveDisabled}\n size={sizeKey}\n />\n )\n }\n)\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\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 checkboxMeta = {\n component: 'Checkbox',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },\n },\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-primary', 'bg-primary-hover', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Checkbox, checkboxVariants }\n"],"names":[],"mappings":"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"}
|
|
@@ -115,7 +115,7 @@ export interface ComboboxProps {
|
|
|
115
115
|
* **2026-05-13 v2 deprecate path**:原 PeoplePicker pass `{8}` 假設「Combobox tagPadding=4px,4+8=12」
|
|
116
116
|
* 但 `tagPadding[size]` 是 density-dependent calc `(field-height - icon-size) / 2`,只在 md size +
|
|
117
117
|
* default density 才 = 4px;其他 size/density 漂 6/8px → 4+8=12 公式破。改 PeoplePicker 直接 inject
|
|
118
|
-
* `!px-
|
|
118
|
+
* `!px-[var(--field-px)]` className 到 Combobox Field wrapper(per people-picker.spec.md:94 v2),`tagAreaPaddingLeftPx`
|
|
119
119
|
* 走 undefined。Future 仍保留此 prop 給其他 consumer 精準調整 padding,但 PeoplePicker 已不再用。
|
|
120
120
|
*/
|
|
121
121
|
tagAreaPaddingLeftPx?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../../../src/components/Combobox/combobox.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAM3F,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,mDAAmD,CAAA;AAcrG;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;CAEvD;AAgLD,KAAK,qBAAqB,GAAG,QAAQ,GAAG,KAAK,CAAA;AA6I7C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,uFAAuF;IACvF,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACpC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW;IACX,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;kGAE8F;IAC9F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,4FAA4F;IAC5F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;gDAC4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,mFAAmF;IACnF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,KAAK,CAAC,SAAS,CAAA;IAC/F;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAA;IAC7E;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;sEAGkE;IAClE,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAA;IACrC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;
|
|
1
|
+
{"version":3,"file":"combobox.d.ts","sourceRoot":"","sources":["../../../src/components/Combobox/combobox.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAM3F,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,mDAAmD,CAAA;AAcrG;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;CAEvD;AAgLD,KAAK,qBAAqB,GAAG,QAAQ,GAAG,KAAK,CAAA;AA6I7C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,uFAAuF;IACvF,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACpC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW;IACX,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;kGAE8F;IAC9F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,4FAA4F;IAC5F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;gDAC4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,mFAAmF;IACnF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,KAAK,CAAC,SAAS,CAAA;IAC/F;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAA;IAC7E;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;sEAGkE;IAClE,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAA;IACrC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAyWD,QAAA,MAAM,QAAQ,sFAab,CAAA;AAKD,eAAO,MAAM,YAAY;;;;;;;;;;;CAef,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
|
@@ -188,6 +188,7 @@ function ReadonlyMultiSelect({
|
|
|
188
188
|
const variant = variantProp ?? "default";
|
|
189
189
|
const sz = size ?? "md";
|
|
190
190
|
const iconSize = sz === "lg" ? 20 : 16;
|
|
191
|
+
const tagHeight = sz === "sm" ? 20 : 24;
|
|
191
192
|
const containerRef = React.useRef(null);
|
|
192
193
|
const hasTags = ((value == null ? void 0 : value.length) ?? 0) > 0;
|
|
193
194
|
if (resolvedMode === "display") {
|
|
@@ -217,10 +218,12 @@ function ReadonlyMultiSelect({
|
|
|
217
218
|
// 2026-05-18 #6A Round 1 Step 1/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:451):
|
|
218
219
|
// readonly/disabled path 對齊 L293 display wrapper 已 ship 的 overflow-hidden fix。
|
|
219
220
|
// M10 propagation:原 overflow-visible 讓 readonly tag 越界蓋 indicator,跟 display 不對稱。
|
|
220
|
-
|
|
221
|
+
// 2026-06-27 對齊 edit path(L598-617):wrap 時 items-start + chevron self-start/tagHeight 鎖第一行;
|
|
222
|
+
// paddingRight: var(--field-px) re-assert 右緣 12px(tagPadding 對稱 calc 會吃掉右緣,跟 edit 一致)。
|
|
223
|
+
wrap ? "flex-wrap items-start py-1" : "overflow-hidden",
|
|
221
224
|
className
|
|
222
225
|
),
|
|
223
|
-
style: { gap: GAP, ...wrap ? { height: "auto" } : void 0 },
|
|
226
|
+
style: { gap: GAP, paddingRight: "var(--field-px)", ...wrap ? { height: "auto" } : void 0 },
|
|
224
227
|
"data-field-mode": resolvedMode,
|
|
225
228
|
"aria-disabled": resolvedMode === "disabled" ? true : void 0,
|
|
226
229
|
children: [
|
|
@@ -235,7 +238,7 @@ function ReadonlyMultiSelect({
|
|
|
235
238
|
disabled: resolvedMode === "disabled"
|
|
236
239
|
}
|
|
237
240
|
) : /* @__PURE__ */ jsx("span", { className: "text-fg-muted", children: EMPTY_DISPLAY }),
|
|
238
|
-
(variant === "naked" ? !!showDisplayEndIcon :
|
|
241
|
+
(variant === "naked" ? !!showDisplayEndIcon : resolvedMode === "disabled") && /* @__PURE__ */ jsx(ItemSuffix, { className: cn("pointer-events-none", wrap && "self-start"), style: wrap ? { height: tagHeight } : void 0, children: /* @__PURE__ */ jsx(ChevronDown, { size: iconSize, className: cn("shrink-0", resolvedMode === "disabled" ? "text-fg-disabled" : "text-fg-muted"), "aria-hidden": true }) })
|
|
239
242
|
]
|
|
240
243
|
}
|
|
241
244
|
);
|
|
@@ -265,6 +268,8 @@ function NativeCombobox({
|
|
|
265
268
|
const handleAdd = (v) => {
|
|
266
269
|
if (!value.includes(v)) onChange == null ? void 0 : onChange([...value, v]);
|
|
267
270
|
};
|
|
271
|
+
const selectRef = React.useRef(null);
|
|
272
|
+
const tagAreaRef = React.useRef(null);
|
|
268
273
|
if (resolvedMode !== "edit") {
|
|
269
274
|
return /* @__PURE__ */ jsx(ReadonlyMultiSelect, { mode: resolvedMode, variant, size, options, value, wrap, className, showDisplayEndIcon });
|
|
270
275
|
}
|
|
@@ -273,8 +278,6 @@ function NativeCombobox({
|
|
|
273
278
|
return { value: v, label: ((_a = options.find((o) => o.value === v)) == null ? void 0 : _a.label) ?? v };
|
|
274
279
|
});
|
|
275
280
|
const unselected = options.filter((o) => !value.includes(o.value));
|
|
276
|
-
const selectRef = React.useRef(null);
|
|
277
|
-
const tagAreaRef = React.useRef(null);
|
|
278
281
|
const tagHeight = size === "sm" ? 20 : 24;
|
|
279
282
|
const selectDropdown = unselected.length > 0 ? /* @__PURE__ */ jsxs(
|
|
280
283
|
"select",
|
|
@@ -304,7 +307,7 @@ function NativeCombobox({
|
|
|
304
307
|
error && ["border-error hover:border-error-hover", "focus-within:border-error focus-within:hover:border-error"],
|
|
305
308
|
className
|
|
306
309
|
),
|
|
307
|
-
style: { paddingRight: "
|
|
310
|
+
style: { paddingRight: "var(--field-px)", ...wrap ? { height: "auto" } : void 0 },
|
|
308
311
|
"data-field-mode": "edit",
|
|
309
312
|
"data-error": error ? "" : void 0,
|
|
310
313
|
onClick: (e) => {
|
|
@@ -428,9 +431,6 @@ function CustomCombobox({
|
|
|
428
431
|
React.useEffect(() => {
|
|
429
432
|
if (!open) setSearch("");
|
|
430
433
|
}, [open]);
|
|
431
|
-
if (resolvedMode !== "edit") {
|
|
432
|
-
return /* @__PURE__ */ jsx(ReadonlyMultiSelect, { mode: resolvedMode, variant, size, options, value, wrap, className, showDisplayEndIcon });
|
|
433
|
-
}
|
|
434
434
|
const items = React.useMemo(
|
|
435
435
|
() => value.map((v) => {
|
|
436
436
|
var _a;
|
|
@@ -457,6 +457,9 @@ function CustomCombobox({
|
|
|
457
457
|
})),
|
|
458
458
|
[filteredOptions]
|
|
459
459
|
);
|
|
460
|
+
if (resolvedMode !== "edit") {
|
|
461
|
+
return /* @__PURE__ */ jsx(ReadonlyMultiSelect, { mode: resolvedMode, variant, size, options, value, wrap, className, showDisplayEndIcon });
|
|
462
|
+
}
|
|
460
463
|
const chevronEl = /* @__PURE__ */ jsx(ChevronDown, { size: iconSize, className: cn("shrink-0 text-fg-muted transition-transform", open && "rotate-180"), "aria-hidden": true });
|
|
461
464
|
const trigger = /* @__PURE__ */ jsxs(
|
|
462
465
|
"div",
|
|
@@ -483,7 +486,7 @@ function CustomCombobox({
|
|
|
483
486
|
error && ["border-error hover:border-error-hover", "focus-within:border-error focus-within:hover:border-error"],
|
|
484
487
|
className
|
|
485
488
|
),
|
|
486
|
-
style: { paddingRight: "
|
|
489
|
+
style: { paddingRight: "var(--field-px)", ...wrap ? { height: "auto" } : void 0 },
|
|
487
490
|
"data-field-mode": "edit",
|
|
488
491
|
"data-error": error ? "" : void 0,
|
|
489
492
|
onKeyDown: (e) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combobox.js","sources":["../../../src/components/Combobox/combobox.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// @renderer-symmetry-allow: ComboboxTagStack(display path)接 consumer tagRenderer 是 Stream C 下 cycle 工作 — 2026-05-12 先 ship Issues 2/3/4 surgical fixes(placeholder vocabulary + cell surface metrics + placeholder truncate),tagRenderer display-path unify deferred per field-controls.spec.md 共享 contract a。當前 multi=1 顯示已透過 PeoplePicker tagRenderer 線 314 PersonDisplay SSOT 對齊;其他 Combobox consumer 走 default `<Tag>` 純文字 backward-compat。\n// code-quality-allow: file-size — Combobox 含 NativeCombobox/CustomCombobox/useOverflowCount/OverflowTagList/ComboboxTagStack 5 子元件 + 共用 helpers,split-into-files 會破壞 measurement closures + 重複 type definitions。\nimport * as React from 'react'\nimport { X, ChevronDown } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, EMPTY_DISPLAY, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { Tag } from '@/design-system/components/Tag/tag'\nimport { ItemInlineAction, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { OverflowIndicator } from '@/design-system/components/OverflowIndicator/overflow-indicator'\nimport { SelectMenu, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'\nimport { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n// ── constants ───────────────────────────────────────────────────────────────\n\nconst GAP = 4\n\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/**\n * Combobox option schema(2026-05-10 post-Issue-4 audit unify):**explicit extends\n * SelectMenuOption(primitive SSOT)** — 避免重蹈先前 PeoplePicker 改壞的 wrapper schema drift。\n *\n * Why `extends SelectMenuOption`(per user 「全盤檢查避免下次又改壞或是偏移」要求):\n * 原 `interface ComboboxOption { value: string; label: string }` 是 weak schema,跟 Select 的\n * `ComboboxOption`(同名)雙重宣告但欄位不同 → TypeScript 不抓(同名 interface 在不同 file\n * 各 export,consumer import 到哪個版本看 import path)→ schema drift。\n * PeoplePicker multi-mode 走 Combobox 路徑,dropdown menu rows lose avatar / description —\n * user 看到「single mode 有 avatar / multi mode 沒 avatar」inconsistency。\n *\n * Fix(post-Issue-4 follow-up):extend SelectMenuOption → 全 primitive surface 自動繼承。\n * Wrapper-only field 都沒有 → empty body interface(future 加 wrapper-only field 加在此處)。\n * `menuOptions` mapping(below)forward 全 SelectMenuOption surface。\n *\n * 對齊 Polaris ChoiceList / Material Autocomplete / Carbon Dropdown 的 wrapper-vs-primitive\n * schema-extension idiom。Hook `check_wrapper_primitive_schema_drift.sh`(M30 機械強制)。\n */\nexport interface ComboboxOption extends SelectMenuOption {\n // (no wrapper-only fields yet — kept for future扩 + same-name SSOT cross-wrapper)\n}\n\n// ── useOverflowCount (unchanged) ────────────────────────────────────────────\n\nfunction useOverflowCount(\n containerRef: React.RefObject<HTMLDivElement | null>,\n tagEls: React.MutableRefObject<(HTMLDivElement | null)[]>,\n overflowEl: React.RefObject<HTMLDivElement | null>,\n totalCount: number,\n enabled: boolean,\n gap: number = GAP, // (2026-05-07 v15.13)stack avatar 模式傳 0\n visibleCountOverride?: number, // 2026-05-15 Bug 3 fix:override DOM measurement(PeoplePicker stack 走 formula primitive)\n): { visibleCount: number; ready: boolean } {\n const [state, setState] = React.useState({ visibleCount: totalCount, ready: !enabled })\n // 2026-05-18 Round 6 fix(per Codex M31 Round 6 H7 verdict + Step 5 共識):\n // `ofEl.offsetWidth` 在 expanded state(visibleCount === totalCount → overflow=0 →\n // `OverflowIndicator` line 92 `return null` → ofEl wrapper empty)= 0,fallback 60;\n // 在 collapsed state(+N rendered)= 真實寬(~28-32px)。同 `available` 在兩 state 給\n // 不同 verdict → 臨界值區 `max(B+g+Q, B+g+O) ≤ available < B+g+F` 振盪。Cache last\n // non-zero measurement → measurement state-independent → oscillation 收斂。\n // 初始 60 沿用舊 fallback(無 measure 史 ok-ish over-estimate)。\n const lastOverflowWRef = React.useRef<number>(60)\n\n // 2026-05-16 RACE FIX(user 抓「逐個 click 滿 6」vs「取消全選→再全選」same length 不同 visible):\n //\n // 原 useEffect + 雙 rAF 沒 capture rAF IDs → cleanup 不 cancel pending rAFs。\n // Path B(length=6→0→6):length=0 時 override=undefined 走 internal calc 排 rAF,\n // 然後 length=6 + override=N → deps change → cleanup 跑(disconnect ResizeObserver 但\n // 不 cancel rAF)→ 新 useEffect 跑 override 寫 el.hidden → 舊 rAF 仍 fire → 跑舊\n // internal calc → 覆寫 el.hidden 用 internal measurement(不一致於 override formula)。\n //\n // Fix:\n // 1. useEffect → useLayoutEffect:tighter timing,measurement 在 paint 前 sync\n // 2. Capture rAF IDs,cancel on cleanup\n // 3. scheduleCalc 函式包裝,cancel in-flight rAF 才排新一輪(避免 ResizeObserver\n // re-fire 堆 rAF)\n //\n // 對齊 2026-05-14 I3 fix comment「user 抓『全選 vs 逐個勾 result 不同』」 — 當時 fix\n // 只加 double-rAF 但漏 cancel,本次補完 race close。\n // 2026-05-18 Round 5 fix(per visual test probe):useLayoutEffect 在 nested component 場景\n // 會在 parent ref attach 前 fire(child layout effect 先於 parent ref attach)→ containerRef.current\n // null → early return → calc never runs → setProperty never called → CSS var unset → tag overflow。\n // 改 useEffect:fires AFTER paint,所有 refs 都 attach。double-rAF guard ensures layout done。\n // Trade-off:可能 1-2 frame flicker,但 functional setState guard + paint target measurement 已 cover。\n React.useEffect(() => {\n if (!enabled || totalCount === 0) { setState({ visibleCount: totalCount, ready: true }); return }\n if (visibleCountOverride !== undefined) {\n for (let i = 0; i < tagEls.current.length; i++) {\n const el = tagEls.current[i]\n if (el) el.hidden = i >= visibleCountOverride\n }\n const ofEl = overflowEl.current\n if (ofEl) ofEl.hidden = visibleCountOverride >= totalCount\n setState({ visibleCount: visibleCountOverride, ready: true }); return\n }\n // totalCount=1 fast path:single-tag case 直接 visible 不跑 measurement loop。\n // (歷史:c90d029 曾移除此 bypass,後復原 — 移除會造成 narrow cell 1-selected 跑 unbounded Tag\n // measurement 後 visibleCount=0 → 顯 +1 indicator 而非 single tag,違反 PeoplePicker length===1\n // 走 PersonDisplay SSOT。)\n // 2026-05-16 Round 5 codex edge case fix:explicit unhide DOM nodes(對齊 override branch)。\n // 原 fast-path 只設 React state 不動 `el.hidden`,如 wrappers 之前 hidden 殘留(從 length>1 降到 1)\n // 可能視覺漏顯。Override branch L78-80 同 contract 對齊。\n if (totalCount === 1) {\n for (let i = 0; i < tagEls.current.length; i++) {\n const el = tagEls.current[i]\n if (el) el.hidden = i >= 1\n }\n const ofEl = overflowEl.current\n if (ofEl) ofEl.hidden = true\n setState({ visibleCount: 1, ready: true }); return\n }\n const container = containerRef.current\n if (!container) return\n\n const calc = () => {\n const cs = getComputedStyle(container)\n const available = container.clientWidth - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0)\n // 2026-05-18 Round 5 fix(per user 拍板「那就開始做」+ Codex M31 Round 5 verdict):\n // inject available 成 CSS var,Tag 用 explicit length 而非 cyclic percentage(避 CSS Sizing 3\n // §5.2.1 cyclic percentage 退化問題)。\n container.style.setProperty('--combobox-tag-area-inline-size', `${available}px`)\n for (const el of tagEls.current) if (el) el.hidden = false\n const ofEl = overflowEl.current\n if (ofEl) ofEl.hidden = false\n // 2026-05-18 Round 6 fix:cache last non-zero ofEl width 破 expanded/collapsed state\n // 量測二態(expanded → ofEl 空 offsetWidth=0 / collapsed → real ~28-32px)。沒 cache\n // 之前同 `available` 在兩 state 給不同 verdict → 永動。詳本 hook 頂部 ref 註解。\n const measuredOverflowW = ofEl?.offsetWidth || 0\n if (measuredOverflowW > 0) lastOverflowWRef.current = measuredOverflowW\n const overflowW = lastOverflowWRef.current\n // **#3 fix(2026-05-04)**:width-check 先於 count++,並處理 i=0 邊界(1 tag 自身就太寬 → 全 hidden 顯 +N)\n // 之前 bug:greedy `count++` 永遠至少 = 1,1-tag-too-wide case 視覺呈半個 tag clipped + +N(錯)\n // 修後:1 tag 太寬時 count = 0,全 N tags 走 +N 顯 indicator\n // 2026-05-18 Round 5:量 paint target `[data-tag-root]` 而非 wrapper(per codex Round 5 verdict)。\n // wrapper basis:auto 自由 grow,offsetWidth ≠ Tag actual paint width。\n let used = 0, count = 0\n for (let i = 0; i < totalCount; i++) {\n const el = tagEls.current[i]\n if (!el) continue\n const tagRoot = el.querySelector('[data-tag-root]') as HTMLElement | null\n const w = tagRoot ? tagRoot.getBoundingClientRect().width : el.offsetWidth\n const next = used + (count > 0 ? gap : 0) + w\n const remaining = totalCount - count - 1\n // width check FIRST(無 `count > 0` 短路):任何超寬都 break,包含 i=0 case\n if (remaining > 0 && next + gap + overflowW > available) break\n if (remaining === 0 && next > available) break\n used = next; count++\n }\n for (let i = 0; i < tagEls.current.length; i++) { const el = tagEls.current[i]; if (el) el.hidden = i >= count }\n if (ofEl) ofEl.hidden = count >= totalCount\n // 2026-05-18 Round 5 last guard(per Codex Round 5 verdict):safety net 防 measurement drift\n // (sub-pixel / rounding)。verify last visible tag rect.right ≤ container right,超出遞減 count。\n const containerRect = container.getBoundingClientRect()\n while (count > 0) {\n const lastEl = tagEls.current[count - 1]\n const tagRoot = lastEl?.querySelector('[data-tag-root]') as HTMLElement | null\n if (!tagRoot) break\n const tagRect = tagRoot.getBoundingClientRect()\n if (tagRect.right <= containerRect.right + 0.5) break\n count--\n if (lastEl) lastEl.hidden = true\n }\n if (ofEl) ofEl.hidden = count >= totalCount\n // 2026-05-18 A' fix functional setState value-equal guard(per Codex Round 3 verdict):\n // sync calc 在 useLayoutEffect 內 + ResizeObserver re-fire 同時跑 → 若每次都 new object setState\n // 觸發 re-render 即使值沒變,可能 cascade。回 prev 不更新 = avoid 抖動。\n setState(prev => (prev.visibleCount === count && prev.ready) ? prev : { visibleCount: count, ready: true })\n }\n\n // 2026-05-14 I3 fix(per codex M31 verdict + user 抓「全選 vs 逐個勾 result 不同」):\n // double-rAF ensures layout 完成 before measurement(原 single rAF 在 batched render\n // 場景 tag 還 0-width)。Plus observe per-item ResizeObserver — 任何 tag width 變動\n // 都 trigger recalc(deterministic regardless of commit order)。\n //\n // 2026-05-16 Race close:capture rAF IDs + cancel on cleanup(原版 race I3 沒 close\n // 完;user 抓 path A 逐個 click vs path B 取消全選再全選 same length 不同 visible)。\n // 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— sync calc in useLayoutEffect:\n // React 18 `useLayoutEffect` 在 DOM commit 後、瀏覽器繪製前同步跑,sync calc 在 paint 前\n // 完成 `el.hidden` 設定 + functional setState guard(value-equal 不更新 = 避免 ResizeObserver\n // 抖動 cascade rerender)。double-rAF 改 fallback only(ResizeObserver / async update path)。\n // 解 user verbatim「tag 過長 / 過多會先全顯再變 +N 閃動」root cause(per codex Round 3 cite\n // `combobox.tsx:248 render 沒設 hidden + L129 calc imperative 寫 DOM`)。\n // 對齊 React docs https://react.dev/reference/react/useLayoutEffect pre-paint guarantee。\n calc()\n let rafId1 = 0, rafId2 = 0\n const scheduleCalc = () => {\n if (rafId1) { cancelAnimationFrame(rafId1); rafId1 = 0 }\n if (rafId2) { cancelAnimationFrame(rafId2); rafId2 = 0 }\n rafId1 = requestAnimationFrame(() => {\n rafId1 = 0\n rafId2 = requestAnimationFrame(() => {\n rafId2 = 0\n calc()\n })\n })\n }\n scheduleCalc()\n const containerObs = new ResizeObserver(scheduleCalc)\n containerObs.observe(container)\n const itemObs = new ResizeObserver(scheduleCalc)\n for (const el of tagEls.current) {\n if (el) itemObs.observe(el)\n }\n return () => {\n if (rafId1) cancelAnimationFrame(rafId1)\n if (rafId2) cancelAnimationFrame(rafId2)\n containerObs.disconnect()\n itemObs.disconnect()\n }\n }, [containerRef, totalCount, enabled, gap, visibleCountOverride]) // 2026-05-15 Bug 3 fix:visibleCountOverride 入 deps,override 改 trigger recalc\n\n return state\n}\n\n// ── OverflowTagList (unchanged) ──────────────────────────────────────────────\n\ntype ComboboxOverflowShape = 'circle' | 'tag'\n\n// 2026-05-16 fix:overflow chip wrapper 必能跟 tag wrapper 套同 overlap class\n// (per user 物理模型「avatar 和 +N 都是同尺寸圓形 + 同 step」)。原 chip wrapper\n// 只有 `shrink-0`,在 stack 模式 -ml-0.5 不 apply → chip 不 overlap → 視覺多 22px\n// 額外空間 → length=4→4 / length=5→2+3 saw bug 物理根因。\n// PeoplePicker stack mode pass `'-ml-0.5 first:ml-0 relative inline-flex'` 對齊。\ninterface OverflowTagListProps {\n containerRef: React.RefObject<HTMLDivElement | null>\n items: { value: string; label: string }[]\n size: 'sm' | 'md' | 'lg'\n wrap: boolean\n renderTag: (item: { value: string; label: string }, index: number) => React.ReactNode\n /**\n * 2026-05-14 I4 fix(per codex M31 verdict + user 抓「display overflow 有 avatar / edit 無」):\n * Optional renderer for hidden items in `+N` overflow popover。Default fallback = `<Tag>{label}</Tag>`\n * (純文字 chip,backward-compat)。Consumer pass 此 prop 讓 hidden items 顯示同 avatar 視覺\n * (對齊 display MultiPersonDisplay overflow popover Tag avatar SSOT)。\n */\n renderHiddenTag?: (item: { value: string; label: string }) => React.ReactNode\n onRemove?: (value: string) => void\n trailing?: React.ReactNode\n /** Tag area gap in px(default 4)。Stack mode 傳 0 讓 negative margin 生效 */\n gap?: number\n /**\n * Optional class merged into each tag's outer measurement wrapper `<div className=\"shrink-0\">`.\n * (2026-05-07 v15.13)為 PeoplePicker stack mode 提供 hook point — 讓 stack avatar 走\n * `-ml-0.5 first:ml-0 relative inline-flex group/avatar` 達成 overlap + dismiss group selector,\n * 同時保留 `useOverflowCount` 量測 wrapper(必要,不可移除)。\n *\n * **Caveat(Q2 known tradeoff)**:`-ml-0.5` 負 margin 不改 each wrapper 的 `offsetWidth` →\n * `useOverflowCount` 累加按完整寬計算 → 視覺實際塞得進的 tag 數 > 量測判定能塞的數 →\n * **+N indicator 偏保守**(視覺還有空間但已顯 `+N`)。當前 v1 接受此 tradeoff;若窄 trigger\n * + 多人場景明顯不對,future 可加 `overlapPx` prop 讓量測補償。\n */\n tagWrapperClassName?: string\n /** 2026-05-16 fix:overflow chip 圓形 wrapper 套此 class(stack 模式套 `-ml-0.5` overlap),\n * 讓 chip 跟 avatar 同 step 物理 — 避免「chip 多 24px 額外空間」造成 saw transition。*/\n overflowWrapperClassName?: string\n /**\n * Overflow indicator(+N)形狀(2026-05-12 Round 7 fix,user 抓 PeoplePicker stack +N 該圓形):\n * - `'tag'`(default,backward-compat)— 矩形 chip(對齊 Combobox 文字 tag)\n * - `'circle'`(opt-in for avatar stack consumers)— 圓形 avatar-shape +N(對齊 GitHub picker idiom)\n * PeoplePicker stack mode pass `'circle'`,Combobox 文字 tag 自走 `'tag'`。\n */\n overflowShape?: ComboboxOverflowShape\n /**\n * 2026-05-15 Bug 3 fix:override visible count via formula-based primitive(PeoplePicker stack\n * 用 `avatar-stack-overflow` primitive deterministic formula 計算 visible,bypass DOM offsetWidth\n * measurement)。對齊 user SSOT「同 cell width 同 overflow 判斷」。\n */\n visibleCountOverride?: number\n}\n\nfunction OverflowTagList({ containerRef, items, size, wrap, renderTag, renderHiddenTag, onRemove, trailing, tagWrapperClassName, overflowWrapperClassName, gap = GAP, overflowShape = 'tag', visibleCountOverride }: OverflowTagListProps) {\n const tagEls = React.useRef<(HTMLDivElement | null)[]>([])\n const overflowEl = React.useRef<HTMLDivElement>(null)\n const { visibleCount, ready } = useOverflowCount(containerRef, tagEls, overflowEl, items.length, !wrap, gap, visibleCountOverride)\n tagEls.current.length = items.length\n\n if (wrap) return <>{items.map((item, i) => renderTag(item, i))}{trailing}</>\n\n const overflow = items.length - visibleCount\n const hiddenItems = items.slice(visibleCount)\n\n // 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— 撤掉舊的 `style={{ opacity: ready ? 1 : 0 }}`\n // gate(掛在 `<span className=\"contents\">` 上,但 CSS Display 3 spec 規定 `display:contents` 元素不產生 box,\n // parent opacity 對 children 無效 → gate 從沒生效是 dead code)。Flicker 已改 sync calc in useLayoutEffect 解\n // (L153 `calc()` 直跑,paint 前 hidden 設好)。`ready` state 保留供未來 instrumentation / debug,不再當 visual gate。\n // 保留 `<span className=\"contents\">` 維持 fragment-like rendering(parent JSX 期望 single child)。\n void ready // intentional: ready 留供 future debug,目前無 consumer\n return (\n <span className=\"contents\">\n {items.map((item, i) => (\n // 2026-05-14 I5 fix(per codex M31 verdict + user 抓「avatar stack 堆疊方向不一致」):\n // 加 z-index per-index — 前 item z 高(對齊 MultiPersonDisplay zIndex: visible.length - i\n // canonical + MUI AvatarGroup surplus pattern)。display + edit stack 堆疊方向統一。\n <div key={item.value} ref={el => { tagEls.current[i] = el }} className={cn('shrink-0 max-w-full', tagWrapperClassName)} style={{ zIndex: items.length - i }}>{renderTag(item, i)}</div>\n ))}\n <div ref={overflowEl} className={cn('shrink-0', overflowWrapperClassName)}>\n <OverflowIndicator count={overflow} shape={overflowShape} size={size}>\n {hiddenItems.map(item => (\n renderHiddenTag\n ? <React.Fragment key={item.value}>{renderHiddenTag(item)}</React.Fragment>\n : <Tag key={item.value} size=\"sm\" onRemove={onRemove ? () => onRemove(item.value) : undefined}>\n {item.label}\n </Tag>\n ))}\n </OverflowIndicator>\n </div>\n {trailing}\n </span>\n )\n}\n\n// ── Internal tag-stack renderer (consumed by ReadonlyMultiSelect / mode='display') ───\n//\n// Phase B2(2026-05-05):原 ComboboxDisplay sub-component 已 retire,改 inline `<Combobox mode=\"display\">`。\n// 本 helper 只負責 tag-stack 內容渲染(OverflowTagList 消費),不包 Field wrapper。\nfunction ComboboxTagStack({\n value, options, tagSize = 'md', wrap = false, containerRef: externalRef, disabled = false,\n}: {\n value?: string[] | null; options?: ComboboxOption[]; tagSize?: 'sm' | 'md' | 'lg'\n wrap?: boolean; containerRef?: React.RefObject<HTMLDivElement | null>; disabled?: boolean\n}) {\n const ownRef = React.useRef<HTMLDivElement>(null)\n if (!value || value.length === 0) return <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n const items = value.map(v => ({ value: v, label: options?.find(o => o.value === v)?.label ?? v }))\n const disabledClass = disabled ? 'bg-disabled text-fg-disabled' : undefined\n\n const content = (\n <OverflowTagList containerRef={externalRef ?? ownRef} items={items} size={tagSize} wrap={wrap}\n // 2026-05-18 7B' fix(per user 拍板「執行」+ Codex Round 3 共識)— 移除 `unbounded`,Tag 回預設\n // `max-w-40` cap(160px)+ 內建 `[data-tag-text] truncate min-w-0` 自帶 ellipsis。原 unbounded 是\n // 「cell-as-input narrow cell < 160px」設計(`tag.tsx:85-90`),但 generic Combobox tag display\n // 應走 Tag canonical cap-with-ellipsis(per `data-table.spec.md:235`「Tag 文字內部 truncate;\n // multiSelect 動態 +N」+ `data-table.spec.md:467`「Tag 不可被外層 overflow-hidden 裁掉邊框」)。\n // Trade-off:長 tag 觸發 +N 提前(160 + gap + overflowW > available 較 quick)— acceptable per user。\n // Round 2 dynamic slot maxWidth 提案經 user + codex 共識撤回(3 chicken-and-egg fatal,KISS 勝)。\n renderTag={(item) => <Tag size={tagSize} className={cn('shrink-0', disabledClass)}>{item.label}</Tag>} />\n )\n\n if (externalRef) return content\n // 2026-05-05 v9 fix(Bug 4):display path 內 wrapper 必須 `flex-1 min-w-0`,否則在 cell flex\n // parent 下不認領完整可用寬度 → OverflowTagList 量得寬度小於 edit path → 顯 `+N` 多於 edit。\n // edit path tagAreaRef wrapper 已是 `flex-1 min-w-0`(NativeCombobox/CustomCombobox line 258 / 354),\n // display 必對稱才 SSOT。\n // 2026-05-15 F1 Q3 fix(per user round 3 verbatim「單人選取時 Tag 越界蓋 indicator」):\n // `overflow-visible` → `overflow-hidden` 讓 narrow cell width 強制 clip(Tag 內建 truncate\n // 處理 text ellipsis,stack `-ml-0.5` 負 margin 在 wrapper 內不受影響)。對齊\n // `data-table.spec.md:233`「禁硬裁無 ellipsis」+ MUI X / Ant Table column.ellipsis 共識。\n // (2026-05-14 nakedCellRowModeAlign 同保留 — autoRowHeight cell first-line align canonical。)\n return (\n <div ref={ownRef} className={cn('flex-1 min-w-0 flex items-center', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: GAP }}>\n {content}\n </div>\n )\n}\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface ComboboxProps {\n mode?: FieldMode\n /** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */\n variant?: FieldVariant\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n options: ComboboxOption[]\n value?: string[]\n onChange?: (value: string[]) => void\n placeholder?: string\n className?: string\n disabled?: boolean\n wrap?: boolean\n clearable?: boolean\n /** 啟用搜尋 */\n searchable?: boolean\n /** Loading state(2026-05-15 audit B fix per user verbatim「dropdown 隨時可開,讀取在 panel 中間 CircularProgress」)。\n * Forward 給 SelectMenu primitive SSOT;dropdown 開啟時取代 options 顯 CircularProgress + loadingText。\n * Trigger 不變(user 隨時可開)。對齊 MUI Autocomplete `loadingText` + Field SSOT + Empty 元件 compose。*/\n loading?: boolean\n /** 搜尋框位置:menu(浮層內,預設)或 trigger(inline input) */\n searchIn?: 'menu' | 'trigger'\n /** 搜尋框 placeholder(未有選項時顯示)。Default: 「搜尋…」 */\n searchPlaceholder?: string\n /** 搜尋框 ARIA label。Default: 「搜尋選項」 */\n searchAriaLabel?: string\n /** Empty-selection placeholder text。Default: 「選擇…」 */\n emptyPlaceholder?: string\n /** a11y:無 Field wrapper 時提供 role='combobox' 的 accessible name(axe aria-input-field-name) */\n 'aria-label'?: string\n /** Initial open state(uncontrolled)— 對齊 Select.defaultOpen / Radix Popover canonical。\n * DataTable cell-as-input 1-step open 用 */\n defaultOpen?: boolean\n /** open state 變更 callback。DataTable cell-as-input 用:open=false → cell exit edit */\n onOpenChange?: (open: boolean) => void\n /**\n * Selected tag pill 客製 render(2026-05-07 v15.5)。\n *\n * 設了 → 每個 selected tag pill 走 consumer 提供的 ReactNode(收 item={value, label}\n * + onRemove,consumer 自己組 onRemove);沒設 → 走預設 `<Tag>` text-only pill。\n *\n * 用例:PeoplePicker(multi)用此 slot 把 selected tag 換成 avatar + name pill,而非\n * 純文字 Tag。對齊 PeoplePicker = Combobox wrapper SSOT。\n */\n tagRenderer?: (item: { value: string; label: string }, onRemove: () => void) => React.ReactNode\n /**\n * 2026-05-14 I4 fix:Optional renderer for hidden items in `+N` overflow popover\n * (對齊 display MultiPersonDisplay overflow popover 含 avatar SSOT)。PeoplePicker stack\n * pass 此 prop 讓 hidden items 顯 avatar + name(同 display path)。Default fallback\n * `<Tag>{label}</Tag>` 純文字 backward-compat。\n */\n renderHiddenTag?: (item: { value: string; label: string }) => React.ReactNode\n /**\n * @internal PeoplePicker stack wrapper 內部協議 — end-user 勿用(measurement-layer hook)。\n *\n * Optional class merged into each tag's outer measurement wrapper (2026-05-07 v15.13)。\n * Stack avatar 模式用此 hook point 達成 sibling-level overlap (`-ml-0.5`) + group selector\n * (`group/avatar`)— 既保留 Combobox 必要 measurement wrapper,又讓 dismiss/overlap 視覺生效。\n */\n tagWrapperClassName?: string\n /** @internal PeoplePicker stack wrapper 內部協議 — end-user 勿用(measurement-layer hook)。\n * 2026-05-16:overflow chip wrapper 套此 class(對齊 tagWrapperClassName)。Stack 模式\n * pass `-ml-0.5 first:ml-0` 讓 chip 跟 avatar 同 overlap step,物理上 chip = 1 個 slot 不\n * 外加 24px。Default undefined = chip 不 overlap(text-tag mode 等)。*/\n overflowWrapperClassName?: string\n /**\n * Tag area gap in px (2026-05-07 v15.13)。預設 4(pill mode 標準 spacing)。\n * Stack avatar 模式傳 0,讓 `tagWrapperClassName` 的 `-ml-0.5` negative margin 生效\n * (CSS `gap` 套在 flex container 上會強制 sibling spacing,蓋過 negative margin)。\n * **Q2 known tradeoff**:0 後 useOverflowCount 仍按 wrapper.offsetWidth 累加(不含 overlap\n * 補償)→ +N 偏保守。當前接受;若需精準可 future 加 `overlapPx` 補償邏輯。\n *\n * @internal PeoplePicker stack wrapper 內部協議 — end-user 勿用(measurement-layer hook)。\n */\n tagAreaGapPx?: number\n /**\n * tagAreaRef container 左 paddingLeft(px,2026-05-12 加,for PeoplePicker Avatar inset)。\n * Default undefined = no extra padding(Field wrapper `tagPadding[size]` calc 公式自然 inset)。\n * 設值時 tagAreaRef 增 `style.paddingLeft`,**useOverflowCount 的 `available = clientWidth -\n * paddingLeft - paddingRight` 自動 include**(`parseFloat(cs.paddingLeft)` 從 container CSS 抓)\n * → width calc 不漂移,無 side-effect。\n *\n * @internal PeoplePicker stack wrapper 內部協議 — 目前無 active consumer(PeoplePicker 傳 undefined),保留供未來精準 padding;新 consumer 請先評估。\n *\n * **2026-05-13 v2 deprecate path**:原 PeoplePicker pass `{8}` 假設「Combobox tagPadding=4px,4+8=12」\n * 但 `tagPadding[size]` 是 density-dependent calc `(field-height - icon-size) / 2`,只在 md size +\n * default density 才 = 4px;其他 size/density 漂 6/8px → 4+8=12 公式破。改 PeoplePicker 直接 inject\n * `!px-3` className 到 Combobox Field wrapper(per people-picker.spec.md:94 v2),`tagAreaPaddingLeftPx`\n * 走 undefined。Future 仍保留此 prop 給其他 consumer 精準調整 padding,但 PeoplePicker 已不再用。\n */\n tagAreaPaddingLeftPx?: number\n /**\n * Overflow indicator (+N) 形狀(2026-05-12 Round 7,opt-in 給 avatar stack consumer):\n * - `'tag'`(default)— 矩形 chip(Combobox 文字 tag default)\n * - `'circle'`(opt-in)— 圓形 avatar-shape(PeoplePicker stack 用)\n */\n overflowShape?: ComboboxOverflowShape\n /**\n * @internal PeoplePicker stack wrapper 內部協議 — end-user 勿用(measurement-layer override)。\n *\n * 2026-05-15 Bug 3 fix:override visible count via formula-based primitive(opt-in;default 走\n * DOM-based `useOverflowCount`)。PeoplePicker stack mode 用 `avatar-stack-overflow` primitive\n * deterministic formula 計算 visible count,forward 給 Combobox bypass DOM offsetWidth\n * measurement,避免 dual-algorithm drift。對齊 user SSOT「同 cell width 同 overflow 判斷」。\n */\n visibleCountOverride?: number\n /**\n * Display 是否渲 ChevronDown + Field naked wrapper(D-path opt-in,2026-05-08)\n * — DataTable cell display↔edit 像素級對齊用。預設 false(裸 tag stack,backward compat)。\n * 設 true 時 display 走 fieldWrapperStyles(naked variant)+ ItemSuffix ChevronDown,\n * 與 edit 同 DOM 結構,消除 Layer-B padding mismatch。\n */\n showDisplayEndIcon?: boolean\n}\n\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 readonly/disabled/display render ─────────────────────────────────\n\nfunction ReadonlyMultiSelect({\n mode, variant: variantProp, size, options, value, wrap, className, showDisplayEndIcon = false,\n}: Pick<ComboboxProps, 'mode' | 'variant' | 'size' | 'options' | 'value' | 'wrap' | 'className' | 'showDisplayEndIcon'>) {\n const resolvedMode = mode ?? 'readonly'\n const variant = variantProp ?? 'default'\n const sz = size ?? 'md'\n const iconSize = sz === 'lg' ? 20 : 16\n const containerRef = React.useRef<HTMLDivElement>(null)\n const hasTags = (value?.length ?? 0) > 0\n\n // mode='display'(Phase B2 2026-05-05):純內容輸出 — tag stack 不包 Field wrapper / 不 reserve 高度。\n // 對齊原 ComboboxDisplay sub-component(retired)。\n // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper + ItemSuffix ChevronDown,\n // 與 edit 同結構消除 cell display↔edit 像素偏移(Layer-B padding mismatch)。\n if (resolvedMode === 'display') {\n if (!showDisplayEndIcon) {\n // 2026-05-14 I2 fix(spec contract (e) display typography canonical):empty bare span 套\n // `fieldDisplayTextClass(sz)`(sm/md→text-body,lg→text-body-lg)— 對齊跨 Field family 統一。\n if (!hasTags) return <span className={cn(fieldDisplayTextClass(sz), 'text-fg-muted', className)}>{EMPTY_DISPLAY}</span>\n return (\n <ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap} />\n )\n }\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'display', variant, size: sz }), hasTags && tagPadding[sz], className)}\n data-field-mode=\"display\"\n >\n {hasTags ? (\n <ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap} />\n ) : (\n <span className={cn('flex-1 min-w-0', 'text-fg-muted')}>{EMPTY_DISPLAY}</span>\n )}\n <ItemSuffix className=\"pointer-events-none\">\n <ChevronDown size={iconSize} className=\"shrink-0 text-fg-muted\" aria-hidden />\n </ItemSuffix>\n </div>\n )\n }\n\n return (\n <div ref={containerRef}\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), hasTags && tagPadding[sz],\n // 2026-05-18 #6A Round 1 Step 1/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:451):\n // readonly/disabled path 對齊 L293 display wrapper 已 ship 的 overflow-hidden fix。\n // M10 propagation:原 overflow-visible 讓 readonly tag 越界蓋 indicator,跟 display 不對稱。\n wrap ? 'flex-wrap py-1' : 'overflow-hidden', className)}\n style={{ gap: GAP, ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode={resolvedMode}\n aria-disabled={resolvedMode === 'disabled' ? true : undefined}>\n {hasTags ? (\n <ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap}\n containerRef={containerRef} disabled={resolvedMode === 'disabled'} />\n ) : (\n <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n )}\n {/* 2026-06-10 類型身份 indicator 規則:readonly/disabled 保留 chevron(naked cell 依 showDisplayEndIcon);disabled → fg-disabled */}\n {(variant === 'naked' ? !!showDisplayEndIcon : true) && (\n <ItemSuffix className=\"pointer-events-none\">\n <ChevronDown size={iconSize} className={cn('shrink-0', resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted')} aria-hidden />\n </ItemSuffix>\n )}\n </div>\n )\n}\n\n// ── Native Combobox (mobile) ────────────────────────────────────────\n\n// 2026-05-16 Bug A root cause fix(Claude+Codex M31 Step 5 比稿 consensus,user verbatim\n// 「圖二/圖三 同 180px 不同 length 不同 visible — 跟 user 一開始抓的問題一模一樣」):\n// 公開 `Combobox.forwardRef` 之前用 `(props, _ref)` 把 ref drop,內部 `NativeCombobox` /\n// `CustomCombobox` 從未拿 ref → PeoplePicker `stackContainerRef.current` 永遠 null →\n// `useLayoutEffect` early return → `visibleCountOverride` 永遠 undefined →\n// Combobox 走原 internal `useOverflowCount` 60px chip fallback bug → drift。\n// Fix:internal `__triggerRef` prop(underscore = internal-only)attach root div;\n// 公開 `Combobox.forwardRef` 把 `ref` forward 為 `__triggerRef`。對齊 codex DS-wide iceberg\n// audit:`SelectMenu` / `DateGrid` / `Toast` 的 `_ref` 是 intentional documented(no DOM\n// target);唯本處 actionable drop。\ntype ComboboxInternalProps = ComboboxProps & { __triggerRef?: React.Ref<HTMLDivElement> }\n\nfunction NativeCombobox({\n mode, variant: variantProp, error = false, size = 'md', options, value = [], onChange, placeholder,\n className, disabled: disabledProp, wrap = false, clearable = false, showDisplayEndIcon = false,\n __triggerRef,\n}: ComboboxInternalProps) {\n const disabled = useResolvedFieldDisabled(disabledProp)\n const variant: FieldVariant = useResolvedFieldVariant(variantProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode;修 <Field mode=\"display\"> 漏 cascade\n const resolvedMode = useResolvedFieldMode({ mode, disabled })\n const iconSize = getIconSize(size)\n const showClear = clearable && value.length > 0 && resolvedMode === 'edit'\n\n const handleRemove = (v: string) => onChange?.(value.filter(x => x !== v))\n const handleAdd = (v: string) => { if (!value.includes(v)) onChange?.([...value, v]) }\n\n if (resolvedMode !== 'edit') {\n return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />\n }\n\n const items = value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v }))\n const unselected = options.filter(o => !value.includes(o.value))\n const selectRef = React.useRef<HTMLSelectElement>(null)\n const tagAreaRef = React.useRef<HTMLDivElement>(null)\n const tagHeight = size === 'sm' ? 20 : 24\n\n const selectDropdown = unselected.length > 0 ? (\n <select ref={selectRef} value=\"\" onChange={(e) => handleAdd(e.target.value)}\n className={cn('bg-transparent outline-none border-none p-0 text-[inherit] font-[inherit] leading-[inherit] text-fg-muted cursor-pointer appearance-none',\n value.length > 0 ? 'absolute inset-0 w-full h-full opacity-0 z-0 cursor-pointer' : 'relative z-10 flex-1 min-w-20')}>\n <option value=\"\" disabled>{placeholder ?? '選擇...'}</option>\n {unselected.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}\n </select>\n ) : null\n\n return (\n <div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative',\n wrap && 'items-start py-1', error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}\n style={{ paddingRight: '0.75rem', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode=\"edit\" data-error={error ? '' : undefined}\n onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>\n {/* 2026-05-18 F2 sync(per user verbatim「modifying 修好 PeoplePicker stack 後改壞 Combobox tag display」\n + 「tag 應該要判斷所在空間最多可以呈現幾個tag(包括+n)去自動判斷何時要變成+n」):\n edit path tagArea 對齊 display path L293 已 ship 的 `overflow-hidden` fix。原 `overflow-visible`\n 讓 tag 視覺越界蓋 chevron / +N indicator(useOverflowCount measurement 對但 CSS overflow 仍露)。\n M10 violation root cause:2026-05-15 F1 Q3 只 fix display path,edit + Native(L518)沒同步。 */}\n <div ref={tagAreaRef} className={cn('flex-1 min-w-0 flex items-center relative', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: GAP }}\n onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>\n <OverflowTagList containerRef={tagAreaRef} items={items} size={size} wrap={wrap}\n renderTag={(item) => (\n <Tag size={size} className=\"shrink-0 relative z-10\" onClick={() => { selectRef.current?.showPicker?.(); selectRef.current?.focus() }}\n onRemove={() => handleRemove(item.value)}>{item.label}</Tag>\n )} onRemove={handleRemove} trailing={value.length === 0 ? selectDropdown : undefined} />\n </div>\n {value.length > 0 && selectDropdown}\n <ItemSuffix className={cn('relative z-10 pointer-events-none', wrap && 'self-start')}\n style={wrap ? { height: tagHeight } : undefined}>\n {showClear && (\n <span className=\"pointer-events-auto\">\n <ItemInlineAction\n size={size ?? 'md'}\n action={{ icon: X, label: '清除全部', onClick: () => onChange?.([]) }} // i18n-allow: DS default inline-action label\n />\n </span>\n )}\n <ChevronDown size={iconSize} className=\"shrink-0 text-fg-muted pointer-events-none\" aria-hidden />\n </ItemSuffix>\n </div>\n )\n}\n\n// ── Custom Combobox (desktop — consumes SelectMenu) ───────────────────\n\nfunction CustomCombobox({\n mode, variant: variantProp, error: errorProp = false, size = 'md', options, value = [], onChange, placeholder,\n className, disabled: disabledProp, wrap = false, clearable = false, searchable = false, loading, searchIn = 'menu',\n searchPlaceholder = '搜尋…', // i18n-allow: DS default\n searchAriaLabel = '搜尋選項', // i18n-allow: DS default\n emptyPlaceholder = '選擇…', // i18n-allow: DS default\n defaultOpen = false,\n onOpenChange,\n __triggerRef,\n tagRenderer,\n renderHiddenTag,\n tagWrapperClassName,\n overflowWrapperClassName,\n tagAreaGapPx,\n visibleCountOverride,\n tagAreaPaddingLeftPx,\n overflowShape,\n showDisplayEndIcon = false,\n 'aria-label': ariaLabel,\n}: ComboboxInternalProps) {\n const tagAreaGap = tagAreaGapPx ?? GAP\n const fieldCtx = useFieldContext()\n const error = useResolvedFieldInvalid(errorProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode;修 <Field mode=\"display\"> 漏 cascade\n const resolvedMode = useResolvedFieldMode({ mode, disabled })\n const variant: FieldVariant = useResolvedFieldVariant(variantProp)\n const iconSize = getIconSize(size)\n const showClear = clearable && value.length > 0 && resolvedMode === 'edit'\n const [open, setOpen] = React.useState(defaultOpen)\n const [search, setSearch] = React.useState('')\n // 2026-05-12 Q3 fix:trigger 內 inline 搜尋 input ref,onOpenAutoFocus 時 explicit focus\n // 讓 user 看到 cursor 知道可 inline search(跟 Select inputRef SSOT 同模式)。\n const inputRef = React.useRef<HTMLInputElement>(null)\n // a11y: 為 listbox 容器(SelectMenu 內 PopoverContent)建立穩定 id,讓 trigger 的\n // aria-controls 能指向它(WAI-ARIA combobox pattern 要求)。React.useId 在 SSR/CSR 都穩定。\n const listboxId = React.useId()\n\n React.useEffect(() => { if (!open) setSearch('') }, [open])\n\n if (resolvedMode !== 'edit') {\n return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />\n }\n\n const items = React.useMemo(\n () => value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v })),\n [value, options]\n )\n const tagAreaRef = React.useRef<HTMLDivElement>(null)\n const tagHeight = size === 'sm' ? 20 : 24\n\n const handleRemove = (v: string) => onChange?.(value.filter(x => x !== v))\n\n // searchIn='trigger' 時由 trigger input 過濾,不走 SelectMenu 內建搜尋\n const filteredOptions = React.useMemo(\n () => (searchable && searchIn === 'trigger' && search\n ? options.filter(o => o.label.toLowerCase().includes(search.toLowerCase()))\n : options),\n [searchable, searchIn, search, options]\n )\n\n // 轉換 ComboboxOption → SelectMenuOption\n // 2026-05-10 post-Issue-4 follow-up:forward 全 SelectMenuOption surface(avatar / description /\n // disabled / icon / group)— 修先前 PeoplePicker multi-mode dropdown 漏 avatar drift bug。\n const menuOptions: SelectMenuOption[] = React.useMemo(\n () => filteredOptions.map(opt => ({\n value: opt.value,\n label: opt.label,\n icon: opt.icon,\n avatar: opt.avatar,\n description: opt.description,\n disabled: opt.disabled,\n group: opt.group,\n })),\n [filteredOptions]\n )\n\n const chevronEl = <ChevronDown size={iconSize} className={cn('shrink-0 text-fg-muted transition-transform', open && 'rotate-180')} aria-hidden />\n\n const trigger = (\n <div\n ref={__triggerRef}\n id={fieldCtx?.id}\n role=\"combobox\" aria-expanded={open} aria-haspopup=\"listbox\" aria-controls={listboxId} tabIndex={0}\n aria-label={ariaLabel}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={fieldCtx?.descriptionId}\n aria-errormessage={error ? fieldCtx?.errorId : undefined}\n className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative cursor-pointer',\n wrap && 'items-start py-1',\n // 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default\n // 統一處理 — open=灰深(data-state)/ focus=藍(focus-within !important)。改一處全 control 跟動。\n error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}\n style={{ paddingRight: '0.75rem', ...(wrap ? { height: 'auto' } : undefined) }}\n data-field-mode=\"edit\" data-error={error ? '' : undefined}\n // WAI-ARIA APG combobox 鍵盤開啟語意 — 對齊 sibling Select(select.tsx:593-598)。\n // <div role=combobox> 不像 native <button> 自動 synthesize Enter/Space click,故顯式補:\n // Enter/Space → 開(searchable 時不攔,讓 inline search input 自行處理打字);\n // Escape → 關;ArrowDown → 開(APG required combobox 展開鍵,僅在 closed 時攔,\n // open 後不 preventDefault 讓方向鍵自由流向選單導覽)。\n // 純 additive:此 trigger 原無 onKeyDown,不覆寫既有 handler;open 邏輯仍走 setOpen SSOT。\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n // 2026-06-11 P0 a11y(R2,同 select.tsx 修):Combobox 恆 searchable → 原 `!searchable`\n // guard 使 Enter/Space 永遠開不了(僅 ArrowDown 可)。正確 guard = !open。\n if (!open) { e.preventDefault(); setOpen(true) }\n }\n if (e.key === 'ArrowDown' && !open) { e.preventDefault(); setOpen(true) }\n if (e.key === 'Escape') setOpen(false)\n }}>\n {/* 2026-05-18 #6A Round 1 Step 2/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:648):\n CustomCombobox edit non-wrap tagArea 對齊 L293 display + L451 readonly + L518 native edit 已 ship 的 overflow-hidden fix。\n 原 overflow-visible 讓 tag 越界蓋 chevron / +N indicator(user 圖三)。M10 propagation 完整 4-path align。 */}\n <div ref={tagAreaRef} className={cn('flex-1 min-w-0 flex items-center relative', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: tagAreaGap, paddingLeft: tagAreaPaddingLeftPx }}>\n {value.length > 0 ? (\n <OverflowTagList containerRef={tagAreaRef} items={items} size={size} wrap={wrap}\n tagWrapperClassName={tagWrapperClassName}\n overflowWrapperClassName={overflowWrapperClassName}\n gap={tagAreaGap}\n overflowShape={overflowShape}\n visibleCountOverride={visibleCountOverride}\n renderTag={(item) => (\n tagRenderer\n ? tagRenderer(item, () => handleRemove(item.value))\n : <Tag size={size} className=\"shrink-0 relative z-10\"\n onRemove={() => handleRemove(item.value)}>{item.label}</Tag>\n )}\n renderHiddenTag={renderHiddenTag}\n onRemove={handleRemove}\n trailing={searchable && searchIn === 'trigger' ? (\n <input ref={inputRef} value={search} onChange={(e) => setSearch(e.target.value)}\n // 2026-05-15 Drift A fix(per user verbatim SSOT clarification「未選 → placeholder 顯示請選擇之類」):\n // items.length === 0(empty selection)→ 用 `placeholder` trigger empty prop(「請選擇…」),\n // **不**用 `searchPlaceholder`(「搜尋…」);後者僅在 panel-top search input 場景才合理。\n // items.length > 0(已選)→ no placeholder,純 cursor(對齊 Combobox empty cursor SSOT)。\n // SSOT 對齊 select.tsx:185 `placeholder={selectedLabel || placeholder || '搜尋…'}`\n // empty-state fallback to trigger placeholder canonical。\n placeholder={items.length === 0 ? placeholder : ''} onClick={(e) => { e.stopPropagation(); setOpen(true) }}\n aria-label={searchAriaLabel}\n className=\"flex-1 min-w-[60px] bg-transparent outline-none text-body leading-compact relative z-10\" />\n ) : undefined} />\n ) : (\n /* 2026-05-12 Stream C Issue 3 fix(codex Q3 Cluster C):placeholder span 必 flex-1 min-w-0\n truncate,narrow container 時單行省略(對齊 Combobox text-tag truncate canonical)。\n 原 hardcode wraps in narrow trigger → user 抓「placeholder 文字 wrap multi-line」。 */\n <span className=\"flex-1 min-w-0 truncate text-fg-muted\">{placeholder ?? emptyPlaceholder}</span>\n )}\n </div>\n <ItemSuffix className={cn('relative z-10 pointer-events-none', wrap && 'self-start')}\n style={wrap ? { height: tagHeight } : undefined}>\n {showClear && (\n <span className=\"pointer-events-auto\">\n <ItemInlineAction\n size={size ?? 'md'}\n action={{\n icon: X,\n label: '清除全部', // i18n-allow: DS default inline-action label\n onClick: (e) => { e?.stopPropagation(); onChange?.([]) },\n }}\n />\n </span>\n )}\n {chevronEl}\n </ItemSuffix>\n </div>\n )\n\n return (\n <SelectMenu\n loading={loading}\n options={menuOptions}\n value={value}\n onValueChange={onChange as (value: string | string[]) => void}\n multiple\n searchable={searchable && searchIn === 'menu'}\n searchPlaceholder={searchPlaceholder}\n size={size}\n open={open}\n onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}\n // 2026-05-12 Q3 fix(user 抓「inline-searchable 開浮層應出現 cursor」)— 跟 Select line 550\n // 同 SSOT pattern:preventDefault Radix default focus + 顯式 focus inline input → 開時\n // cursor 直接 visible,user 知道可 inline search。\n onOpenAutoFocus={searchIn === 'trigger' ? (e) => { e.preventDefault(); inputRef.current?.focus() } : undefined}\n contentId={listboxId}\n >\n {trigger}\n </SelectMenu>\n )\n}\n\n// ── Public component ────────────────────────────────────────────────────────\n\nconst Combobox = React.forwardRef<HTMLDivElement, ComboboxProps>(\n ({ size: sizeProp, ...props }, ref) => {\n // B 組 cascade fix:public wrapper 一處 resolve size(prop>Field>surface>md)後 forward 給內層,\n // 讓 <Field size> / DataTable cell surface-size 對 Combobox 生效(內層默認 'md' 被此顯式 size 覆蓋)。\n const size = useResolvedFieldSize(sizeProp)\n // 2026-05-16 真 root cause fix:之前用 `_ref` drop ref。修為 forward 給 internal\n // `__triggerRef`,讓 PeoplePicker stack 透過 ref 量 trigger DOM(visibleCountOverride\n // 才生效)。對齊 React forwardRef public-API canonical(MUI Autocomplete / Radix\n // Popover.Trigger 共識)+ codex M31 Step 5 比稿 verdict + DS-wide ref-drop iceberg audit。\n const isMobile = useIsTouchDevice()\n if (isMobile) return <NativeCombobox {...props} size={size} __triggerRef={ref} />\n return <CustomCombobox {...props} size={size} __triggerRef={ref} />\n }\n)\nCombobox.displayName = 'Combobox'\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 comboboxMeta = {\n component: 'Combobox',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-transparent'],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { Combobox }\n"],"names":[],"mappings":";;;;;;;;;;;AAkBA,MAAM,MAAM;AAEZ,MAAM,aAAqC;AAAA,EACzC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AA0BA,SAAS,iBACP,cACA,QACA,YACA,YACA,SACA,MAAc,KACd,sBAC0C;AAC1C,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,EAAE,cAAc,YAAY,OAAO,CAAC,QAAA,CAAS;AAQtF,QAAM,mBAAmB,MAAM,OAAe,EAAE;AAuBhD,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,WAAW,eAAe,GAAG;AAAE,eAAS,EAAE,cAAc,YAAY,OAAO,MAAM;AAAG;AAAA,IAAO;AAChG,QAAI,yBAAyB,QAAW;AACtC,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAC9C,cAAM,KAAK,OAAO,QAAQ,CAAC;AAC3B,YAAI,GAAI,IAAG,SAAS,KAAK;AAAA,MAC3B;AACA,YAAM,OAAO,WAAW;AACxB,UAAI,KAAM,MAAK,SAAS,wBAAwB;AAChD,eAAS,EAAE,cAAc,sBAAsB,OAAO,MAAM;AAAG;AAAA,IACjE;AAQA,QAAI,eAAe,GAAG;AACpB,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAC9C,cAAM,KAAK,OAAO,QAAQ,CAAC;AAC3B,YAAI,GAAI,IAAG,SAAS,KAAK;AAAA,MAC3B;AACA,YAAM,OAAO,WAAW;AACxB,UAAI,WAAW,SAAS;AACxB,eAAS,EAAE,cAAc,GAAG,OAAO,MAAM;AAAG;AAAA,IAC9C;AACA,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,UAAM,OAAO,MAAM;AACjB,YAAM,KAAK,iBAAiB,SAAS;AACrC,YAAM,YAAY,UAAU,eAAe,WAAW,GAAG,WAAW,KAAK,MAAM,WAAW,GAAG,YAAY,KAAK;AAI9G,gBAAU,MAAM,YAAY,mCAAmC,GAAG,SAAS,IAAI;AAC/E,iBAAW,MAAM,OAAO,QAAS,KAAI,OAAO,SAAS;AACrD,YAAM,OAAO,WAAW;AACxB,UAAI,WAAW,SAAS;AAIxB,YAAM,qBAAoB,6BAAM,gBAAe;AAC/C,UAAI,oBAAoB,EAAG,kBAAiB,UAAU;AACtD,YAAM,YAAY,iBAAiB;AAMnC,UAAI,OAAO,GAAG,QAAQ;AACtB,eAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,cAAM,KAAK,OAAO,QAAQ,CAAC;AAC3B,YAAI,CAAC,GAAI;AACT,cAAM,UAAU,GAAG,cAAc,iBAAiB;AAClD,cAAM,IAAI,UAAU,QAAQ,sBAAA,EAAwB,QAAQ,GAAG;AAC/D,cAAM,OAAO,QAAQ,QAAQ,IAAI,MAAM,KAAK;AAC5C,cAAM,YAAY,aAAa,QAAQ;AAEvC,YAAI,YAAY,KAAK,OAAO,MAAM,YAAY,UAAW;AACzD,YAAI,cAAc,KAAK,OAAO,UAAW;AACzC,eAAO;AAAM;AAAA,MACf;AACA,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAAE,cAAM,KAAK,OAAO,QAAQ,CAAC;AAAG,YAAI,GAAI,IAAG,SAAS,KAAK;AAAA,MAAM;AAC/G,UAAI,KAAM,MAAK,SAAS,SAAS;AAGjC,YAAM,gBAAgB,UAAU,sBAAA;AAChC,aAAO,QAAQ,GAAG;AAChB,cAAM,SAAS,OAAO,QAAQ,QAAQ,CAAC;AACvC,cAAM,UAAU,iCAAQ,cAAc;AACtC,YAAI,CAAC,QAAS;AACd,cAAM,UAAU,QAAQ,sBAAA;AACxB,YAAI,QAAQ,SAAS,cAAc,QAAQ,IAAK;AAChD;AACA,YAAI,eAAe,SAAS;AAAA,MAC9B;AACA,UAAI,KAAM,MAAK,SAAS,SAAS;AAIjC,eAAS,CAAA,SAAS,KAAK,iBAAiB,SAAS,KAAK,QAAS,OAAO,EAAE,cAAc,OAAO,OAAO,KAAA,CAAM;AAAA,IAC5G;AAgBA,SAAA;AACA,QAAI,SAAS,GAAG,SAAS;AACzB,UAAM,eAAe,MAAM;AACzB,UAAI,QAAQ;AAAE,6BAAqB,MAAM;AAAG,iBAAS;AAAA,MAAE;AACvD,UAAI,QAAQ;AAAE,6BAAqB,MAAM;AAAG,iBAAS;AAAA,MAAE;AACvD,eAAS,sBAAsB,MAAM;AACnC,iBAAS;AACT,iBAAS,sBAAsB,MAAM;AACnC,mBAAS;AACT,eAAA;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AACA,iBAAA;AACA,UAAM,eAAe,IAAI,eAAe,YAAY;AACpD,iBAAa,QAAQ,SAAS;AAC9B,UAAM,UAAU,IAAI,eAAe,YAAY;AAC/C,eAAW,MAAM,OAAO,SAAS;AAC/B,UAAI,GAAI,SAAQ,QAAQ,EAAE;AAAA,IAC5B;AACA,WAAO,MAAM;AACX,UAAI,6BAA6B,MAAM;AACvC,UAAI,6BAA6B,MAAM;AACvC,mBAAa,WAAA;AACb,cAAQ,WAAA;AAAA,IACV;AAAA,EACF,GAAG,CAAC,cAAc,YAAY,SAAS,KAAK,oBAAoB,CAAC;AAEjE,SAAO;AACT;AA0DA,SAAS,gBAAgB,EAAE,cAAc,OAAO,MAAM,MAAM,WAAW,iBAAiB,UAAU,UAAU,qBAAqB,0BAA0B,MAAM,KAAK,gBAAgB,OAAO,wBAA8C;AACzO,QAAM,SAAS,MAAM,OAAkC,EAAE;AACzD,QAAM,aAAa,MAAM,OAAuB,IAAI;AACpD,QAAM,EAAE,cAAc,UAAU,iBAAiB,cAAc,QAAQ,YAAY,MAAM,QAAQ,CAAC,MAAM,KAAK,oBAAoB;AACjI,SAAO,QAAQ,SAAS,MAAM;AAE9B,MAAI,aAAa,qBAAA,UAAA,EAAG,UAAA;AAAA,IAAA,MAAM,IAAI,CAAC,MAAM,MAAM,UAAU,MAAM,CAAC,CAAC;AAAA,IAAG;AAAA,EAAA,GAAS;AAEzE,QAAM,WAAW,MAAM,SAAS;AAChC,QAAM,cAAc,MAAM,MAAM,YAAY;AAQ5C,SACE,qBAAC,QAAA,EAAK,WAAU,YACb,UAAA;AAAA,IAAA,MAAM,IAAI,CAAC,MAAM;AAAA;AAAA;AAAA;AAAA,MAIhB,oBAAC,OAAA,EAAqB,KAAK,CAAA,OAAM;AAAE,eAAO,QAAQ,CAAC,IAAI;AAAA,MAAG,GAAG,WAAW,GAAG,uBAAuB,mBAAmB,GAAG,OAAO,EAAE,QAAQ,MAAM,SAAS,KAAM,UAAA,UAAU,MAAM,CAAC,EAAA,GAArK,KAAK,KAAkK;AAAA,KAClL;AAAA,IACD,oBAAC,SAAI,KAAK,YAAY,WAAW,GAAG,YAAY,wBAAwB,GACtE,UAAA,oBAAC,mBAAA,EAAkB,OAAO,UAAU,OAAO,eAAe,MACvD,UAAA,YAAY,IAAI,CAAA,SACf,kBACI,oBAAC,MAAM,UAAN,EAAiC,0BAAgB,IAAI,EAAA,GAAjC,KAAK,KAA8B,wBACvD,KAAA,EAAqB,MAAK,MAAK,UAAU,WAAW,MAAM,SAAS,KAAK,KAAK,IAAI,QAC/E,UAAA,KAAK,SADE,KAAK,KAEf,CACL,EAAA,CACH,EAAA,CACF;AAAA,IACC;AAAA,EAAA,GACH;AAEJ;AAMA,SAAS,iBAAiB;AAAA,EACxB;AAAA,EAAO;AAAA,EAAS,UAAU;AAAA,EAAM,OAAO;AAAA,EAAO,cAAc;AAAA,EAAa,WAAW;AACtF,GAGG;AACD,QAAM,SAAS,MAAM,OAAuB,IAAI;AAChD,MAAI,CAAC,SAAS,MAAM,WAAW,UAAU,oBAAC,QAAA,EAAK,WAAU,iBAAiB,UAAA,cAAA,CAAc;AACxF,QAAM,QAAQ,MAAM,IAAI,CAAA,MAAA;;AAAM,aAAE,OAAO,GAAG,SAAO,wCAAS,KAAK,OAAK,EAAE,UAAU,OAA/B,mBAAmC,UAAS;GAAI;AACjG,QAAM,gBAAgB,WAAW,iCAAiC;AAElE,QAAM,UACJ;AAAA,IAAC;AAAA,IAAA;AAAA,MAAgB,cAAc,eAAe;AAAA,MAAQ;AAAA,MAAc,MAAM;AAAA,MAAS;AAAA,MAQjF,WAAW,CAAC,SAAS,oBAAC,KAAA,EAAI,MAAM,SAAS,WAAW,GAAG,YAAY,aAAa,GAAI,eAAK,MAAA,CAAM;AAAA,IAAA;AAAA,EAAA;AAGnG,MAAI,YAAa,QAAO;AAUxB,6BACG,OAAA,EAAI,KAAK,QAAQ,WAAW,GAAG,oCAAoC,uBAAuB,OAAO,cAAc,iBAAiB,GAAG,OAAO,EAAE,KAAK,IAAA,GAC/I,UAAA,SACH;AAEJ;AAwHA,MAAM,cAAc,CAAC,SAAiB,UAAU,IAA0B;AAI1E,SAAS,oBAAoB;AAAA,EAC3B;AAAA,EAAM,SAAS;AAAA,EAAa;AAAA,EAAM;AAAA,EAAS;AAAA,EAAO;AAAA,EAAM;AAAA,EAAW,qBAAqB;AAC1F,GAAyH;AACvH,QAAM,eAAe,QAAQ;AAC7B,QAAM,UAAU,eAAe;AAC/B,QAAM,KAAK,QAAQ;AACnB,QAAM,WAAW,OAAO,OAAO,KAAK;AACpC,QAAM,eAAe,MAAM,OAAuB,IAAI;AACtD,QAAM,YAAW,+BAAO,WAAU,KAAK;AAMvC,MAAI,iBAAiB,WAAW;AAC9B,QAAI,CAAC,oBAAoB;AAGvB,UAAI,CAAC,QAAS,QAAO,oBAAC,QAAA,EAAK,WAAW,GAAG,sBAAsB,EAAE,GAAG,iBAAiB,SAAS,GAAI,UAAA,cAAA,CAAc;AAChH,iCACG,kBAAA,EAAiB,OAAc,SAAkB,SAAS,IAAI,MAAY;AAAA,IAE/E;AACA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,GAAG,mBAAmB,EAAE,MAAM,WAAW,SAAS,MAAM,GAAA,CAAI,GAAG,WAAW,WAAW,EAAE,GAAG,SAAS;AAAA,QAC9G,mBAAgB;AAAA,QAEf,UAAA;AAAA,UAAA,UACC,oBAAC,kBAAA,EAAiB,OAAc,SAAkB,SAAS,IAAI,KAAA,CAAY,IAE3E,oBAAC,UAAK,WAAW,GAAG,kBAAkB,eAAe,GAAI,UAAA,eAAc;AAAA,UAEzE,oBAAC,YAAA,EAAW,WAAU,uBACpB,UAAA,oBAAC,aAAA,EAAY,MAAM,UAAU,WAAU,0BAAyB,eAAW,KAAA,CAAC,EAAA,CAC9E;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAAI,KAAK;AAAA,MACR,WAAW;AAAA,QAAG,mBAAmB,EAAE,MAAM,cAAc,SAAS,MAAM,IAAI;AAAA,QAAG,WAAW,WAAW,EAAE;AAAA;AAAA;AAAA;AAAA,QAInG,OAAO,mBAAmB;AAAA,QAAmB;AAAA,MAAA;AAAA,MAC/C,OAAO,EAAE,KAAK,KAAK,GAAI,OAAO,EAAE,QAAQ,OAAA,IAAW,OAAA;AAAA,MAAc,mBAAiB;AAAA,MAClF,iBAAe,iBAAiB,aAAa,OAAO;AAAA,MACnD,UAAA;AAAA,QAAA,UACC;AAAA,UAAC;AAAA,UAAA;AAAA,YAAiB;AAAA,YAAc;AAAA,YAAkB,SAAS;AAAA,YAAI;AAAA,YAC7D;AAAA,YAA4B,UAAU,iBAAiB;AAAA,UAAA;AAAA,QAAA,IAEzD,oBAAC,QAAA,EAAK,WAAU,iBAAiB,UAAA,eAAc;AAAA,SAG/C,YAAY,UAAU,CAAC,CAAC,qBAAqB,SAC7C,oBAAC,YAAA,EAAW,WAAU,uBACpB,UAAA,oBAAC,aAAA,EAAY,MAAM,UAAU,WAAW,GAAG,YAAY,iBAAiB,aAAa,qBAAqB,eAAe,GAAG,eAAW,KAAA,CAAC,EAAA,CAC1I;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR;AAgBA,SAAS,eAAe;AAAA,EACtB;AAAA,EAAM,SAAS;AAAA,EAAa,QAAQ;AAAA,EAAO,OAAO;AAAA,EAAM;AAAA,EAAS,QAAQ,CAAA;AAAA,EAAI;AAAA,EAAU;AAAA,EACvF;AAAA,EAAW,UAAU;AAAA,EAAc,OAAO;AAAA,EAAO,YAAY;AAAA,EAAO,qBAAqB;AAAA,EACzF;AACF,GAA0B;AACxB,QAAM,WAAW,yBAAyB,YAAY;AACtD,QAAM,UAAwB,wBAAwB,WAAW;AAEjE,QAAM,eAAe,qBAAqB,EAAE,MAAM,UAAU;AAC5D,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,YAAY,aAAa,MAAM,SAAS,KAAK,iBAAiB;AAEpE,QAAM,eAAe,CAAC,MAAc,qCAAW,MAAM,OAAO,CAAA,MAAK,MAAM,CAAC;AACxE,QAAM,YAAY,CAAC,MAAc;AAAE,QAAI,CAAC,MAAM,SAAS,CAAC,wCAAc,CAAC,GAAG,OAAO,CAAC;AAAA,EAAG;AAErF,MAAI,iBAAiB,QAAQ;AAC3B,WAAO,oBAAC,qBAAA,EAAoB,MAAM,cAAc,SAAkB,MAAY,SAAkB,OAAc,MAAY,WAAsB,mBAAA,CAAwC;AAAA,EAC1L;AAEA,QAAM,QAAQ,MAAM,IAAI,CAAA,MAAA;;AAAM,aAAE,OAAO,GAAG,SAAO,aAAQ,KAAK,OAAK,EAAE,UAAU,CAAC,MAA/B,mBAAkC,UAAS;GAAI;AAChG,QAAM,aAAa,QAAQ,OAAO,CAAA,MAAK,CAAC,MAAM,SAAS,EAAE,KAAK,CAAC;AAC/D,QAAM,YAAY,MAAM,OAA0B,IAAI;AACtD,QAAM,aAAa,MAAM,OAAuB,IAAI;AACpD,QAAM,YAAY,SAAS,OAAO,KAAK;AAEvC,QAAM,iBAAiB,WAAW,SAAS,IACzC;AAAA,IAAC;AAAA,IAAA;AAAA,MAAO,KAAK;AAAA,MAAW,OAAM;AAAA,MAAG,UAAU,CAAC,MAAM,UAAU,EAAE,OAAO,KAAK;AAAA,MACxE,WAAW;AAAA,QAAG;AAAA,QACZ,MAAM,SAAS,IAAI,gEAAgE;AAAA,MAAA;AAAA,MACrF,UAAA;AAAA,QAAA,oBAAC,YAAO,OAAM,IAAG,UAAQ,MAAE,yBAAe,SAAQ;AAAA,QACjD,WAAW,IAAI,CAAA,QAAO,oBAAC,UAAA,EAAuB,OAAO,IAAI,OAAQ,UAAA,IAAI,MAAA,GAAlC,IAAI,KAAoC,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA,IAErF;AAEJ,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAAI,KAAK;AAAA,MAAc,WAAW;AAAA,QAAG,mBAAmB,EAAE,MAAM,QAAQ,SAAkB,MAAM;AAAA,QAAG,MAAM,SAAS,KAAK,WAAW,IAAI;AAAA,QAAG;AAAA,QACxI,QAAQ;AAAA,QAAoB,SAAS,CAAC,yCAAyC,2DAA2D;AAAA,QAAG;AAAA,MAAA;AAAA,MAC7I,OAAO,EAAE,cAAc,WAAW,GAAI,OAAO,EAAE,QAAQ,OAAA,IAAW,OAAA;AAAA,MAAc,mBAAgB;AAAA,MAAO,cAAY,QAAQ,KAAK;AAAA,MAChI,SAAS,CAAC,MAAM;;AAAE,YAAI,EAAE,WAAW,EAAE,eAAe;AAAE,gCAAU,YAAV,mBAAmB,eAAnB;AAAmC,0BAAU,YAAV,mBAAmB;AAAA,QAAQ;AAAA,MAAE;AAAA,MAMtH,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAI,KAAK;AAAA,YAAY,WAAW,GAAG,6CAA6C,uBAAuB,OAAO,cAAc,iBAAiB;AAAA,YAAG,OAAO,EAAE,KAAK,IAAA;AAAA,YAC7J,SAAS,CAAC,MAAM;;AAAE,kBAAI,EAAE,WAAW,EAAE,eAAe;AAAE,sCAAU,YAAV,mBAAmB,eAAnB;AAAmC,gCAAU,YAAV,mBAAmB;AAAA,cAAQ;AAAA,YAAE;AAAA,YACtH,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBAAgB,cAAc;AAAA,gBAAY;AAAA,gBAAc;AAAA,gBAAY;AAAA,gBACnE,WAAW,CAAC,SACV;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBAAI;AAAA,oBAAY,WAAU;AAAA,oBAAyB,SAAS,MAAM;;AAAE,4CAAU,YAAV,mBAAmB,eAAnB;AAAmC,sCAAU,YAAV,mBAAmB;AAAA,oBAAQ;AAAA,oBACjI,UAAU,MAAM,aAAa,KAAK,KAAK;AAAA,oBAAI,UAAA,KAAK;AAAA,kBAAA;AAAA,gBAAA;AAAA,gBACjD,UAAU;AAAA,gBAAc,UAAU,MAAM,WAAW,IAAI,iBAAiB;AAAA,cAAA;AAAA,YAAA;AAAA,UAAW;AAAA,QAAA;AAAA,QAEzF,MAAM,SAAS,KAAK;AAAA,QACrB;AAAA,UAAC;AAAA,UAAA;AAAA,YAAW,WAAW,GAAG,qCAAqC,QAAQ,YAAY;AAAA,YACjF,OAAO,OAAO,EAAE,QAAQ,cAAc;AAAA,YACrC,UAAA;AAAA,cAAA,aACC,oBAAC,QAAA,EAAK,WAAU,uBACd,UAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,MAAM,QAAQ;AAAA,kBACd,QAAQ,EAAE,MAAM,GAAG,OAAO,QAAQ,SAAS,MAAM,qCAAW,IAAE;AAAA,gBAAE;AAAA,cAAA,GAEpE;AAAA,kCAED,aAAA,EAAY,MAAM,UAAU,WAAU,8CAA6C,eAAW,KAAA,CAAC;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MAClG;AAAA,IAAA;AAAA,EAAA;AAGN;AAIA,SAAS,eAAe;AAAA,EACtB;AAAA,EAAM,SAAS;AAAA,EAAa,OAAO,YAAY;AAAA,EAAO,OAAO;AAAA,EAAM;AAAA,EAAS,QAAQ,CAAA;AAAA,EAAI;AAAA,EAAU;AAAA,EAClG;AAAA,EAAW,UAAU;AAAA,EAAc,OAAO;AAAA,EAAO,YAAY;AAAA,EAAO,aAAa;AAAA,EAAO;AAAA,EAAS,WAAW;AAAA,EAC5G,oBAAoB;AAAA;AAAA,EACpB,kBAAkB;AAAA;AAAA,EAClB,mBAAmB;AAAA;AAAA,EACnB,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,qBAAqB;AAAA,EACrB,cAAc;AAChB,GAA0B;AACxB,QAAM,aAAa,gBAAgB;AACnC,QAAM,WAAW,gBAAA;AACjB,QAAM,QAAQ,wBAAwB,SAAS;AAC/C,QAAM,WAAW,yBAAyB,YAAY;AAEtD,QAAM,eAAe,qBAAqB,EAAE,MAAM,UAAU;AAC5D,QAAM,UAAwB,wBAAwB,WAAW;AACjE,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,YAAY,aAAa,MAAM,SAAS,KAAK,iBAAiB;AACpE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,WAAW;AAClD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAG7C,QAAM,WAAW,MAAM,OAAyB,IAAI;AAGpD,QAAM,YAAY,MAAM,MAAA;AAExB,QAAM,UAAU,MAAM;AAAE,QAAI,CAAC,KAAM,WAAU,EAAE;AAAA,EAAE,GAAG,CAAC,IAAI,CAAC;AAE1D,MAAI,iBAAiB,QAAQ;AAC3B,WAAO,oBAAC,qBAAA,EAAoB,MAAM,cAAc,SAAkB,MAAY,SAAkB,OAAc,MAAY,WAAsB,mBAAA,CAAwC;AAAA,EAC1L;AAEA,QAAM,QAAQ,MAAM;AAAA,IAClB,MAAM,MAAM,IAAI,CAAA,MAAA;;AAAM,eAAE,OAAO,GAAG,SAAO,aAAQ,KAAK,OAAK,EAAE,UAAU,CAAC,MAA/B,mBAAkC,UAAS;KAAI;AAAA,IACxF,CAAC,OAAO,OAAO;AAAA,EAAA;AAEjB,QAAM,aAAa,MAAM,OAAuB,IAAI;AACpD,QAAM,YAAY,SAAS,OAAO,KAAK;AAEvC,QAAM,eAAe,CAAC,MAAc,qCAAW,MAAM,OAAO,CAAA,MAAK,MAAM,CAAC;AAGxE,QAAM,kBAAkB,MAAM;AAAA,IAC5B,MAAO,cAAc,aAAa,aAAa,SAC3C,QAAQ,OAAO,CAAA,MAAK,EAAE,MAAM,cAAc,SAAS,OAAO,YAAA,CAAa,CAAC,IACxE;AAAA,IACJ,CAAC,YAAY,UAAU,QAAQ,OAAO;AAAA,EAAA;AAMxC,QAAM,cAAkC,MAAM;AAAA,IAC5C,MAAM,gBAAgB,IAAI,CAAA,SAAQ;AAAA,MAChC,OAAO,IAAI;AAAA,MACX,OAAO,IAAI;AAAA,MACX,MAAM,IAAI;AAAA,MACV,QAAQ,IAAI;AAAA,MACZ,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,OAAO,IAAI;AAAA,IAAA,EACX;AAAA,IACF,CAAC,eAAe;AAAA,EAAA;AAGlB,QAAM,YAAY,oBAAC,aAAA,EAAY,MAAM,UAAU,WAAW,GAAG,+CAA+C,QAAQ,YAAY,GAAG,eAAW,KAAA,CAAC;AAE/I,QAAM,UACJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL,IAAI,qCAAU;AAAA,MACd,MAAK;AAAA,MAAW,iBAAe;AAAA,MAAM,iBAAc;AAAA,MAAU,iBAAe;AAAA,MAAW,UAAU;AAAA,MACjG,cAAY;AAAA,MACZ,gBAAc,SAAS;AAAA,MACvB,kBAAe,qCAAU,aAAY;AAAA,MACrC,oBAAkB,qCAAU;AAAA,MAC5B,qBAAmB,QAAQ,qCAAU,UAAU;AAAA,MAC/C,WAAW;AAAA,QAAG,mBAAmB,EAAE,MAAM,QAAQ,SAAkB,MAAM;AAAA,QAAG,MAAM,SAAS,KAAK,WAAW,IAAI;AAAA,QAAG;AAAA,QAChH,QAAQ;AAAA;AAAA;AAAA,QAGR,SAAS,CAAC,yCAAyC,2DAA2D;AAAA,QAAG;AAAA,MAAA;AAAA,MACnH,OAAO,EAAE,cAAc,WAAW,GAAI,OAAO,EAAE,QAAQ,OAAA,IAAW,OAAA;AAAA,MAClE,mBAAgB;AAAA,MAAO,cAAY,QAAQ,KAAK;AAAA,MAOhD,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AAGtC,cAAI,CAAC,MAAM;AAAE,cAAE,eAAA;AAAkB,oBAAQ,IAAI;AAAA,UAAE;AAAA,QACjD;AACA,YAAI,EAAE,QAAQ,eAAe,CAAC,MAAM;AAAE,YAAE,eAAA;AAAkB,kBAAQ,IAAI;AAAA,QAAE;AACxE,YAAI,EAAE,QAAQ,SAAU,SAAQ,KAAK;AAAA,MACvC;AAAA,MAIA,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAI,KAAK,YAAY,WAAW,GAAG,6CAA6C,uBAAuB,OAAO,cAAc,iBAAiB,GAAG,OAAO,EAAE,KAAK,YAAY,aAAa,wBACrL,UAAA,MAAM,SAAS,IACd;AAAA,UAAC;AAAA,UAAA;AAAA,YAAgB,cAAc;AAAA,YAAY;AAAA,YAAc;AAAA,YAAY;AAAA,YACnE;AAAA,YACA;AAAA,YACA,KAAK;AAAA,YACL;AAAA,YACA;AAAA,YACA,WAAW,CAAC,SACV,cACI,YAAY,MAAM,MAAM,aAAa,KAAK,KAAK,CAAC,IAChD;AAAA,cAAC;AAAA,cAAA;AAAA,gBAAI;AAAA,gBAAY,WAAU;AAAA,gBACzB,UAAU,MAAM,aAAa,KAAK,KAAK;AAAA,gBAAI,UAAA,KAAK;AAAA,cAAA;AAAA,YAAA;AAAA,YAExD;AAAA,YACA,UAAU;AAAA,YACV,UAAU,cAAc,aAAa,YACnC;AAAA,cAAC;AAAA,cAAA;AAAA,gBAAM,KAAK;AAAA,gBAAU,OAAO;AAAA,gBAAQ,UAAU,CAAC,MAAM,UAAU,EAAE,OAAO,KAAK;AAAA,gBAO5E,aAAa,MAAM,WAAW,IAAI,cAAc;AAAA,gBAAI,SAAS,CAAC,MAAM;AAAE,oBAAE,gBAAA;AAAmB,0BAAQ,IAAI;AAAA,gBAAE;AAAA,gBACzG,cAAY;AAAA,gBACZ,WAAU;AAAA,cAAA;AAAA,YAAA,IACV;AAAA,UAAA;AAAA,QAAA;AAAA;AAAA;AAAA;AAAA,UAKN,oBAAC,QAAA,EAAK,WAAU,yCAAyC,yBAAe,iBAAA,CAAiB;AAAA,WAE7F;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAW,WAAW,GAAG,qCAAqC,QAAQ,YAAY;AAAA,YACjF,OAAO,OAAO,EAAE,QAAQ,cAAc;AAAA,YACrC,UAAA;AAAA,cAAA,aACC,oBAAC,QAAA,EAAK,WAAU,uBACd,UAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,MAAM,QAAQ;AAAA,kBACd,QAAQ;AAAA,oBACN,MAAM;AAAA,oBACN,OAAO;AAAA;AAAA,oBACP,SAAS,CAAC,MAAM;AAAE,6CAAG;AAAmB,2DAAW,CAAA;AAAA,oBAAI;AAAA,kBAAA;AAAA,gBACzD;AAAA,cAAA,GAEJ;AAAA,cAED;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MACH;AAAA,IAAA;AAAA,EAAA;AAIJ,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,eAAe;AAAA,MACf,UAAQ;AAAA,MACR,YAAY,cAAc,aAAa;AAAA,MACvC;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,CAAC,MAAM;AAAE,gBAAQ,CAAC;AAAG,qDAAe;AAAA,MAAG;AAAA,MAIrD,iBAAiB,aAAa,YAAY,CAAC,MAAM;;AAAE,UAAE,eAAA;AAAkB,uBAAS,YAAT,mBAAkB;AAAA,MAAQ,IAAI;AAAA,MACrG,WAAW;AAAA,MAEV,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;AAIA,MAAM,WAAW,MAAM;AAAA,EACrB,CAAC,EAAE,MAAM,UAAU,GAAG,MAAA,GAAS,QAAQ;AAGrC,UAAM,OAAO,qBAAqB,QAAQ;AAK1C,UAAM,WAAW,iBAAA;AACjB,QAAI,iBAAiB,oBAAC,gBAAA,EAAgB,GAAG,OAAO,MAAY,cAAc,KAAK;AAC/E,+BAAQ,gBAAA,EAAgB,GAAG,OAAO,MAAY,cAAc,KAAK;AAAA,EACnE;AACF;AACA,SAAS,cAAc;AAIhB,MAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU,CAAA;AAAA,EAGV,OAAO,CAAA;AAAA,EAGP,QAAQ,CAAC,WAAW,SAAS,UAAU,iBAAiB,UAAU;AAAA,EAClE,QAAQ;AAAA,IACN,IAAI,CAAC,eAAe,gBAAgB;AAAA,IACpC,IAAI,CAAC,oBAAoB,eAAe;AAAA,IACxC,MAAM,CAAA;AAAA,EAAC;AAEX;"}
|
|
1
|
+
{"version":3,"file":"combobox.js","sources":["../../../src/components/Combobox/combobox.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// @renderer-symmetry-allow: ComboboxTagStack(display path)接 consumer tagRenderer 是 Stream C 下 cycle 工作 — 2026-05-12 先 ship Issues 2/3/4 surgical fixes(placeholder vocabulary + cell surface metrics + placeholder truncate),tagRenderer display-path unify deferred per field-controls.spec.md 共享 contract a。當前 multi=1 顯示已透過 PeoplePicker tagRenderer 線 314 PersonDisplay SSOT 對齊;其他 Combobox consumer 走 default `<Tag>` 純文字 backward-compat。\n// code-quality-allow: file-size — Combobox 含 NativeCombobox/CustomCombobox/useOverflowCount/OverflowTagList/ComboboxTagStack 5 子元件 + 共用 helpers,split-into-files 會破壞 measurement closures + 重複 type definitions。\nimport * as React from 'react'\nimport { X, ChevronDown } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, EMPTY_DISPLAY, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { Tag } from '@/design-system/components/Tag/tag'\nimport { ItemInlineAction, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { OverflowIndicator } from '@/design-system/components/OverflowIndicator/overflow-indicator'\nimport { SelectMenu, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'\nimport { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n// ── constants ───────────────────────────────────────────────────────────────\n\nconst GAP = 4\n\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/**\n * Combobox option schema(2026-05-10 post-Issue-4 audit unify):**explicit extends\n * SelectMenuOption(primitive SSOT)** — 避免重蹈先前 PeoplePicker 改壞的 wrapper schema drift。\n *\n * Why `extends SelectMenuOption`(per user 「全盤檢查避免下次又改壞或是偏移」要求):\n * 原 `interface ComboboxOption { value: string; label: string }` 是 weak schema,跟 Select 的\n * `ComboboxOption`(同名)雙重宣告但欄位不同 → TypeScript 不抓(同名 interface 在不同 file\n * 各 export,consumer import 到哪個版本看 import path)→ schema drift。\n * PeoplePicker multi-mode 走 Combobox 路徑,dropdown menu rows lose avatar / description —\n * user 看到「single mode 有 avatar / multi mode 沒 avatar」inconsistency。\n *\n * Fix(post-Issue-4 follow-up):extend SelectMenuOption → 全 primitive surface 自動繼承。\n * Wrapper-only field 都沒有 → empty body interface(future 加 wrapper-only field 加在此處)。\n * `menuOptions` mapping(below)forward 全 SelectMenuOption surface。\n *\n * 對齊 Polaris ChoiceList / Material Autocomplete / Carbon Dropdown 的 wrapper-vs-primitive\n * schema-extension idiom。Hook `check_wrapper_primitive_schema_drift.sh`(M30 機械強制)。\n */\nexport interface ComboboxOption extends SelectMenuOption {\n // (no wrapper-only fields yet — kept for future扩 + same-name SSOT cross-wrapper)\n}\n\n// ── useOverflowCount (unchanged) ────────────────────────────────────────────\n\nfunction useOverflowCount(\n containerRef: React.RefObject<HTMLDivElement | null>,\n tagEls: React.MutableRefObject<(HTMLDivElement | null)[]>,\n overflowEl: React.RefObject<HTMLDivElement | null>,\n totalCount: number,\n enabled: boolean,\n gap: number = GAP, // (2026-05-07 v15.13)stack avatar 模式傳 0\n visibleCountOverride?: number, // 2026-05-15 Bug 3 fix:override DOM measurement(PeoplePicker stack 走 formula primitive)\n): { visibleCount: number; ready: boolean } {\n const [state, setState] = React.useState({ visibleCount: totalCount, ready: !enabled })\n // 2026-05-18 Round 6 fix(per Codex M31 Round 6 H7 verdict + Step 5 共識):\n // `ofEl.offsetWidth` 在 expanded state(visibleCount === totalCount → overflow=0 →\n // `OverflowIndicator` line 92 `return null` → ofEl wrapper empty)= 0,fallback 60;\n // 在 collapsed state(+N rendered)= 真實寬(~28-32px)。同 `available` 在兩 state 給\n // 不同 verdict → 臨界值區 `max(B+g+Q, B+g+O) ≤ available < B+g+F` 振盪。Cache last\n // non-zero measurement → measurement state-independent → oscillation 收斂。\n // 初始 60 沿用舊 fallback(無 measure 史 ok-ish over-estimate)。\n const lastOverflowWRef = React.useRef<number>(60)\n\n // 2026-05-16 RACE FIX(user 抓「逐個 click 滿 6」vs「取消全選→再全選」same length 不同 visible):\n //\n // 原 useEffect + 雙 rAF 沒 capture rAF IDs → cleanup 不 cancel pending rAFs。\n // Path B(length=6→0→6):length=0 時 override=undefined 走 internal calc 排 rAF,\n // 然後 length=6 + override=N → deps change → cleanup 跑(disconnect ResizeObserver 但\n // 不 cancel rAF)→ 新 useEffect 跑 override 寫 el.hidden → 舊 rAF 仍 fire → 跑舊\n // internal calc → 覆寫 el.hidden 用 internal measurement(不一致於 override formula)。\n //\n // Fix:\n // 1. useEffect → useLayoutEffect:tighter timing,measurement 在 paint 前 sync\n // 2. Capture rAF IDs,cancel on cleanup\n // 3. scheduleCalc 函式包裝,cancel in-flight rAF 才排新一輪(避免 ResizeObserver\n // re-fire 堆 rAF)\n //\n // 對齊 2026-05-14 I3 fix comment「user 抓『全選 vs 逐個勾 result 不同』」 — 當時 fix\n // 只加 double-rAF 但漏 cancel,本次補完 race close。\n // 2026-05-18 Round 5 fix(per visual test probe):useLayoutEffect 在 nested component 場景\n // 會在 parent ref attach 前 fire(child layout effect 先於 parent ref attach)→ containerRef.current\n // null → early return → calc never runs → setProperty never called → CSS var unset → tag overflow。\n // 改 useEffect:fires AFTER paint,所有 refs 都 attach。double-rAF guard ensures layout done。\n // Trade-off:可能 1-2 frame flicker,但 functional setState guard + paint target measurement 已 cover。\n React.useEffect(() => {\n if (!enabled || totalCount === 0) { setState({ visibleCount: totalCount, ready: true }); return }\n if (visibleCountOverride !== undefined) {\n for (let i = 0; i < tagEls.current.length; i++) {\n const el = tagEls.current[i]\n if (el) el.hidden = i >= visibleCountOverride\n }\n const ofEl = overflowEl.current\n if (ofEl) ofEl.hidden = visibleCountOverride >= totalCount\n setState({ visibleCount: visibleCountOverride, ready: true }); return\n }\n // totalCount=1 fast path:single-tag case 直接 visible 不跑 measurement loop。\n // (歷史:c90d029 曾移除此 bypass,後復原 — 移除會造成 narrow cell 1-selected 跑 unbounded Tag\n // measurement 後 visibleCount=0 → 顯 +1 indicator 而非 single tag,違反 PeoplePicker length===1\n // 走 PersonDisplay SSOT。)\n // 2026-05-16 Round 5 codex edge case fix:explicit unhide DOM nodes(對齊 override branch)。\n // 原 fast-path 只設 React state 不動 `el.hidden`,如 wrappers 之前 hidden 殘留(從 length>1 降到 1)\n // 可能視覺漏顯。Override branch L78-80 同 contract 對齊。\n if (totalCount === 1) {\n for (let i = 0; i < tagEls.current.length; i++) {\n const el = tagEls.current[i]\n if (el) el.hidden = i >= 1\n }\n const ofEl = overflowEl.current\n if (ofEl) ofEl.hidden = true\n setState({ visibleCount: 1, ready: true }); return\n }\n const container = containerRef.current\n if (!container) return\n\n const calc = () => {\n const cs = getComputedStyle(container)\n const available = container.clientWidth - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0)\n // 2026-05-18 Round 5 fix(per user 拍板「那就開始做」+ Codex M31 Round 5 verdict):\n // inject available 成 CSS var,Tag 用 explicit length 而非 cyclic percentage(避 CSS Sizing 3\n // §5.2.1 cyclic percentage 退化問題)。\n container.style.setProperty('--combobox-tag-area-inline-size', `${available}px`)\n for (const el of tagEls.current) if (el) el.hidden = false\n const ofEl = overflowEl.current\n if (ofEl) ofEl.hidden = false\n // 2026-05-18 Round 6 fix:cache last non-zero ofEl width 破 expanded/collapsed state\n // 量測二態(expanded → ofEl 空 offsetWidth=0 / collapsed → real ~28-32px)。沒 cache\n // 之前同 `available` 在兩 state 給不同 verdict → 永動。詳本 hook 頂部 ref 註解。\n const measuredOverflowW = ofEl?.offsetWidth || 0\n if (measuredOverflowW > 0) lastOverflowWRef.current = measuredOverflowW\n const overflowW = lastOverflowWRef.current\n // **#3 fix(2026-05-04)**:width-check 先於 count++,並處理 i=0 邊界(1 tag 自身就太寬 → 全 hidden 顯 +N)\n // 之前 bug:greedy `count++` 永遠至少 = 1,1-tag-too-wide case 視覺呈半個 tag clipped + +N(錯)\n // 修後:1 tag 太寬時 count = 0,全 N tags 走 +N 顯 indicator\n // 2026-05-18 Round 5:量 paint target `[data-tag-root]` 而非 wrapper(per codex Round 5 verdict)。\n // wrapper basis:auto 自由 grow,offsetWidth ≠ Tag actual paint width。\n let used = 0, count = 0\n for (let i = 0; i < totalCount; i++) {\n const el = tagEls.current[i]\n if (!el) continue\n const tagRoot = el.querySelector('[data-tag-root]') as HTMLElement | null\n const w = tagRoot ? tagRoot.getBoundingClientRect().width : el.offsetWidth\n const next = used + (count > 0 ? gap : 0) + w\n const remaining = totalCount - count - 1\n // width check FIRST(無 `count > 0` 短路):任何超寬都 break,包含 i=0 case\n if (remaining > 0 && next + gap + overflowW > available) break\n if (remaining === 0 && next > available) break\n used = next; count++\n }\n for (let i = 0; i < tagEls.current.length; i++) { const el = tagEls.current[i]; if (el) el.hidden = i >= count }\n if (ofEl) ofEl.hidden = count >= totalCount\n // 2026-05-18 Round 5 last guard(per Codex Round 5 verdict):safety net 防 measurement drift\n // (sub-pixel / rounding)。verify last visible tag rect.right ≤ container right,超出遞減 count。\n const containerRect = container.getBoundingClientRect()\n while (count > 0) {\n const lastEl = tagEls.current[count - 1]\n const tagRoot = lastEl?.querySelector('[data-tag-root]') as HTMLElement | null\n if (!tagRoot) break\n const tagRect = tagRoot.getBoundingClientRect()\n if (tagRect.right <= containerRect.right + 0.5) break\n count--\n if (lastEl) lastEl.hidden = true\n }\n if (ofEl) ofEl.hidden = count >= totalCount\n // 2026-05-18 A' fix functional setState value-equal guard(per Codex Round 3 verdict):\n // sync calc 在 useLayoutEffect 內 + ResizeObserver re-fire 同時跑 → 若每次都 new object setState\n // 觸發 re-render 即使值沒變,可能 cascade。回 prev 不更新 = avoid 抖動。\n setState(prev => (prev.visibleCount === count && prev.ready) ? prev : { visibleCount: count, ready: true })\n }\n\n // 2026-05-14 I3 fix(per codex M31 verdict + user 抓「全選 vs 逐個勾 result 不同」):\n // double-rAF ensures layout 完成 before measurement(原 single rAF 在 batched render\n // 場景 tag 還 0-width)。Plus observe per-item ResizeObserver — 任何 tag width 變動\n // 都 trigger recalc(deterministic regardless of commit order)。\n //\n // 2026-05-16 Race close:capture rAF IDs + cancel on cleanup(原版 race I3 沒 close\n // 完;user 抓 path A 逐個 click vs path B 取消全選再全選 same length 不同 visible)。\n // 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— sync calc in useLayoutEffect:\n // React 18 `useLayoutEffect` 在 DOM commit 後、瀏覽器繪製前同步跑,sync calc 在 paint 前\n // 完成 `el.hidden` 設定 + functional setState guard(value-equal 不更新 = 避免 ResizeObserver\n // 抖動 cascade rerender)。double-rAF 改 fallback only(ResizeObserver / async update path)。\n // 解 user verbatim「tag 過長 / 過多會先全顯再變 +N 閃動」root cause(per codex Round 3 cite\n // `combobox.tsx:248 render 沒設 hidden + L129 calc imperative 寫 DOM`)。\n // 對齊 React docs https://react.dev/reference/react/useLayoutEffect pre-paint guarantee。\n calc()\n let rafId1 = 0, rafId2 = 0\n const scheduleCalc = () => {\n if (rafId1) { cancelAnimationFrame(rafId1); rafId1 = 0 }\n if (rafId2) { cancelAnimationFrame(rafId2); rafId2 = 0 }\n rafId1 = requestAnimationFrame(() => {\n rafId1 = 0\n rafId2 = requestAnimationFrame(() => {\n rafId2 = 0\n calc()\n })\n })\n }\n scheduleCalc()\n const containerObs = new ResizeObserver(scheduleCalc)\n containerObs.observe(container)\n const itemObs = new ResizeObserver(scheduleCalc)\n for (const el of tagEls.current) {\n if (el) itemObs.observe(el)\n }\n return () => {\n if (rafId1) cancelAnimationFrame(rafId1)\n if (rafId2) cancelAnimationFrame(rafId2)\n containerObs.disconnect()\n itemObs.disconnect()\n }\n }, [containerRef, totalCount, enabled, gap, visibleCountOverride]) // 2026-05-15 Bug 3 fix:visibleCountOverride 入 deps,override 改 trigger recalc\n\n return state\n}\n\n// ── OverflowTagList (unchanged) ──────────────────────────────────────────────\n\ntype ComboboxOverflowShape = 'circle' | 'tag'\n\n// 2026-05-16 fix:overflow chip wrapper 必能跟 tag wrapper 套同 overlap class\n// (per user 物理模型「avatar 和 +N 都是同尺寸圓形 + 同 step」)。原 chip wrapper\n// 只有 `shrink-0`,在 stack 模式 -ml-0.5 不 apply → chip 不 overlap → 視覺多 22px\n// 額外空間 → length=4→4 / length=5→2+3 saw bug 物理根因。\n// PeoplePicker stack mode pass `'-ml-0.5 first:ml-0 relative inline-flex'` 對齊。\ninterface OverflowTagListProps {\n containerRef: React.RefObject<HTMLDivElement | null>\n items: { value: string; label: string }[]\n size: 'sm' | 'md' | 'lg'\n wrap: boolean\n renderTag: (item: { value: string; label: string }, index: number) => React.ReactNode\n /**\n * 2026-05-14 I4 fix(per codex M31 verdict + user 抓「display overflow 有 avatar / edit 無」):\n * Optional renderer for hidden items in `+N` overflow popover。Default fallback = `<Tag>{label}</Tag>`\n * (純文字 chip,backward-compat)。Consumer pass 此 prop 讓 hidden items 顯示同 avatar 視覺\n * (對齊 display MultiPersonDisplay overflow popover Tag avatar SSOT)。\n */\n renderHiddenTag?: (item: { value: string; label: string }) => React.ReactNode\n onRemove?: (value: string) => void\n trailing?: React.ReactNode\n /** Tag area gap in px(default 4)。Stack mode 傳 0 讓 negative margin 生效 */\n gap?: number\n /**\n * Optional class merged into each tag's outer measurement wrapper `<div className=\"shrink-0\">`.\n * (2026-05-07 v15.13)為 PeoplePicker stack mode 提供 hook point — 讓 stack avatar 走\n * `-ml-0.5 first:ml-0 relative inline-flex group/avatar` 達成 overlap + dismiss group selector,\n * 同時保留 `useOverflowCount` 量測 wrapper(必要,不可移除)。\n *\n * **Caveat(Q2 known tradeoff)**:`-ml-0.5` 負 margin 不改 each wrapper 的 `offsetWidth` →\n * `useOverflowCount` 累加按完整寬計算 → 視覺實際塞得進的 tag 數 > 量測判定能塞的數 →\n * **+N indicator 偏保守**(視覺還有空間但已顯 `+N`)。當前 v1 接受此 tradeoff;若窄 trigger\n * + 多人場景明顯不對,future 可加 `overlapPx` prop 讓量測補償。\n */\n tagWrapperClassName?: string\n /** 2026-05-16 fix:overflow chip 圓形 wrapper 套此 class(stack 模式套 `-ml-0.5` overlap),\n * 讓 chip 跟 avatar 同 step 物理 — 避免「chip 多 24px 額外空間」造成 saw transition。*/\n overflowWrapperClassName?: string\n /**\n * Overflow indicator(+N)形狀(2026-05-12 Round 7 fix,user 抓 PeoplePicker stack +N 該圓形):\n * - `'tag'`(default,backward-compat)— 矩形 chip(對齊 Combobox 文字 tag)\n * - `'circle'`(opt-in for avatar stack consumers)— 圓形 avatar-shape +N(對齊 GitHub picker idiom)\n * PeoplePicker stack mode pass `'circle'`,Combobox 文字 tag 自走 `'tag'`。\n */\n overflowShape?: ComboboxOverflowShape\n /**\n * 2026-05-15 Bug 3 fix:override visible count via formula-based primitive(PeoplePicker stack\n * 用 `avatar-stack-overflow` primitive deterministic formula 計算 visible,bypass DOM offsetWidth\n * measurement)。對齊 user SSOT「同 cell width 同 overflow 判斷」。\n */\n visibleCountOverride?: number\n}\n\nfunction OverflowTagList({ containerRef, items, size, wrap, renderTag, renderHiddenTag, onRemove, trailing, tagWrapperClassName, overflowWrapperClassName, gap = GAP, overflowShape = 'tag', visibleCountOverride }: OverflowTagListProps) {\n const tagEls = React.useRef<(HTMLDivElement | null)[]>([])\n const overflowEl = React.useRef<HTMLDivElement>(null)\n const { visibleCount, ready } = useOverflowCount(containerRef, tagEls, overflowEl, items.length, !wrap, gap, visibleCountOverride)\n tagEls.current.length = items.length\n\n if (wrap) return <>{items.map((item, i) => renderTag(item, i))}{trailing}</>\n\n const overflow = items.length - visibleCount\n const hiddenItems = items.slice(visibleCount)\n\n // 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— 撤掉舊的 `style={{ opacity: ready ? 1 : 0 }}`\n // gate(掛在 `<span className=\"contents\">` 上,但 CSS Display 3 spec 規定 `display:contents` 元素不產生 box,\n // parent opacity 對 children 無效 → gate 從沒生效是 dead code)。Flicker 已改 sync calc in useLayoutEffect 解\n // (L153 `calc()` 直跑,paint 前 hidden 設好)。`ready` state 保留供未來 instrumentation / debug,不再當 visual gate。\n // 保留 `<span className=\"contents\">` 維持 fragment-like rendering(parent JSX 期望 single child)。\n void ready // intentional: ready 留供 future debug,目前無 consumer\n return (\n <span className=\"contents\">\n {items.map((item, i) => (\n // 2026-05-14 I5 fix(per codex M31 verdict + user 抓「avatar stack 堆疊方向不一致」):\n // 加 z-index per-index — 前 item z 高(對齊 MultiPersonDisplay zIndex: visible.length - i\n // canonical + MUI AvatarGroup surplus pattern)。display + edit stack 堆疊方向統一。\n <div key={item.value} ref={el => { tagEls.current[i] = el }} className={cn('shrink-0 max-w-full', tagWrapperClassName)} style={{ zIndex: items.length - i }}>{renderTag(item, i)}</div>\n ))}\n <div ref={overflowEl} className={cn('shrink-0', overflowWrapperClassName)}>\n <OverflowIndicator count={overflow} shape={overflowShape} size={size}>\n {hiddenItems.map(item => (\n renderHiddenTag\n ? <React.Fragment key={item.value}>{renderHiddenTag(item)}</React.Fragment>\n : <Tag key={item.value} size=\"sm\" onRemove={onRemove ? () => onRemove(item.value) : undefined}>\n {item.label}\n </Tag>\n ))}\n </OverflowIndicator>\n </div>\n {trailing}\n </span>\n )\n}\n\n// ── Internal tag-stack renderer (consumed by ReadonlyMultiSelect / mode='display') ───\n//\n// Phase B2(2026-05-05):原 ComboboxDisplay sub-component 已 retire,改 inline `<Combobox mode=\"display\">`。\n// 本 helper 只負責 tag-stack 內容渲染(OverflowTagList 消費),不包 Field wrapper。\nfunction ComboboxTagStack({\n value, options, tagSize = 'md', wrap = false, containerRef: externalRef, disabled = false,\n}: {\n value?: string[] | null; options?: ComboboxOption[]; tagSize?: 'sm' | 'md' | 'lg'\n wrap?: boolean; containerRef?: React.RefObject<HTMLDivElement | null>; disabled?: boolean\n}) {\n const ownRef = React.useRef<HTMLDivElement>(null)\n if (!value || value.length === 0) return <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n const items = value.map(v => ({ value: v, label: options?.find(o => o.value === v)?.label ?? v }))\n const disabledClass = disabled ? 'bg-disabled text-fg-disabled' : undefined\n\n const content = (\n <OverflowTagList containerRef={externalRef ?? ownRef} items={items} size={tagSize} wrap={wrap}\n // 2026-05-18 7B' fix(per user 拍板「執行」+ Codex Round 3 共識)— 移除 `unbounded`,Tag 回預設\n // `max-w-40` cap(160px)+ 內建 `[data-tag-text] truncate min-w-0` 自帶 ellipsis。原 unbounded 是\n // 「cell-as-input narrow cell < 160px」設計(`tag.tsx:85-90`),但 generic Combobox tag display\n // 應走 Tag canonical cap-with-ellipsis(per `data-table.spec.md:235`「Tag 文字內部 truncate;\n // multiSelect 動態 +N」+ `data-table.spec.md:467`「Tag 不可被外層 overflow-hidden 裁掉邊框」)。\n // Trade-off:長 tag 觸發 +N 提前(160 + gap + overflowW > available 較 quick)— acceptable per user。\n // Round 2 dynamic slot maxWidth 提案經 user + codex 共識撤回(3 chicken-and-egg fatal,KISS 勝)。\n renderTag={(item) => <Tag size={tagSize} className={cn('shrink-0', disabledClass)}>{item.label}</Tag>} />\n )\n\n if (externalRef) return content\n // 2026-05-05 v9 fix(Bug 4):display path 內 wrapper 必須 `flex-1 min-w-0`,否則在 cell flex\n // parent 下不認領完整可用寬度 → OverflowTagList 量得寬度小於 edit path → 顯 `+N` 多於 edit。\n // edit path tagAreaRef wrapper 已是 `flex-1 min-w-0`(NativeCombobox/CustomCombobox line 258 / 354),\n // display 必對稱才 SSOT。\n // 2026-05-15 F1 Q3 fix(per user round 3 verbatim「單人選取時 Tag 越界蓋 indicator」):\n // `overflow-visible` → `overflow-hidden` 讓 narrow cell width 強制 clip(Tag 內建 truncate\n // 處理 text ellipsis,stack `-ml-0.5` 負 margin 在 wrapper 內不受影響)。對齊\n // `data-table.spec.md:233`「禁硬裁無 ellipsis」+ MUI X / Ant Table column.ellipsis 共識。\n // (2026-05-14 nakedCellRowModeAlign 同保留 — autoRowHeight cell first-line align canonical。)\n return (\n <div ref={ownRef} className={cn('flex-1 min-w-0 flex items-center', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: GAP }}>\n {content}\n </div>\n )\n}\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface ComboboxProps {\n mode?: FieldMode\n /** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */\n variant?: FieldVariant\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n options: ComboboxOption[]\n value?: string[]\n onChange?: (value: string[]) => void\n placeholder?: string\n className?: string\n disabled?: boolean\n wrap?: boolean\n clearable?: boolean\n /** 啟用搜尋 */\n searchable?: boolean\n /** Loading state(2026-05-15 audit B fix per user verbatim「dropdown 隨時可開,讀取在 panel 中間 CircularProgress」)。\n * Forward 給 SelectMenu primitive SSOT;dropdown 開啟時取代 options 顯 CircularProgress + loadingText。\n * Trigger 不變(user 隨時可開)。對齊 MUI Autocomplete `loadingText` + Field SSOT + Empty 元件 compose。*/\n loading?: boolean\n /** 搜尋框位置:menu(浮層內,預設)或 trigger(inline input) */\n searchIn?: 'menu' | 'trigger'\n /** 搜尋框 placeholder(未有選項時顯示)。Default: 「搜尋…」 */\n searchPlaceholder?: string\n /** 搜尋框 ARIA label。Default: 「搜尋選項」 */\n searchAriaLabel?: string\n /** Empty-selection placeholder text。Default: 「選擇…」 */\n emptyPlaceholder?: string\n /** a11y:無 Field wrapper 時提供 role='combobox' 的 accessible name(axe aria-input-field-name) */\n 'aria-label'?: string\n /** Initial open state(uncontrolled)— 對齊 Select.defaultOpen / Radix Popover canonical。\n * DataTable cell-as-input 1-step open 用 */\n defaultOpen?: boolean\n /** open state 變更 callback。DataTable cell-as-input 用:open=false → cell exit edit */\n onOpenChange?: (open: boolean) => void\n /**\n * Selected tag pill 客製 render(2026-05-07 v15.5)。\n *\n * 設了 → 每個 selected tag pill 走 consumer 提供的 ReactNode(收 item={value, label}\n * + onRemove,consumer 自己組 onRemove);沒設 → 走預設 `<Tag>` text-only pill。\n *\n * 用例:PeoplePicker(multi)用此 slot 把 selected tag 換成 avatar + name pill,而非\n * 純文字 Tag。對齊 PeoplePicker = Combobox wrapper SSOT。\n */\n tagRenderer?: (item: { value: string; label: string }, onRemove: () => void) => React.ReactNode\n /**\n * 2026-05-14 I4 fix:Optional renderer for hidden items in `+N` overflow popover\n * (對齊 display MultiPersonDisplay overflow popover 含 avatar SSOT)。PeoplePicker stack\n * pass 此 prop 讓 hidden items 顯 avatar + name(同 display path)。Default fallback\n * `<Tag>{label}</Tag>` 純文字 backward-compat。\n */\n renderHiddenTag?: (item: { value: string; label: string }) => React.ReactNode\n /**\n * @internal PeoplePicker stack wrapper 內部協議 — end-user 勿用(measurement-layer hook)。\n *\n * Optional class merged into each tag's outer measurement wrapper (2026-05-07 v15.13)。\n * Stack avatar 模式用此 hook point 達成 sibling-level overlap (`-ml-0.5`) + group selector\n * (`group/avatar`)— 既保留 Combobox 必要 measurement wrapper,又讓 dismiss/overlap 視覺生效。\n */\n tagWrapperClassName?: string\n /** @internal PeoplePicker stack wrapper 內部協議 — end-user 勿用(measurement-layer hook)。\n * 2026-05-16:overflow chip wrapper 套此 class(對齊 tagWrapperClassName)。Stack 模式\n * pass `-ml-0.5 first:ml-0` 讓 chip 跟 avatar 同 overlap step,物理上 chip = 1 個 slot 不\n * 外加 24px。Default undefined = chip 不 overlap(text-tag mode 等)。*/\n overflowWrapperClassName?: string\n /**\n * Tag area gap in px (2026-05-07 v15.13)。預設 4(pill mode 標準 spacing)。\n * Stack avatar 模式傳 0,讓 `tagWrapperClassName` 的 `-ml-0.5` negative margin 生效\n * (CSS `gap` 套在 flex container 上會強制 sibling spacing,蓋過 negative margin)。\n * **Q2 known tradeoff**:0 後 useOverflowCount 仍按 wrapper.offsetWidth 累加(不含 overlap\n * 補償)→ +N 偏保守。當前接受;若需精準可 future 加 `overlapPx` 補償邏輯。\n *\n * @internal PeoplePicker stack wrapper 內部協議 — end-user 勿用(measurement-layer hook)。\n */\n tagAreaGapPx?: number\n /**\n * tagAreaRef container 左 paddingLeft(px,2026-05-12 加,for PeoplePicker Avatar inset)。\n * Default undefined = no extra padding(Field wrapper `tagPadding[size]` calc 公式自然 inset)。\n * 設值時 tagAreaRef 增 `style.paddingLeft`,**useOverflowCount 的 `available = clientWidth -\n * paddingLeft - paddingRight` 自動 include**(`parseFloat(cs.paddingLeft)` 從 container CSS 抓)\n * → width calc 不漂移,無 side-effect。\n *\n * @internal PeoplePicker stack wrapper 內部協議 — 目前無 active consumer(PeoplePicker 傳 undefined),保留供未來精準 padding;新 consumer 請先評估。\n *\n * **2026-05-13 v2 deprecate path**:原 PeoplePicker pass `{8}` 假設「Combobox tagPadding=4px,4+8=12」\n * 但 `tagPadding[size]` 是 density-dependent calc `(field-height - icon-size) / 2`,只在 md size +\n * default density 才 = 4px;其他 size/density 漂 6/8px → 4+8=12 公式破。改 PeoplePicker 直接 inject\n * `!px-[var(--field-px)]` className 到 Combobox Field wrapper(per people-picker.spec.md:94 v2),`tagAreaPaddingLeftPx`\n * 走 undefined。Future 仍保留此 prop 給其他 consumer 精準調整 padding,但 PeoplePicker 已不再用。\n */\n tagAreaPaddingLeftPx?: number\n /**\n * Overflow indicator (+N) 形狀(2026-05-12 Round 7,opt-in 給 avatar stack consumer):\n * - `'tag'`(default)— 矩形 chip(Combobox 文字 tag default)\n * - `'circle'`(opt-in)— 圓形 avatar-shape(PeoplePicker stack 用)\n */\n overflowShape?: ComboboxOverflowShape\n /**\n * @internal PeoplePicker stack wrapper 內部協議 — end-user 勿用(measurement-layer override)。\n *\n * 2026-05-15 Bug 3 fix:override visible count via formula-based primitive(opt-in;default 走\n * DOM-based `useOverflowCount`)。PeoplePicker stack mode 用 `avatar-stack-overflow` primitive\n * deterministic formula 計算 visible count,forward 給 Combobox bypass DOM offsetWidth\n * measurement,避免 dual-algorithm drift。對齊 user SSOT「同 cell width 同 overflow 判斷」。\n */\n visibleCountOverride?: number\n /**\n * Display 是否渲 ChevronDown + Field naked wrapper(D-path opt-in,2026-05-08)\n * — DataTable cell display↔edit 像素級對齊用。預設 false(裸 tag stack,backward compat)。\n * 設 true 時 display 走 fieldWrapperStyles(naked variant)+ ItemSuffix ChevronDown,\n * 與 edit 同 DOM 結構,消除 Layer-B padding mismatch。\n */\n showDisplayEndIcon?: boolean\n}\n\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 readonly/disabled/display render ─────────────────────────────────\n\nfunction ReadonlyMultiSelect({\n mode, variant: variantProp, size, options, value, wrap, className, showDisplayEndIcon = false,\n}: Pick<ComboboxProps, 'mode' | 'variant' | 'size' | 'options' | 'value' | 'wrap' | 'className' | 'showDisplayEndIcon'>) {\n const resolvedMode = mode ?? 'readonly'\n const variant = variantProp ?? 'default'\n const sz = size ?? 'md'\n const iconSize = sz === 'lg' ? 20 : 16\n const tagHeight = sz === 'sm' ? 20 : 24\n const containerRef = React.useRef<HTMLDivElement>(null)\n const hasTags = (value?.length ?? 0) > 0\n\n // mode='display'(Phase B2 2026-05-05):純內容輸出 — tag stack 不包 Field wrapper / 不 reserve 高度。\n // 對齊原 ComboboxDisplay sub-component(retired)。\n // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper + ItemSuffix ChevronDown,\n // 與 edit 同結構消除 cell display↔edit 像素偏移(Layer-B padding mismatch)。\n if (resolvedMode === 'display') {\n if (!showDisplayEndIcon) {\n // 2026-05-14 I2 fix(spec contract (e) display typography canonical):empty bare span 套\n // `fieldDisplayTextClass(sz)`(sm/md→text-body,lg→text-body-lg)— 對齊跨 Field family 統一。\n if (!hasTags) return <span className={cn(fieldDisplayTextClass(sz), 'text-fg-muted', className)}>{EMPTY_DISPLAY}</span>\n return (\n <ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap} />\n )\n }\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'display', variant, size: sz }), hasTags && tagPadding[sz], className)}\n data-field-mode=\"display\"\n >\n {hasTags ? (\n <ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap} />\n ) : (\n <span className={cn('flex-1 min-w-0', 'text-fg-muted')}>{EMPTY_DISPLAY}</span>\n )}\n <ItemSuffix className=\"pointer-events-none\">\n <ChevronDown size={iconSize} className=\"shrink-0 text-fg-muted\" aria-hidden />\n </ItemSuffix>\n </div>\n )\n }\n\n return (\n <div ref={containerRef}\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), hasTags && tagPadding[sz],\n // 2026-05-18 #6A Round 1 Step 1/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:451):\n // readonly/disabled path 對齊 L293 display wrapper 已 ship 的 overflow-hidden fix。\n // M10 propagation:原 overflow-visible 讓 readonly tag 越界蓋 indicator,跟 display 不對稱。\n // 2026-06-27 對齊 edit path(L598-617):wrap 時 items-start + chevron self-start/tagHeight 鎖第一行;\n // paddingRight: var(--field-px) re-assert 右緣 12px(tagPadding 對稱 calc 會吃掉右緣,跟 edit 一致)。\n wrap ? 'flex-wrap items-start py-1' : 'overflow-hidden', className)}\n style={{ gap: GAP, paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode={resolvedMode}\n aria-disabled={resolvedMode === 'disabled' ? true : undefined}>\n {hasTags ? (\n <ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap}\n containerRef={containerRef} disabled={resolvedMode === 'disabled'} />\n ) : (\n <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n )}\n {/* 2026-06-26 類型身份 indicator:edit 顯示 / readonly 不顯示(純值、不可開下拉) / disabled 保留(fg-disabled,對齊原生 <select disabled>);naked cell 依 showDisplayEndIcon */}\n {(variant === 'naked' ? !!showDisplayEndIcon : resolvedMode === 'disabled') && (\n <ItemSuffix className={cn('pointer-events-none', wrap && 'self-start')} style={wrap ? { height: tagHeight } : undefined}>\n <ChevronDown size={iconSize} className={cn('shrink-0', resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted')} aria-hidden />\n </ItemSuffix>\n )}\n </div>\n )\n}\n\n// ── Native Combobox (mobile) ────────────────────────────────────────\n\n// 2026-05-16 Bug A root cause fix(Claude+Codex M31 Step 5 比稿 consensus,user verbatim\n// 「圖二/圖三 同 180px 不同 length 不同 visible — 跟 user 一開始抓的問題一模一樣」):\n// 公開 `Combobox.forwardRef` 之前用 `(props, _ref)` 把 ref drop,內部 `NativeCombobox` /\n// `CustomCombobox` 從未拿 ref → PeoplePicker `stackContainerRef.current` 永遠 null →\n// `useLayoutEffect` early return → `visibleCountOverride` 永遠 undefined →\n// Combobox 走原 internal `useOverflowCount` 60px chip fallback bug → drift。\n// Fix:internal `__triggerRef` prop(underscore = internal-only)attach root div;\n// 公開 `Combobox.forwardRef` 把 `ref` forward 為 `__triggerRef`。對齊 codex DS-wide iceberg\n// audit:`SelectMenu` / `DateGrid` / `Toast` 的 `_ref` 是 intentional documented(no DOM\n// target);唯本處 actionable drop。\ntype ComboboxInternalProps = ComboboxProps & { __triggerRef?: React.Ref<HTMLDivElement> }\n\nfunction NativeCombobox({\n mode, variant: variantProp, error = false, size = 'md', options, value = [], onChange, placeholder,\n className, disabled: disabledProp, wrap = false, clearable = false, showDisplayEndIcon = false,\n __triggerRef,\n}: ComboboxInternalProps) {\n const disabled = useResolvedFieldDisabled(disabledProp)\n const variant: FieldVariant = useResolvedFieldVariant(variantProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode;修 <Field mode=\"display\"> 漏 cascade\n const resolvedMode = useResolvedFieldMode({ mode, disabled })\n const iconSize = getIconSize(size)\n const showClear = clearable && value.length > 0 && resolvedMode === 'edit'\n\n const handleRemove = (v: string) => onChange?.(value.filter(x => x !== v))\n const handleAdd = (v: string) => { if (!value.includes(v)) onChange?.([...value, v]) }\n\n // React #310 fix(對齊 select.tsx):hooks 必在 conditional early-return 前無條件呼叫。\n // resolvedMode 在 edit↔非edit 切換(<Field mode/disabled> cascade / DataTable cell 進出編輯)時\n // hook 數量不可變動,否則 Rules of Hooks violation → React #310 「rendered fewer/more hooks」crash。\n const selectRef = React.useRef<HTMLSelectElement>(null)\n const tagAreaRef = React.useRef<HTMLDivElement>(null)\n\n if (resolvedMode !== 'edit') {\n return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />\n }\n\n const items = value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v }))\n const unselected = options.filter(o => !value.includes(o.value))\n const tagHeight = size === 'sm' ? 20 : 24\n\n const selectDropdown = unselected.length > 0 ? (\n <select ref={selectRef} value=\"\" onChange={(e) => handleAdd(e.target.value)}\n className={cn('bg-transparent outline-none border-none p-0 text-[inherit] font-[inherit] leading-[inherit] text-fg-muted cursor-pointer appearance-none',\n value.length > 0 ? 'absolute inset-0 w-full h-full opacity-0 z-0 cursor-pointer' : 'relative z-10 flex-1 min-w-20')}>\n <option value=\"\" disabled>{placeholder ?? '選擇...'}</option>\n {unselected.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}\n </select>\n ) : null\n\n return (\n <div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative',\n wrap && 'items-start py-1', error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}\n style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode=\"edit\" data-error={error ? '' : undefined}\n onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>\n {/* 2026-05-18 F2 sync(per user verbatim「modifying 修好 PeoplePicker stack 後改壞 Combobox tag display」\n + 「tag 應該要判斷所在空間最多可以呈現幾個tag(包括+n)去自動判斷何時要變成+n」):\n edit path tagArea 對齊 display path L293 已 ship 的 `overflow-hidden` fix。原 `overflow-visible`\n 讓 tag 視覺越界蓋 chevron / +N indicator(useOverflowCount measurement 對但 CSS overflow 仍露)。\n M10 violation root cause:2026-05-15 F1 Q3 只 fix display path,edit + Native(L518)沒同步。 */}\n <div ref={tagAreaRef} className={cn('flex-1 min-w-0 flex items-center relative', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: GAP }}\n onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>\n <OverflowTagList containerRef={tagAreaRef} items={items} size={size} wrap={wrap}\n renderTag={(item) => (\n <Tag size={size} className=\"shrink-0 relative z-10\" onClick={() => { selectRef.current?.showPicker?.(); selectRef.current?.focus() }}\n onRemove={() => handleRemove(item.value)}>{item.label}</Tag>\n )} onRemove={handleRemove} trailing={value.length === 0 ? selectDropdown : undefined} />\n </div>\n {value.length > 0 && selectDropdown}\n <ItemSuffix className={cn('relative z-10 pointer-events-none', wrap && 'self-start')}\n style={wrap ? { height: tagHeight } : undefined}>\n {showClear && (\n <span className=\"pointer-events-auto\">\n <ItemInlineAction\n size={size ?? 'md'}\n action={{ icon: X, label: '清除全部', onClick: () => onChange?.([]) }} // i18n-allow: DS default inline-action label\n />\n </span>\n )}\n <ChevronDown size={iconSize} className=\"shrink-0 text-fg-muted pointer-events-none\" aria-hidden />\n </ItemSuffix>\n </div>\n )\n}\n\n// ── Custom Combobox (desktop — consumes SelectMenu) ───────────────────\n\nfunction CustomCombobox({\n mode, variant: variantProp, error: errorProp = false, size = 'md', options, value = [], onChange, placeholder,\n className, disabled: disabledProp, wrap = false, clearable = false, searchable = false, loading, searchIn = 'menu',\n searchPlaceholder = '搜尋…', // i18n-allow: DS default\n searchAriaLabel = '搜尋選項', // i18n-allow: DS default\n emptyPlaceholder = '選擇…', // i18n-allow: DS default\n defaultOpen = false,\n onOpenChange,\n __triggerRef,\n tagRenderer,\n renderHiddenTag,\n tagWrapperClassName,\n overflowWrapperClassName,\n tagAreaGapPx,\n visibleCountOverride,\n tagAreaPaddingLeftPx,\n overflowShape,\n showDisplayEndIcon = false,\n 'aria-label': ariaLabel,\n}: ComboboxInternalProps) {\n const tagAreaGap = tagAreaGapPx ?? GAP\n const fieldCtx = useFieldContext()\n const error = useResolvedFieldInvalid(errorProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode;修 <Field mode=\"display\"> 漏 cascade\n const resolvedMode = useResolvedFieldMode({ mode, disabled })\n const variant: FieldVariant = useResolvedFieldVariant(variantProp)\n const iconSize = getIconSize(size)\n const showClear = clearable && value.length > 0 && resolvedMode === 'edit'\n const [open, setOpen] = React.useState(defaultOpen)\n const [search, setSearch] = React.useState('')\n // 2026-05-12 Q3 fix:trigger 內 inline 搜尋 input ref,onOpenAutoFocus 時 explicit focus\n // 讓 user 看到 cursor 知道可 inline search(跟 Select inputRef SSOT 同模式)。\n const inputRef = React.useRef<HTMLInputElement>(null)\n // a11y: 為 listbox 容器(SelectMenu 內 PopoverContent)建立穩定 id,讓 trigger 的\n // aria-controls 能指向它(WAI-ARIA combobox pattern 要求)。React.useId 在 SSR/CSR 都穩定。\n const listboxId = React.useId()\n\n React.useEffect(() => { if (!open) setSearch('') }, [open])\n\n // React #310 fix(對齊 select.tsx):以下 hooks(useMemo/useRef)必在 conditional early-return 前\n // 無條件呼叫。resolvedMode 在 edit↔非edit 切換時 hook 數量不可變動,否則 Rules of Hooks\n // violation → React #310 「rendered fewer/more hooks」crash(ReadonlyMultiSelect 用不到這些值,\n // 多算無害,對齊 select.tsx hoist pattern)。\n const items = React.useMemo(\n () => value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v })),\n [value, options]\n )\n const tagAreaRef = React.useRef<HTMLDivElement>(null)\n const tagHeight = size === 'sm' ? 20 : 24\n\n const handleRemove = (v: string) => onChange?.(value.filter(x => x !== v))\n\n // searchIn='trigger' 時由 trigger input 過濾,不走 SelectMenu 內建搜尋\n const filteredOptions = React.useMemo(\n () => (searchable && searchIn === 'trigger' && search\n ? options.filter(o => o.label.toLowerCase().includes(search.toLowerCase()))\n : options),\n [searchable, searchIn, search, options]\n )\n\n // 轉換 ComboboxOption → SelectMenuOption\n // 2026-05-10 post-Issue-4 follow-up:forward 全 SelectMenuOption surface(avatar / description /\n // disabled / icon / group)— 修先前 PeoplePicker multi-mode dropdown 漏 avatar drift bug。\n const menuOptions: SelectMenuOption[] = React.useMemo(\n () => filteredOptions.map(opt => ({\n value: opt.value,\n label: opt.label,\n icon: opt.icon,\n avatar: opt.avatar,\n description: opt.description,\n disabled: opt.disabled,\n group: opt.group,\n })),\n [filteredOptions]\n )\n\n if (resolvedMode !== 'edit') {\n return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />\n }\n\n const chevronEl = <ChevronDown size={iconSize} className={cn('shrink-0 text-fg-muted transition-transform', open && 'rotate-180')} aria-hidden />\n\n const trigger = (\n <div\n ref={__triggerRef}\n id={fieldCtx?.id}\n role=\"combobox\" aria-expanded={open} aria-haspopup=\"listbox\" aria-controls={listboxId} tabIndex={0}\n aria-label={ariaLabel}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={fieldCtx?.descriptionId}\n aria-errormessage={error ? fieldCtx?.errorId : undefined}\n className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative cursor-pointer',\n wrap && 'items-start py-1',\n // 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default\n // 統一處理 — open=灰深(data-state)/ focus=藍(focus-within !important)。改一處全 control 跟動。\n error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}\n style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }}\n data-field-mode=\"edit\" data-error={error ? '' : undefined}\n // WAI-ARIA APG combobox 鍵盤開啟語意 — 對齊 sibling Select(select.tsx:593-598)。\n // <div role=combobox> 不像 native <button> 自動 synthesize Enter/Space click,故顯式補:\n // Enter/Space → 開(searchable 時不攔,讓 inline search input 自行處理打字);\n // Escape → 關;ArrowDown → 開(APG required combobox 展開鍵,僅在 closed 時攔,\n // open 後不 preventDefault 讓方向鍵自由流向選單導覽)。\n // 純 additive:此 trigger 原無 onKeyDown,不覆寫既有 handler;open 邏輯仍走 setOpen SSOT。\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n // 2026-06-11 P0 a11y(R2,同 select.tsx 修):Combobox 恆 searchable → 原 `!searchable`\n // guard 使 Enter/Space 永遠開不了(僅 ArrowDown 可)。正確 guard = !open。\n if (!open) { e.preventDefault(); setOpen(true) }\n }\n if (e.key === 'ArrowDown' && !open) { e.preventDefault(); setOpen(true) }\n if (e.key === 'Escape') setOpen(false)\n }}>\n {/* 2026-05-18 #6A Round 1 Step 2/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:648):\n CustomCombobox edit non-wrap tagArea 對齊 L293 display + L451 readonly + L518 native edit 已 ship 的 overflow-hidden fix。\n 原 overflow-visible 讓 tag 越界蓋 chevron / +N indicator(user 圖三)。M10 propagation 完整 4-path align。 */}\n <div ref={tagAreaRef} className={cn('flex-1 min-w-0 flex items-center relative', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: tagAreaGap, paddingLeft: tagAreaPaddingLeftPx }}>\n {value.length > 0 ? (\n <OverflowTagList containerRef={tagAreaRef} items={items} size={size} wrap={wrap}\n tagWrapperClassName={tagWrapperClassName}\n overflowWrapperClassName={overflowWrapperClassName}\n gap={tagAreaGap}\n overflowShape={overflowShape}\n visibleCountOverride={visibleCountOverride}\n renderTag={(item) => (\n tagRenderer\n ? tagRenderer(item, () => handleRemove(item.value))\n : <Tag size={size} className=\"shrink-0 relative z-10\"\n onRemove={() => handleRemove(item.value)}>{item.label}</Tag>\n )}\n renderHiddenTag={renderHiddenTag}\n onRemove={handleRemove}\n trailing={searchable && searchIn === 'trigger' ? (\n <input ref={inputRef} value={search} onChange={(e) => setSearch(e.target.value)}\n // 2026-05-15 Drift A fix(per user verbatim SSOT clarification「未選 → placeholder 顯示請選擇之類」):\n // items.length === 0(empty selection)→ 用 `placeholder` trigger empty prop(「請選擇…」),\n // **不**用 `searchPlaceholder`(「搜尋…」);後者僅在 panel-top search input 場景才合理。\n // items.length > 0(已選)→ no placeholder,純 cursor(對齊 Combobox empty cursor SSOT)。\n // SSOT 對齊 select.tsx:185 `placeholder={selectedLabel || placeholder || '搜尋…'}`\n // empty-state fallback to trigger placeholder canonical。\n placeholder={items.length === 0 ? placeholder : ''} onClick={(e) => { e.stopPropagation(); setOpen(true) }}\n aria-label={searchAriaLabel}\n className=\"flex-1 min-w-[60px] bg-transparent outline-none text-body leading-compact relative z-10\" />\n ) : undefined} />\n ) : (\n /* 2026-05-12 Stream C Issue 3 fix(codex Q3 Cluster C):placeholder span 必 flex-1 min-w-0\n truncate,narrow container 時單行省略(對齊 Combobox text-tag truncate canonical)。\n 原 hardcode wraps in narrow trigger → user 抓「placeholder 文字 wrap multi-line」。 */\n <span className=\"flex-1 min-w-0 truncate text-fg-muted\">{placeholder ?? emptyPlaceholder}</span>\n )}\n </div>\n <ItemSuffix className={cn('relative z-10 pointer-events-none', wrap && 'self-start')}\n style={wrap ? { height: tagHeight } : undefined}>\n {showClear && (\n <span className=\"pointer-events-auto\">\n <ItemInlineAction\n size={size ?? 'md'}\n action={{\n icon: X,\n label: '清除全部', // i18n-allow: DS default inline-action label\n onClick: (e) => { e?.stopPropagation(); onChange?.([]) },\n }}\n />\n </span>\n )}\n {chevronEl}\n </ItemSuffix>\n </div>\n )\n\n return (\n <SelectMenu\n loading={loading}\n options={menuOptions}\n value={value}\n onValueChange={onChange as (value: string | string[]) => void}\n multiple\n searchable={searchable && searchIn === 'menu'}\n searchPlaceholder={searchPlaceholder}\n size={size}\n open={open}\n onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}\n // 2026-05-12 Q3 fix(user 抓「inline-searchable 開浮層應出現 cursor」)— 跟 Select line 550\n // 同 SSOT pattern:preventDefault Radix default focus + 顯式 focus inline input → 開時\n // cursor 直接 visible,user 知道可 inline search。\n onOpenAutoFocus={searchIn === 'trigger' ? (e) => { e.preventDefault(); inputRef.current?.focus() } : undefined}\n contentId={listboxId}\n >\n {trigger}\n </SelectMenu>\n )\n}\n\n// ── Public component ────────────────────────────────────────────────────────\n\nconst Combobox = React.forwardRef<HTMLDivElement, ComboboxProps>(\n ({ size: sizeProp, ...props }, ref) => {\n // B 組 cascade fix:public wrapper 一處 resolve size(prop>Field>surface>md)後 forward 給內層,\n // 讓 <Field size> / DataTable cell surface-size 對 Combobox 生效(內層默認 'md' 被此顯式 size 覆蓋)。\n const size = useResolvedFieldSize(sizeProp)\n // 2026-05-16 真 root cause fix:之前用 `_ref` drop ref。修為 forward 給 internal\n // `__triggerRef`,讓 PeoplePicker stack 透過 ref 量 trigger DOM(visibleCountOverride\n // 才生效)。對齊 React forwardRef public-API canonical(MUI Autocomplete / Radix\n // Popover.Trigger 共識)+ codex M31 Step 5 比稿 verdict + DS-wide ref-drop iceberg audit。\n const isMobile = useIsTouchDevice()\n if (isMobile) return <NativeCombobox {...props} size={size} __triggerRef={ref} />\n return <CustomCombobox {...props} size={size} __triggerRef={ref} />\n }\n)\nCombobox.displayName = 'Combobox'\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 comboboxMeta = {\n component: 'Combobox',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-transparent'],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { Combobox }\n"],"names":[],"mappings":"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"}
|
|
@@ -96,7 +96,7 @@ declare const CommandShortcut: {
|
|
|
96
96
|
};
|
|
97
97
|
export declare const commandMeta: {
|
|
98
98
|
readonly component: "Command";
|
|
99
|
-
readonly family:
|
|
99
|
+
readonly family: "composite";
|
|
100
100
|
readonly variants: {};
|
|
101
101
|
readonly sizes: {};
|
|
102
102
|
readonly states: readonly ["default", "hover", "active", "focus-visible", "disabled"];
|
|
@@ -72,7 +72,7 @@ const CommandItem = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
72
72
|
{
|
|
73
73
|
ref,
|
|
74
74
|
className: cn(
|
|
75
|
-
"relative flex cursor-default gap-2 select-none items-center rounded-md px-2 py-1.5 text-body outline-none data-[disabled=true]:pointer-events-none data-[selected=
|
|
75
|
+
"relative flex cursor-default gap-2 select-none items-center rounded-md px-2 py-1.5 text-body outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-neutral-hover data-[selected=true]:text-foreground data-[disabled=true]:text-fg-disabled [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
76
76
|
className
|
|
77
77
|
),
|
|
78
78
|
...props
|
|
@@ -97,8 +97,8 @@ const CommandShortcut = ({
|
|
|
97
97
|
CommandShortcut.displayName = "CommandShortcut";
|
|
98
98
|
const commandMeta = {
|
|
99
99
|
component: "Command",
|
|
100
|
-
family:
|
|
101
|
-
//
|
|
100
|
+
family: "composite",
|
|
101
|
+
// 對齊 command.spec.md frontmatter family: composite(SSOT)
|
|
102
102
|
variants: {},
|
|
103
103
|
sizes: {},
|
|
104
104
|
states: ["default", "hover", "active", "focus-visible", "disabled"],
|