@qijenchen/design-system 0.1.0-beta.117 → 0.1.0-beta.119

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/components/Button/button-group.d.ts +29 -3
  2. package/dist/components/Button/button-group.d.ts.map +1 -1
  3. package/dist/components/Button/button-group.js +29 -12
  4. package/dist/components/Button/button-group.js.map +1 -1
  5. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  6. package/dist/components/FileViewer/file-viewer.js +3 -3
  7. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/patterns/element-anatomy/item-anatomy.js +1 -1
  11. package/ds-canonical/fork/consumer/lock.json +6 -6
  12. package/ds-canonical/fork/consumer/managed-files/145aab4b4fc470fc9ba7679b4a1d87d4f6a6d604940bd40f8d675c8af696498f.blob +6 -0
  13. package/ds-canonical/fork/consumer/managed-files/5aeda1df235815f40dd755951f2b868d4e02035653d4536a8dde209cdc75b493.blob +23 -0
  14. package/ds-canonical/fork/governance.lock +5 -5
  15. package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +4 -3
  16. package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +3 -2
  17. package/ds-canonical/fork/manifest.json +10 -10
  18. package/ds-canonical/hooks/check_consumer_app_invariants.sh +4 -3
  19. package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +3 -2
  20. package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +1 -1
  21. package/package.json +1 -1
  22. package/src/components/Button/button-group.tsx +60 -17
  23. package/src/components/FileViewer/file-viewer.tsx +7 -6
  24. package/src/components/Separator/separator.spec.md +2 -2
  25. package/src/index.ts +1 -0
  26. package/src/patterns/action-bar/action-bar.spec.md +7 -4
  27. package/src/tokens/uiSize/uiSize.css +16 -0
@@ -32,10 +32,36 @@ interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {
32
32
  align?: 'start' | 'center' | 'end';
33
33
  }
34
34
  declare const ButtonGroup: React.ForwardRefExoticComponent<ButtonGroupProps & React.RefAttributes<HTMLDivElement>>;
35
+ export interface ButtonDividerProps extends React.HTMLAttributes<HTMLDivElement> {
36
+ /**
37
+ * 群組排列方向。橫排群組畫直線(預設),直排群組畫橫線。寫死方向會讓
38
+ * `<ButtonGroup orientation="vertical">` 出現方向錯誤的線。
39
+ */
40
+ orientation?: 'horizontal' | 'vertical';
41
+ /**
42
+ * 是否對輔助科技宣告語意。預設 `true`(純視覺)→ `role="none"`,與 Separator primitive
43
+ * 預設一致(separator.spec.md:102「純視覺分隔,SR 不朗讀」)。動作群組的分組語意已由排列
44
+ * 順序與間距表達,線本身不切分內容,故不朗讀。
45
+ */
46
+ decorative?: boolean;
47
+ }
35
48
  /**
36
- * ButtonDivider — 按鈕群組內的分隔線
49
+ * ButtonDivider — action region 群組分隔線
50
+ *
51
+ * **幾何 SSOT** → `patterns/action-bar/action-bar.spec.md`「分隔線幾何」:
52
+ * 線長 = 所在那一列**最高元件**的高度 − `--action-divider-inset`(8px),不短於
53
+ * `--action-divider-min`(16px),垂直置中。
54
+ *
55
+ * 兩層構造是必要的而非包裝:外層 `self-stretch` 取「該列最高元件」當基準——同一列的每條線
56
+ * 因此必然等長、不會參差;內層畫線並套 inset 與地板,置中由外層 `items-center` 負責,所以
57
+ * 地板生效時線仍置中。單層寫法(`self-stretch` + `min-h`)會被釘在行首,地板一啟動線就歪掉。
58
+ * 外層佔位與舊寫法完全相同,改動不位移任何同列元素。
59
+ *
60
+ * **前提**:必須放在自動高度的 cluster 內。直接置於固定高度容器(如 48px chrome header)時
61
+ * 基準會退化成容器高——CSS 無法在容器有明確高度時看見「最高手足」。由
62
+ * `scripts/test-action-divider-placement.mjs` 機械把關。
37
63
  *
38
- * 自身左右各 4px,與相鄰按鈕形成 8px(gap)+ 4px(自身)= 12px 的視覺距離。
64
+ * **水平間距**:自身兩端各 4px,與 `gap-2` 容器合成 12px 視覺距離。
39
65
  *
40
66
  * @example
41
67
  * <ButtonGroup>
@@ -44,6 +70,6 @@ declare const ButtonGroup: React.ForwardRefExoticComponent<ButtonGroupProps & Re
44
70
  * <Button variant="primary" danger size="sm" iconOnly startIcon={Trash2} aria-label="刪除" />
45
71
  * </ButtonGroup>
46
72
  */
47
- declare const ButtonDivider: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
73
+ declare const ButtonDivider: React.ForwardRefExoticComponent<ButtonDividerProps & React.RefAttributes<HTMLDivElement>>;
48
74
  export { ButtonGroup, ButtonDivider };
49
75
  //# sourceMappingURL=button-group.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"button-group.d.ts","sourceRoot":"","sources":["../../../src/components/Button/button-group.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,UAAU,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACrE,yBAAyB;IACzB,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,oBAAoB;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;CACnC;AAMD,QAAA,MAAM,WAAW,yFAsBhB,CAAA;AAGD;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,aAAa,6GAcjB,CAAA;AAGF,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"button-group.d.ts","sourceRoot":"","sources":["../../../src/components/Button/button-group.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,UAAU,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACrE,yBAAyB;IACzB,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,oBAAoB;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;CACnC;AAMD,QAAA,MAAM,WAAW,yFAsBhB,CAAA;AAGD,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC9E;;;OAGG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,QAAA,MAAM,aAAa,2FA8BlB,CAAA;AAGD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,CAAA"}
@@ -25,19 +25,36 @@ const ButtonGroup = React.forwardRef(
25
25
  }
26
26
  );
27
27
  ButtonGroup.displayName = "ButtonGroup";
28
- const ButtonDivider = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
29
- "div",
30
- {
31
- ref,
32
- role: "separator",
33
- className: cn(
34
- "self-stretch w-px mx-1 my-1",
35
- "bg-divider",
36
- className
37
- ),
38
- ...props
28
+ const ButtonDivider = React.forwardRef(
29
+ ({ className, orientation = "vertical", decorative = true, ...props }, ref) => {
30
+ const isVertical = orientation === "vertical";
31
+ return /* @__PURE__ */ jsx(
32
+ "div",
33
+ {
34
+ ref,
35
+ role: decorative ? "none" : "separator",
36
+ "aria-orientation": decorative ? void 0 : orientation,
37
+ "data-orientation": orientation,
38
+ "data-action-divider": "",
39
+ className: cn(
40
+ "flex shrink-0 self-stretch",
41
+ isVertical ? "w-px mx-1 items-center" : "h-px my-1 justify-center",
42
+ className
43
+ ),
44
+ ...props,
45
+ children: /* @__PURE__ */ jsx(
46
+ "div",
47
+ {
48
+ className: cn(
49
+ "bg-divider",
50
+ isVertical ? "w-px h-[calc(100%-var(--action-divider-inset))] min-h-[var(--action-divider-min)]" : "h-px w-[calc(100%-var(--action-divider-inset))] min-w-[var(--action-divider-min)]"
51
+ )
52
+ }
53
+ )
54
+ }
55
+ );
39
56
  }
40
- ));
57
+ );
41
58
  ButtonDivider.displayName = "ButtonDivider";
42
59
  export {
43
60
  ButtonDivider,
@@ -1 +1 @@
1
- {"version":3,"file":"button-group.js","sources":["../../../src/components/Button/button-group.tsx"],"sourcesContent":["import * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { ButtonGroupContext } from './button'\n\n/**\n * ButtonGroup — 按鈕群組容器\n *\n * 負責群組內的間距(gap 8px)與對齊方式。\n * 垂直排列時透過 ButtonGroupContext 讓所有直接子 Button 自動套用 fullWidth,\n * 無需 cloneElement — 符合 shadcn 的 Context 慣例。\n *\n * @example\n * // 水平排列(預設)\n * <ButtonGroup>\n * <Button variant=\"primary\">確認</Button>\n * <Button variant=\"tertiary\">取消</Button>\n * </ButtonGroup>\n *\n * // 靠右對齊(primary 放右側)\n * <ButtonGroup align=\"end\">\n * <Button variant=\"tertiary\">取消</Button>\n * <Button variant=\"primary\">確認</Button>\n * </ButtonGroup>\n *\n * // 垂直排列(fullWidth 自動套用)\n * <ButtonGroup orientation=\"vertical\">\n * <Button variant=\"primary\">確認</Button>\n * <Button variant=\"tertiary\">取消</Button>\n * </ButtonGroup>\n */\ninterface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 排列方向,預設 horizontal */\n orientation?: 'horizontal' | 'vertical'\n /** 水平對齊,預設 start */\n align?: 'start' | 'center' | 'end'\n}\n\n// Module-level 常數(2026-04-22 D3 perf audit):provider value 為 2 狀態 boolean,hoist 避免 render 重建\nconst BUTTON_GROUP_CTX_VERTICAL = { fullWidth: true } as const\nconst BUTTON_GROUP_CTX_HORIZONTAL = { fullWidth: false } as const\n\nconst ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(\n ({ orientation = 'horizontal', align = 'start', className, children, ...props }, ref) => {\n const isVertical = orientation === 'vertical'\n\n return (\n <ButtonGroupContext.Provider value={isVertical ? BUTTON_GROUP_CTX_VERTICAL : BUTTON_GROUP_CTX_HORIZONTAL}>\n <div\n ref={ref}\n className={cn(\n 'flex gap-2',\n isVertical ? 'flex-col items-stretch' : 'flex-row items-center flex-wrap',\n !isVertical && align === 'center' && 'justify-center',\n !isVertical && align === 'end' && 'justify-end',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </ButtonGroupContext.Provider>\n )\n }\n)\nButtonGroup.displayName = 'ButtonGroup'\n\n/**\n * ButtonDivider — 按鈕群組內的分隔線\n *\n * 自身左右各 4px,與相鄰按鈕形成 8px(gap)+ 4px(自身)= 12px 的視覺距離。\n *\n * @example\n * <ButtonGroup>\n * <Button variant=\"text\" size=\"sm\" iconOnly startIcon={Settings} aria-label=\"設定\" />\n * <ButtonDivider />\n * <Button variant=\"primary\" danger size=\"sm\" iconOnly startIcon={Trash2} aria-label=\"刪除\" />\n * </ButtonGroup>\n */\nconst ButtonDivider = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"separator\"\n className={cn(\n 'self-stretch w-px mx-1 my-1',\n 'bg-divider',\n className,\n )}\n {...props}\n />\n))\nButtonDivider.displayName = 'ButtonDivider'\n\nexport { ButtonGroup, ButtonDivider }\n"],"names":[],"mappings":";;;;AAsCA,MAAM,4BAA4B,EAAE,WAAW,KAAA;AAC/C,MAAM,8BAA8B,EAAE,WAAW,MAAA;AAEjD,MAAM,cAAc,MAAM;AAAA,EACxB,CAAC,EAAE,cAAc,cAAc,QAAQ,SAAS,WAAW,UAAU,GAAG,MAAA,GAAS,QAAQ;AACvF,UAAM,aAAa,gBAAgB;AAEnC,+BACG,mBAAmB,UAAnB,EAA4B,OAAO,aAAa,4BAA4B,6BAC3E,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA,aAAa,2BAA2B;AAAA,UACxC,CAAC,cAAc,UAAU,YAAY;AAAA,UACrC,CAAC,cAAc,UAAU,SAAY;AAAA,UACrC;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH;AAAA,MAAA;AAAA,IAAA,GAEL;AAAA,EAEJ;AACF;AACA,YAAY,cAAc;AAc1B,MAAM,gBAAgB,MAAM,WAG1B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1B;AAAA,EAAC;AAAA,EAAA;AAAA,IACC;AAAA,IACA,MAAK;AAAA,IACL,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,EAAA;AACN,CACD;AACD,cAAc,cAAc;"}
1
+ {"version":3,"file":"button-group.js","sources":["../../../src/components/Button/button-group.tsx"],"sourcesContent":["import * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { ButtonGroupContext } from './button'\n\n/**\n * ButtonGroup — 按鈕群組容器\n *\n * 負責群組內的間距(gap 8px)與對齊方式。\n * 垂直排列時透過 ButtonGroupContext 讓所有直接子 Button 自動套用 fullWidth,\n * 無需 cloneElement — 符合 shadcn 的 Context 慣例。\n *\n * @example\n * // 水平排列(預設)\n * <ButtonGroup>\n * <Button variant=\"primary\">確認</Button>\n * <Button variant=\"tertiary\">取消</Button>\n * </ButtonGroup>\n *\n * // 靠右對齊(primary 放右側)\n * <ButtonGroup align=\"end\">\n * <Button variant=\"tertiary\">取消</Button>\n * <Button variant=\"primary\">確認</Button>\n * </ButtonGroup>\n *\n * // 垂直排列(fullWidth 自動套用)\n * <ButtonGroup orientation=\"vertical\">\n * <Button variant=\"primary\">確認</Button>\n * <Button variant=\"tertiary\">取消</Button>\n * </ButtonGroup>\n */\ninterface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 排列方向,預設 horizontal */\n orientation?: 'horizontal' | 'vertical'\n /** 水平對齊,預設 start */\n align?: 'start' | 'center' | 'end'\n}\n\n// Module-level 常數(2026-04-22 D3 perf audit):provider value 為 2 狀態 boolean,hoist 避免 render 重建\nconst BUTTON_GROUP_CTX_VERTICAL = { fullWidth: true } as const\nconst BUTTON_GROUP_CTX_HORIZONTAL = { fullWidth: false } as const\n\nconst ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(\n ({ orientation = 'horizontal', align = 'start', className, children, ...props }, ref) => {\n const isVertical = orientation === 'vertical'\n\n return (\n <ButtonGroupContext.Provider value={isVertical ? BUTTON_GROUP_CTX_VERTICAL : BUTTON_GROUP_CTX_HORIZONTAL}>\n <div\n ref={ref}\n className={cn(\n 'flex gap-2',\n isVertical ? 'flex-col items-stretch' : 'flex-row items-center flex-wrap',\n !isVertical && align === 'center' && 'justify-center',\n !isVertical && align === 'end' && 'justify-end',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </ButtonGroupContext.Provider>\n )\n }\n)\nButtonGroup.displayName = 'ButtonGroup'\n\nexport interface ButtonDividerProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * 群組排列方向。橫排群組畫直線(預設),直排群組畫橫線。寫死方向會讓\n * `<ButtonGroup orientation=\"vertical\">` 出現方向錯誤的線。\n */\n orientation?: 'horizontal' | 'vertical'\n /**\n * 是否對輔助科技宣告語意。預設 `true`(純視覺)→ `role=\"none\"`,與 Separator primitive\n * 預設一致(separator.spec.md:102「純視覺分隔,SR 不朗讀」)。動作群組的分組語意已由排列\n * 順序與間距表達,線本身不切分內容,故不朗讀。\n */\n decorative?: boolean\n}\n\n/**\n * ButtonDivider — action region 群組分隔線\n *\n * **幾何 SSOT** → `patterns/action-bar/action-bar.spec.md`「分隔線幾何」:\n * 線長 = 所在那一列**最高元件**的高度 − `--action-divider-inset`(8px),不短於\n * `--action-divider-min`(16px),垂直置中。\n *\n * 兩層構造是必要的而非包裝:外層 `self-stretch` 取「該列最高元件」當基準——同一列的每條線\n * 因此必然等長、不會參差;內層畫線並套 inset 與地板,置中由外層 `items-center` 負責,所以\n * 地板生效時線仍置中。單層寫法(`self-stretch` + `min-h`)會被釘在行首,地板一啟動線就歪掉。\n * 外層佔位與舊寫法完全相同,改動不位移任何同列元素。\n *\n * **前提**:必須放在自動高度的 cluster 內。直接置於固定高度容器(如 48px chrome header)時\n * 基準會退化成容器高——CSS 無法在容器有明確高度時看見「最高手足」。由\n * `scripts/test-action-divider-placement.mjs` 機械把關。\n *\n * **水平間距**:自身兩端各 4px,與 `gap-2` 容器合成 12px 視覺距離。\n *\n * @example\n * <ButtonGroup>\n * <Button variant=\"text\" size=\"sm\" iconOnly startIcon={Settings} aria-label=\"設定\" />\n * <ButtonDivider />\n * <Button variant=\"primary\" danger size=\"sm\" iconOnly startIcon={Trash2} aria-label=\"刪除\" />\n * </ButtonGroup>\n */\nconst ButtonDivider = React.forwardRef<HTMLDivElement, ButtonDividerProps>(\n ({ className, orientation = 'vertical', decorative = true, ...props }, ref) => {\n const isVertical = orientation === 'vertical'\n return (\n <div\n ref={ref}\n role={decorative ? 'none' : 'separator'}\n aria-orientation={decorative ? undefined : orientation}\n data-orientation={orientation}\n // 穩定標記:讓像素量測(scripts/measure-action-dividers.mjs)只鎖 action region 群組線,\n // 不會誤抓版面切分家族(DataTable 欄界 / 欄寬把手 / Steps connector — 那些不適用本幾何)。\n data-action-divider=\"\"\n className={cn(\n 'flex shrink-0 self-stretch',\n isVertical ? 'w-px mx-1 items-center' : 'h-px my-1 justify-center',\n className,\n )}\n {...props}\n >\n <div\n className={cn(\n 'bg-divider',\n isVertical\n ? 'w-px h-[calc(100%-var(--action-divider-inset))] min-h-[var(--action-divider-min)]'\n : 'h-px w-[calc(100%-var(--action-divider-inset))] min-w-[var(--action-divider-min)]',\n )}\n />\n </div>\n )\n },\n)\nButtonDivider.displayName = 'ButtonDivider'\n\nexport { ButtonGroup, ButtonDivider }\n"],"names":[],"mappings":";;;;AAsCA,MAAM,4BAA4B,EAAE,WAAW,KAAA;AAC/C,MAAM,8BAA8B,EAAE,WAAW,MAAA;AAEjD,MAAM,cAAc,MAAM;AAAA,EACxB,CAAC,EAAE,cAAc,cAAc,QAAQ,SAAS,WAAW,UAAU,GAAG,MAAA,GAAS,QAAQ;AACvF,UAAM,aAAa,gBAAgB;AAEnC,+BACG,mBAAmB,UAAnB,EAA4B,OAAO,aAAa,4BAA4B,6BAC3E,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA,aAAa,2BAA2B;AAAA,UACxC,CAAC,cAAc,UAAU,YAAY;AAAA,UACrC,CAAC,cAAc,UAAU,SAAY;AAAA,UACrC;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH;AAAA,MAAA;AAAA,IAAA,GAEL;AAAA,EAEJ;AACF;AACA,YAAY,cAAc;AAyC1B,MAAM,gBAAgB,MAAM;AAAA,EAC1B,CAAC,EAAE,WAAW,cAAc,YAAY,aAAa,MAAM,GAAG,MAAA,GAAS,QAAQ;AAC7E,UAAM,aAAa,gBAAgB;AACnC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAM,aAAa,SAAS;AAAA,QAC5B,oBAAkB,aAAa,SAAY;AAAA,QAC3C,oBAAkB;AAAA,QAGlB,uBAAoB;AAAA,QACpB,WAAW;AAAA,UACT;AAAA,UACA,aAAa,2BAA2B;AAAA,UACxC;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,aACI,sFACA;AAAA,YAAA;AAAA,UACN;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAGN;AACF;AACA,cAAc,cAAc;"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-viewer.d.ts","sourceRoot":"","sources":["../../../src/components/FileViewer/file-viewer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AAyCzD,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EAGb,MAAM,qBAAqB,CAAA;AAgF5B,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAQjE;AA0hBD;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kDAAkD;IAClD,8BAA8B,CAAC,EAAE,MAAM,CAAA;IACvC,kDAAkD;IAClD,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAqBD,MAAM,WAAW,eACf,SAAQ,IAAI,CACV,KAAK,CAAC,wBAAwB,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,EAC9D,cAAc,GAAG,UAAU,CAC5B;IACD,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;IACnE,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gCAAgC;IAChC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,qDAAqD;IACrD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAA;IACrC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAC1B;AAED,QAAA,MAAM,UAAU,wFAwWd,CAAA;AAKF,eAAO,MAAM,cAAc;;;;;;;;;;;CAiBjB,CAAA;AAEV,OAAO,EAAE,UAAU,EAAE,CAAA;AACrB,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA"}
1
+ {"version":3,"file":"file-viewer.d.ts","sourceRoot":"","sources":["../../../src/components/FileViewer/file-viewer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AAyCzD,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EAGb,MAAM,qBAAqB,CAAA;AAgF5B,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAQjE;AA2hBD;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kDAAkD;IAClD,8BAA8B,CAAC,EAAE,MAAM,CAAA;IACvC,kDAAkD;IAClD,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAqBD,MAAM,WAAW,eACf,SAAQ,IAAI,CACV,KAAK,CAAC,wBAAwB,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC,EAC9D,cAAc,GAAG,UAAU,CAC5B;IACD,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;IACnE,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gCAAgC;IAChC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,qDAAqD;IACrD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAA;IACrC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAC1B;AAED,QAAA,MAAM,UAAU,wFAwWd,CAAA;AAKF,eAAO,MAAM,cAAc;;;;;;;;;;;CAiBjB,CAAA;AAEV,OAAO,EAAE,UAAU,EAAE,CAAA;AACrB,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA"}
@@ -5,7 +5,7 @@ import { ChevronLeft, ChevronRight, File, Info, Download, X, FileText, Minus, Ch
5
5
  import { cn } from "../../lib/utils.js";
6
6
  import { OVERLAY_SIDE_OFFSET } from "../../tokens/elevation/overlay-geometry.js";
7
7
  import { Button } from "../Button/button.js";
8
- import { Separator } from "../Separator/separator.js";
8
+ import { ButtonDivider } from "../Button/button-group.js";
9
9
  import { Input } from "../Input/input.js";
10
10
  import { Empty } from "../Empty/empty.js";
11
11
  import { AspectRatio } from "../AspectRatio/aspect-ratio.js";
@@ -236,7 +236,7 @@ const Toolbar = ({
236
236
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
237
237
  capabilities.zoom && /* @__PURE__ */ jsxs(Fragment, { children: [
238
238
  /* @__PURE__ */ jsx(ZoomInput, { value: zoom, onChange: onZoomChange, onFit, labels }),
239
- /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-6 mx-1" })
239
+ /* @__PURE__ */ jsx(ButtonDivider, {})
240
240
  ] }),
241
241
  /* @__PURE__ */ jsx(
242
242
  Button,
@@ -261,7 +261,7 @@ const Toolbar = ({
261
261
  onClick: onDownload
262
262
  }
263
263
  ),
264
- /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-6 mx-1" }),
264
+ /* @__PURE__ */ jsx(ButtonDivider, {}),
265
265
  /* @__PURE__ */ jsx(
266
266
  Button,
267
267
  {
@@ -1 +1 @@
1
- {"version":3,"file":"file-viewer.js","sources":["../../../src/components/FileViewer/file-viewer.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — composite 拼裝(Toolbar / ZoomInput / InfoPanel / Filmstrip + Dialog shell + renderer registry);拆檔會把 useState/useEffect/key handler 跨檔同步過於複雜\nimport * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport {\n X as XIcon,\n Download,\n Info,\n ChevronLeft,\n ChevronRight,\n ChevronDown,\n Plus,\n Minus,\n File as FileIcon,\n FileText,\n} from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { OVERLAY_SIDE_OFFSET } from '@/design-system/tokens/elevation/overlay-geometry'\nimport { Button } from '@/design-system/components/Button/button'\nimport { Separator } from '@/design-system/components/Separator/separator'\nimport { Input } from '@/design-system/components/Input/input'\nimport { Empty } from '@/design-system/components/Empty/empty'\nimport { AspectRatio } from '@/design-system/components/AspectRatio/aspect-ratio'\nimport { Textarea } from '@/design-system/components/Textarea/textarea'\nimport { Field, FieldLabel } from '@/design-system/components/Field/field'\nimport { DescriptionList, DescriptionItem } from '@/design-system/components/DescriptionList/description-list'\nimport { ItemInlineActionButton } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { ChromeHeader } from '@/design-system/patterns/header-canonical/chrome-header'\nimport { ScrollArea } from '@/design-system/components/ScrollArea/scroll-area'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\nimport { ARROW_BUTTON_WIDTH,\n useScrollEdges,\n useScrollByPage,\n buildFadeMask,\n OverflowScrollArrow,\n} from '@/design-system/patterns/horizontal-overflow/horizontal-overflow'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport { canRenderImage } from './can-render-image'\nimport type {\n FileInfo,\n FileRenderer,\n FileRendererCapabilities,\n FileRendererProps,\n} from './file-viewer-types'\nimport { surfaceMotion } from \"@/design-system/tokens/motion/overlay-motion\"\n\n/**\n * FileViewer — 可延伸的網頁檔案 preview shell(modal fullscreen)\n *\n * ── 定位 ──\n * 公開、composite 元件。consumer 傳 `files`,FileViewer 處理 overlay / toolbar /\n * keyboard / filmstrip / info panel 一切 chrome;檔案本體由 renderer registry\n * 按 file MIME 決定誰渲染(MVP 內建 ImageRenderer + FallbackRenderer)。\n *\n * ── 實作基礎 ──\n * 自建 composite,消費 DS primitives:\n * - Radix DialogPrimitive(焦點 trap / Esc / modal 隔離靠 hideOthers aria-hidden — react-dialog@1.1.x 不輸出 aria-modal,保有 shadcn 結構優勢)\n * - `<Empty>` / `<Button>` / `<Input size=\"sm\" autoWidth>`(ZoomInput,default variant)/ `<AspectRatio>` / `<Textarea>` / `<DropdownMenu>`\n * - `patterns/horizontal-overflow`(filmstrip 溢出捲動)\n * 不用 DS 的 `<Dialog>` wrapper:因為 FileViewer 需要 edge-to-edge fullscreen\n * (無 viewport inset / 無 rounded-lg / 無 maxWidth),Dialog 的這些預設都要覆寫。\n * 直接消費 Radix primitive 讓 shell 擁有完整 layout 控制權。\n *\n * ── Layout Family ──\n * 非 Family 1/2/3/4 — composite / multi-region(Toolbar / Viewport / Filmstrip +\n * 可選 InfoPanel)。見 `file-viewer.spec.md`「Layout Family」段。\n *\n * ── Extensibility ──\n * `registerFileRenderer(renderer)` 註冊新 renderer;shell 按註冊順序 iterate,\n * 第一個 `canRender(file)` 回 true 的渲染。FallbackRenderer 永遠兜底(未知檔案\n * 類型顯示 icon + 檔名 + download)。\n */\n\n// ─── Renderer Registry ────────────────────────────────────────────────────────\n\n/**\n * Fallback renderer — 無 renderer 能處理時兜底。\n * 顯示 Empty 佈局:icon + 檔名 + 「請下載檢視」提示。\n */\nconst FallbackRenderer: React.FC<FileRendererProps> = ({ file, onCapabilitiesChange }) => {\n // Contract(file-viewer-types.ts `onCapabilitiesChange` doc):「第一個 renderer mount\n // 時必須 emit」。fallback 不支援 zoom → emit { zoom: false };不 emit 的話 shell 會沿用\n // 上一個 renderer 的 capability(image → 未知檔切換時 toolbar 殘留 zoom controls,\n // `+`/`-`/`0`/`F` 快捷鍵也會對不支援 zoom 的檔案生效)。同 image-renderer.tsx mount emit pattern。\n React.useEffect(() => {\n onCapabilitiesChange({ zoom: false })\n }, [onCapabilitiesChange])\n\n return (\n <div className=\"w-full h-full flex items-center justify-center p-8\">\n <Empty\n icon={FileText}\n title={file.name}\n description={`無法在瀏覽器中預覽此檔案類型(${file.mimeType || 'unknown'})。請下載後檢視。`}\n />\n </div>\n )\n}\n\nconst fallbackRenderer: FileRenderer = {\n id: 'fallback',\n canRender: () => true,\n component: FallbackRenderer,\n}\n\n// 2026-07-06 D3 bundle fix:ImageRenderer 改 React.lazy 動態 import——FileViewer 是\n// rare-use modal overlay,react-zoom-pan-pinch(~12KB gz)不該在 viewer 從未打開時就進\n// consumer 首屏 bundle。canRenderImage(registry 判斷 + Filmstrip 分流需同步呼叫)已拆至\n// ./can-render-image 保持靜態 import。\nconst LazyImageRenderer = React.lazy(() =>\n import('./image-renderer').then((m) => ({ default: m.ImageRenderer })),\n)\n\nconst imageRenderer: FileRenderer = {\n id: 'image',\n canRender: canRenderImage,\n component: LazyImageRenderer,\n}\n\n// Registry 是 module-singleton:新 renderer 透過 registerFileRenderer 加入。\n// Fallback 永遠最後(兜底),因此用陣列第二段存放。\nconst userRegistered: FileRenderer[] = []\n\nexport function registerFileRenderer(renderer: FileRenderer): void {\n // 去重:同 id 則覆寫\n const existingIdx = userRegistered.findIndex((r) => r.id === renderer.id)\n if (existingIdx >= 0) {\n userRegistered[existingIdx] = renderer\n } else {\n userRegistered.push(renderer)\n }\n}\n\nfunction resolveRenderer(file: FileInfo): FileRenderer {\n // 先查 user registered,再 built-in,最後 fallback\n for (const r of userRegistered) {\n if (r.canRender(file)) return r\n }\n if (imageRenderer.canRender(file)) return imageRenderer\n return fallbackRenderer\n}\n\n// ─── Zoom presets ─────────────────────────────────────────────────────────────\n\ntype ZoomFit = 'fit-width' | 'fit-page'\n\nconst ZOOM_PRESETS: number[] = [10, 25, 50, 75, 100, 125, 150, 200, 400]\n// i18n-allow-block: DS defaults for zoom fit menu;consumer override via `labels.zoomFitOptions` (future) or fork\nconst ZOOM_FIT_OPTIONS: { value: ZoomFit; label: string }[] = [\n { value: 'fit-width', label: 'Fit to width' },\n { value: 'fit-page', label: 'Fit to page' },\n]\n\nfunction nextZoomIn(current: number): number {\n for (const p of ZOOM_PRESETS) {\n if (p > current) return p\n }\n return ZOOM_PRESETS[ZOOM_PRESETS.length - 1]\n}\nfunction nextZoomOut(current: number): number {\n for (let i = ZOOM_PRESETS.length - 1; i >= 0; i--) {\n if (ZOOM_PRESETS[i] < current) return ZOOM_PRESETS[i]\n }\n return ZOOM_PRESETS[0]\n}\n\n// ─── ZoomInput ────────────────────────────────────────────────────────────────\n\ninterface ZoomInputProps {\n value: number\n onChange: (next: number) => void\n onFit: (fit: ZoomFit) => void\n labels: Pick<Required<FileViewerLabels>, 'zoomInput' | 'zoomMenu'>\n}\n\n/**\n * ZoomInput — [−] [% input(default variant + autoWidth)with ⌄ menu trigger] [+]\n *\n * 世界級對照:Figma zoom control / Adobe Acrobat / Google Slides zoom。\n *\n * ── 消費 DS primitive ──\n * - `<Button>` iconOnly size=\"sm\" 作 ±按鈕\n * - `<Input size=\"sm\" autoWidth>` 作 %輸入(Toolbar inline editing canonical;autoWidth 隨文字寬)\n * - Input `endSlot` escape hatch 包 `<DropdownMenuTrigger asChild>` + chevron 觸發 DropdownMenu\n * - `<DropdownMenu>` 作 preset + fit 選單(取代原先 Popover + 手刻 button list)\n *\n * ── 為什麼 inline(不抽獨立 primitive)──\n * 目前只 FileViewer 消費;MVP 階段遵循 YAGNI。當 PDF / Video viewer 也需要相同\n * primitive 時,再依「建立前必查既有 pattern」原則從 FileViewer 抽出升級。\n */\nconst ZoomInput: React.FC<ZoomInputProps> = ({ value, onChange, onFit, labels }) => {\n const [draft, setDraft] = React.useState<string>(`${value}%`)\n const [menuOpen, setMenuOpen] = React.useState(false)\n\n React.useEffect(() => {\n setDraft(`${value}%`)\n }, [value])\n\n const commitDraft = () => {\n const parsed = parseInt(draft.replace(/[^0-9]/g, ''), 10)\n if (Number.isFinite(parsed) && parsed > 0) {\n // 限 10–400 範圍,對齊 ImageRenderer MIN_SCALE/MAX_SCALE\n const clamped = Math.min(400, Math.max(10, parsed))\n onChange(clamped)\n setDraft(`${clamped}%`)\n } else {\n setDraft(`${value}%`)\n }\n }\n\n return (\n // zoom group = toolbar 按鈕群組,`gap-2`(8px)對齊本 DS 按鈕 gap canonical。\n <div className=\"inline-flex items-center gap-2\">\n {/* 縮小 */}\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={Minus}\n aria-label=\"縮小\"\n disabled={value <= 10}\n onClick={() => onChange(nextZoomOut(value))}\n />\n\n {/* % Input + chevron 內嵌為 endSlot(ItemInlineActionButton 作 DropdownMenuTrigger):\n — Input body 可自由打字(chevron 是 Input 內部 element,body 區域 click 不觸發 menu)\n — Chevron 是 inline action,同時是 DropdownMenuTrigger → menu 精確 anchor 在 chevron 下方\n — 靠 Radix asChild + ItemInlineActionButton:視覺是 Input + endAction,行為是 chevron-as-trigger\n — 完全對齊 user AR:「只有 inline action 能觸發選單,menu 對齊 inline action」 */}\n <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>\n {/* maxLength 限縮(2026-07-09 user 要求「找合理最大值避免無限輸入」):autoWidth 下輸入框寬度隨字數長,\n 無上限則可無限打字撐爆 toolbar。max 合法值「400%」= 4 字(對齊 ZOOM_PRESETS / ImageRenderer MAX_SCALE 400;\n commitDraft 另 clamp 10–400),留 1 字編輯緩衝 → maxLength={5}。 */}\n <Input\n size=\"sm\"\n autoWidth\n maxLength={5}\n aria-label={labels.zoomInput}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commitDraft}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n commitDraft()\n ;(e.target as HTMLInputElement).blur()\n }\n }}\n className=\"text-center tabular-nums\"\n endSlot={\n <DropdownMenuTrigger asChild>\n <ItemInlineActionButton\n icon={ChevronDown}\n aria-label={labels.zoomMenu}\n size=\"sm\"\n overlayTrigger\n />\n </DropdownMenuTrigger>\n }\n />\n {/* data-theme=\"dark\":DropdownMenuContent 走 Portal 到 document body 外,\n 不繼承 FileViewer 外層 data-theme=\"dark\",需顯式打 dark 讓選單跟 chrome 一致。\n **加 bg-surface-raised 強制用 dark token**(純 data-theme attr 在 Portal 不夠,\n Tailwind 條件 class + CSS variable 都要一起帶) */}\n <DropdownMenuContent\n align=\"end\"\n sideOffset={OVERLAY_SIDE_OFFSET} // 2026-07-04:M17 — elevation.spec「primitive default 必 import 該 const」\n // minWidth 對齊 trigger(Input autoWidth),menu 寬度 fit-content 更貼近觸發點視覺中心\n className=\"min-w-[9rem] w-auto bg-surface-raised text-foreground border-divider\"\n data-theme=\"dark\"\n >\n {/* 內層 data-theme 再覆蓋一次 — 確保 DropdownMenuItem children 都 resolve dark token */}\n <div data-theme=\"dark\" className=\"contents\">\n {ZOOM_FIT_OPTIONS.map((opt) => (\n <DropdownMenuItem\n key={opt.value}\n onSelect={() => onFit(opt.value)}\n >\n {opt.label}\n </DropdownMenuItem>\n ))}\n <DropdownMenuSeparator />\n {ZOOM_PRESETS.map((p) => {\n const selected = p === value\n return (\n <DropdownMenuItem\n key={p}\n onSelect={() => onChange(p)}\n data-state={selected ? 'checked' : undefined}\n className={cn(\n 'tabular-nums',\n selected && 'bg-neutral-selected',\n )}\n >\n {p}%\n </DropdownMenuItem>\n )\n })}\n </div>\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* 放大 */}\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={Plus}\n aria-label=\"放大\"\n disabled={value >= 400}\n onClick={() => onChange(nextZoomIn(value))}\n />\n </div>\n )\n}\nZoomInput.displayName = 'ZoomInput'\n\n// ─── Toolbar ──────────────────────────────────────────────────────────────────\n\ninterface ToolbarProps {\n file: FileInfo\n capabilities: FileRendererCapabilities\n zoom: number\n onZoomChange: (z: number) => void\n onFit: (fit: ZoomFit) => void\n infoOpen: boolean\n onInfoToggle: () => void\n onDownload?: () => void\n allowDownload: boolean\n onClose: () => void\n labels: Required<FileViewerLabels>\n}\n\nconst Toolbar: React.FC<ToolbarProps> = ({\n file,\n capabilities,\n zoom,\n onZoomChange,\n onFit,\n infoOpen,\n onInfoToggle,\n onDownload,\n allowDownload,\n onClose,\n labels,\n}) => {\n return (\n <ChromeHeader\n // Density:繼承 page(2026-06-15 canonical)。原 lockDensity=\"lg\" 只鎖在 header、沒鎖 body →\n // InfoPanel header(px-loose@lg=24)與其 body(px-loose@page=16)左緣不對齊(圖二 bug)。移除 lock\n // → header + body 全繼承 page density,左緣對齊;FileViewer 全 surface 同一密度(non-special)。\n className={cn(\n // Chrome layer — `bg-surface-raised` 對齊 token semantic「遮蓋型浮層必須不透明」。\n // FileViewer 整體是 overlay,chrome 屬其 raised surface(同 DropdownMenuContent line 244)。\n // 不用 bg-surface(dark = white α8 半透明,outer 透明時失去 backdrop 洗白)。\n // 不用 bg-canvas(那是「頁面最底層」semantic,chrome 不是 page)。\n // ChromeHeader 自帶 flex/items-center/gap-2/shrink-0/h-chrome-header-height/border-b/px-loose\n 'bg-surface-raised',\n )}\n >\n {/* 檔名(左,佔據可用寬度,ellipsis)—— file-type icon 代表檔名的意象(這是什麼檔),\n 對齊 CLAUDE.md「icon 代表 label 意象時與 label 同色」原則:icon 走 text-foreground\n 不走 text-fg-muted(後者是裝飾性 / 輔助 icon 的色階) */}\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n <FileIcon size={16} className=\"text-foreground shrink-0\" aria-hidden />\n <span\n className=\"text-body-lg text-foreground truncate\"\n title={file.name}\n >\n {file.name}\n </span>\n </div>\n\n {/* 按鈕順序 canonical:zoom → info → download → close(影響力遞增)\n action-bar 三分區:zoom(data op)/ info+download(action group)/ close(dismiss)\n dismiss 前分隔線 = action-bar「dismiss 跟動作分群」canonical\n\n ── gap-2 canonical(2026-04-21 follow-up)──\n 按鈕間距 **8px**(gap-2),對齊 Dialog footer `gap-2` / CLAUDE 按鈕間距 SSOT。\n zoom group(ZoomInput)內部同樣是 gap-2(見 `ZoomInput` L197),\n 與這裡 action-group-to-action-group 的 gap-2 一致。 */}\n <div className=\"flex items-center gap-2 shrink-0\">\n {capabilities.zoom && (\n <>\n {/* Zoom group:-/%/+/▼ 屬同類「縮放」操作,群組並在右側加分隔線跟其他動作分群 */}\n <ZoomInput value={zoom} onChange={onZoomChange} onFit={onFit} labels={labels} />\n {/* zoom group → next action group divider(action-bar canonical;v11 升級成 Separator\n 元件,對齊 separator.spec.md「consumer 手動放置 toolbar 群組分隔線 = 用 Separator」)*/}\n <Separator orientation=\"vertical\" className=\"h-6 mx-1\" />\n </>\n )}\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={Info}\n aria-label={infoOpen ? labels.infoToggleCollapse : labels.infoToggleExpand}\n pressed={infoOpen}\n onClick={onInfoToggle}\n />\n {allowDownload && (\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={Download}\n aria-label={labels.download}\n onClick={onDownload}\n />\n )}\n {/* action-bar canonical:dismiss 前加分隔線跟其他動作分群(info/download = action group,\n close = dismiss group;v11 升級成 Separator,對齊 separator.spec.md canonical)*/}\n <Separator orientation=\"vertical\" className=\"h-6 mx-1\" />\n {/* Close X 走 dismiss canonical(`<Button iconOnly dismiss />`)——對齊 CLAUDE.md\n `button.spec.md`「Dismiss 視覺類」+ `patterns/element-anatomy/item-anatomy.spec.md`\n 「Dismiss canonical」:chrome corner close X = Button dismiss,不是 Inline Action。 */}\n <Button\n iconOnly\n dismiss\n size=\"sm\"\n data-dismiss\n startIcon={XIcon}\n aria-label={labels.close}\n onClick={onClose}\n />\n </div>\n </ChromeHeader>\n )\n}\n\n// ─── InfoPanel ────────────────────────────────────────────────────────────────\n\ninterface InfoPanelProps {\n file: FileInfo\n readOnly: boolean\n onDescriptionChange?: (fileId: string, description: string) => void\n onClose: () => void\n labels: Required<FileViewerLabels>\n}\n\nfunction formatBytes(bytes: number | undefined): string | undefined {\n if (bytes == null) return undefined\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n}\n\n// React.memo(2026-07-06 D3):同 Filmstrip——wheel-zoom 每 tick 全 chrome re-render,InfoPanel\n// 開啟時(Field/Textarea/DescriptionList subtree)白付。onClose 走 shell handleInfoClose\n// useCallback;file(files[activeIndex] identity 穩定)/readOnly/onDescriptionChange/labels 本已穩定。\nconst InfoPanel = React.memo(function InfoPanel({\n file,\n readOnly,\n onDescriptionChange,\n onClose,\n labels,\n}: InfoPanelProps) {\n const [draft, setDraft] = React.useState(file.description ?? '')\n\n React.useEffect(() => {\n setDraft(file.description ?? '')\n }, [file.id, file.description])\n\n const commit = () => {\n if (readOnly) return\n if (draft !== (file.description ?? '')) {\n onDescriptionChange?.(file.id, draft)\n }\n }\n\n const sizeText = formatBytes(file.size)\n\n return (\n <aside\n className={cn(\n // Chrome — bg-surface-raised 同 Toolbar / Filmstrip(token semantic「遮蓋型浮層」)\n 'w-80 shrink-0 flex flex-col bg-surface-raised border-l border-divider',\n 'h-full',\n )}\n aria-label={labels.detailPanel}\n >\n {/* Panel header — 與 Toolbar 同 ChromeHeader,繼承 page density(2026-06-15:移除 lockDensity,\n 修 header(原 px-loose@lg=24)與 body(px-loose@page=16)左緣不對齊 = 圖二 bug;全 surface 同密度)。 */}\n <ChromeHeader className=\"justify-between\">\n <h3 className=\"text-body-lg font-medium text-foreground\">{labels.detailsHeading}</h3>\n {/* InfoPanel close 走 dismiss canonical `<Button iconOnly dismiss />`,對齊 button.spec.md\n 「Dismiss 視覺類」+ inline-action.spec.md「Dismiss canonical — X close only」。 */}\n <Button\n iconOnly\n dismiss\n size=\"sm\"\n data-dismiss\n startIcon={XIcon}\n aria-label={labels.detailPanelClose}\n onClick={onClose}\n />\n </ChromeHeader>\n\n {/* Panel body — header(shrink-0)上常駐 + body 走 ScrollArea(高度小時內容可捲動)。\n padding 對齊 layoutSpace v6 規則 4「bounded region → 容器底(無 action buttons)= loose」。\n gap 對齊 v6 規則 3「跨範疇 parallel = loose」(說明 vs 檔案資訊兩個獨立 functional sections,\n 屬「跨範疇 + 不相關」)— 從 gap-4 寫死改為 token-aware loose。 */}\n <ScrollArea className=\"flex-1 min-h-0\">\n <div className={cn(\n 'flex flex-col gap-[var(--layout-space-loose)]',\n 'px-[var(--layout-space-loose)]',\n 'pt-[var(--layout-space-tight)] pb-[var(--layout-space-loose)]',\n )}>\n {/* 說明 — 用 DS Field + FieldLabel + Textarea(2026-04-20 B12 決策:\n FileViewer 一律消費 DS Field 家族,不手刻 `<span>label` + raw control) */}\n <Field>\n <FieldLabel>說明</FieldLabel>\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commit}\n readOnly={readOnly}\n placeholder={readOnly ? labels.descriptionPlaceholderReadOnly : labels.descriptionPlaceholderEdit}\n rows={5}\n />\n </Field>\n\n {/* 檔案資訊 — 用 DS DescriptionList horizontal + divided(Google Drive /\n Notion file info panel 模式):\n - section header 用 FieldLabel 同款 typography 保視覺一致\n - DescriptionList orientation=\"horizontal\" divided 提供 row 下底線\n 對齊格線,key 長度不一也易讀\n - 不再手刻 dl/dt/dd — canonical 由 DS primitive own */}\n {/* heading → first-item gap = item → item gap(Gestalt proximity,見 description-list.spec.md) */}\n <div className=\"flex flex-col gap-[var(--layout-space-tight)]\">\n <span className=\"text-body font-normal text-foreground\">{labels.fileInfoHeading}</span>\n <DescriptionList orientation=\"horizontal\" divided>\n <DescriptionItem label=\"檔名\">{file.name}</DescriptionItem>\n <DescriptionItem label=\"類型\">{file.mimeType || '-'}</DescriptionItem>\n {sizeText && (\n <DescriptionItem label=\"大小\">\n <span className=\"tabular-nums\">{sizeText}</span>\n </DescriptionItem>\n )}\n {file.metadata &&\n Object.entries(file.metadata).map(([k, v]) => (\n <DescriptionItem key={k} label={k}>{String(v)}</DescriptionItem>\n ))}\n </DescriptionList>\n </div>\n </div>\n </ScrollArea>\n </aside>\n )\n})\n\n// ─── Filmstrip ────────────────────────────────────────────────────────────────\n\ninterface FilmstripProps {\n files: FileInfo[]\n activeIndex: number\n onSelect: (index: number) => void\n labels: Pick<Required<FileViewerLabels>, 'filmstripLabel'>\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst THUMB_SIZE = 64 // px, 固定\n\n// React.memo(2026-07-06 D3):wheel-zoom 每 tick(trackpad 60Hz+)setZoom → shell 全 chrome\n// re-render,Filmstrip(N thumbs × cn + buildFadeMask)是最貴的無關 subtree。props 已全穩定\n// (files=prop / activeIndex=primitive / onSelect=setIndex useCallback / labels=useMemo)→ memo 真命中。\nconst Filmstrip = React.memo(function Filmstrip({ files, activeIndex, onSelect, labels }: FilmstripProps) {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n const scrollByPage = useScrollByPage(scrollRef)\n const maskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: ARROW_BUTTON_WIDTH }) // 2026-07-04:消 SSOT 硬寫(spec 禁止重新定義 ARROW_BUTTON_WIDTH)\n\n // 切換當前檔案時,自動 scroll 讓 active thumb 可見\n React.useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n const active = el.querySelector<HTMLButtonElement>(`[data-thumb-index=\"${activeIndex}\"]`)\n active?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' })\n }, [activeIndex, scrollRef])\n\n return (\n <div\n className={cn(\n // Chrome — bg-surface-raised 同 Toolbar / InfoPanel(token semantic「遮蓋型浮層」)\n 'relative shrink-0 h-24 bg-surface-raised border-t border-divider',\n 'flex items-center',\n 'px-[var(--layout-space-loose)]',\n )}\n >\n {canScroll && !atStart && (\n <OverflowScrollArrow direction=\"left\" onClick={() => scrollByPage('left')} />\n )}\n <div\n ref={scrollRef}\n className={cn(\n 'flex items-center',\n // 刻意隱藏 native scrollbar + 用 fade-mask(horizontal-overflow pattern)\n 'scrollbar-none overflow-x-auto overflow-y-hidden h-full py-2',\n 'w-full',\n )}\n style={{\n maskImage,\n WebkitMaskImage: maskImage,\n }}\n >\n {/* 內層 wrapper:mx-auto 讓 thumbs 在少量時水平置中,多量溢出時 mx-auto = 0 自然轉 scroll。\n gap-[var(--layout-space-tight)] 走 DS density-aware token(不用 raw gap-1)——\n 世界級 idiom:Google Drive / Dropbox / Notion file preview 的 filmstrip 都是\n 少量置中 / 多量靠 start scroll。\n ARIA role=\"group\"(非 tablist):filmstrip 是「選圖導航」不是「切換 N 個 tabpanel」——\n lightbox 縮圖膠卷只有單一 viewport,不符 W3C tabs pattern(tab MUST 對應自己的 tabpanel + roving tabindex)。\n 對齊 APG grouped-carousel + PhotoSwipe / codeaccessible.com lightbox 共識:group + button + aria-current。 */}\n <div\n role=\"group\"\n aria-label={labels.filmstripLabel}\n className=\"flex items-center gap-[var(--layout-space-tight)] mx-auto shrink-0\"\n >\n {files.map((file, i) => {\n const active = i === activeIndex\n const isImage = canRenderImage(file)\n const ext = file.name.split('.').pop()?.toUpperCase() ?? '檔'\n return (\n <button\n key={file.id}\n type=\"button\"\n aria-current={active ? 'true' : undefined}\n aria-label={`${i + 1} / ${files.length}:${file.name}`}\n data-thumb-index={i}\n onClick={() => onSelect(i)}\n className={cn(\n 'shrink-0 rounded-md bg-muted overflow-hidden',\n 'outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'transition-shadow duration-150',\n active\n ? 'ring-2 ring-primary'\n : 'ring-1 ring-border hover:ring-border-hover',\n )}\n style={{ width: THUMB_SIZE, height: THUMB_SIZE }}\n >\n <AspectRatio ratio={1} className=\"w-full h-full\">\n {isImage ? (\n <img\n src={file.url}\n alt=\"\"\n aria-hidden\n className=\"w-full h-full object-cover\"\n draggable={false}\n />\n ) : (\n <div className=\"w-full h-full flex flex-col items-center justify-center gap-0.5\">\n <FileText size={20} className=\"text-fg-muted\" aria-hidden />\n <span className=\"text-footnote text-fg-muted font-medium\">{ext}</span>\n </div>\n )}\n </AspectRatio>\n </button>\n )\n })}\n </div>\n </div>\n {canScroll && !atEnd && (\n <OverflowScrollArrow direction=\"right\" onClick={() => scrollByPage('right')} />\n )}\n </div>\n )\n})\n\n// ─── FileViewer (shell) ───────────────────────────────────────────────────────\n\n/**\n * i18n-able labels for FileViewer chrome / controls.\n * All keys are optional — defaults are CJK (see `DEFAULT_LABELS`).\n * Consumer typically spreads partial override:\n * `<FileViewer labels={{ close: 'Close', download: 'Download' }} />`\n */\n// code-quality-allow: dead-export — public API surface — consumer-exposed for future use\nexport interface FileViewerLabels {\n /** Zoom input ARIA label */\n zoomInput?: string\n /** Zoom menu trigger ARIA label */\n zoomMenu?: string\n /** Info panel toggle button — shown when panel is OPEN */\n infoToggleCollapse?: string\n /** Info panel toggle button — shown when panel is CLOSED */\n infoToggleExpand?: string\n /** Download button ARIA label */\n download?: string\n /** Close viewer button ARIA label */\n close?: string\n /** InfoPanel outer aside ARIA label */\n detailPanel?: string\n /** InfoPanel heading text */\n detailsHeading?: string\n /** InfoPanel close button ARIA label */\n detailPanelClose?: string\n /** Description textarea placeholder (readOnly) */\n descriptionPlaceholderReadOnly?: string\n /** Description textarea placeholder (editable) */\n descriptionPlaceholderEdit?: string\n /** Detail section — file info section heading */\n fileInfoHeading?: string\n /** Filmstrip group ARIA label(role=\"group\" + thumb button + aria-current,非 tablist)*/\n filmstripLabel?: string\n /** Previous-file nav button ARIA label */\n previousFile?: string\n /** Next-file nav button ARIA label */\n nextFile?: string\n}\n\n// i18n-allow: DS defaults;consumer override via `labels` prop\nconst DEFAULT_LABELS: Required<FileViewerLabels> = {\n zoomInput: '縮放比例',\n zoomMenu: '開啟縮放選單',\n infoToggleCollapse: '收合詳細資訊面板',\n infoToggleExpand: '展開詳細資訊面板',\n download: '下載檔案',\n close: '關閉檢視器',\n detailPanel: '檔案詳細資訊',\n detailsHeading: '詳細資訊',\n detailPanelClose: '關閉詳細資訊',\n descriptionPlaceholderReadOnly: '尚無說明',\n descriptionPlaceholderEdit: '為這個檔案加上說明…',\n fileInfoHeading: '檔案資訊',\n filmstripLabel: '檔案佇列',\n previousFile: '上一個檔案',\n nextFile: '下一個檔案',\n}\n\nexport interface FileViewerProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,\n 'onOpenChange' | 'tabIndex'\n > {\n files: FileInfo[]\n initialIndex?: number\n /** Controlled open state。與 `defaultOpen` 二擇一。 */\n open?: boolean\n /** Uncontrolled open 預設(2026-04-25 加,對齊 Dialog/Sheet/Popover dual-mode canonical)。 */\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** 當前索引(controlled);consumer 想自己控制 active file 時傳。不傳則 shell 管理。 */\n index?: number\n onIndexChange?: (index: number) => void\n /** 當前檔案 description 變化。consumer 負責持久化。readOnly 為 true 時不觸發。 */\n onDescriptionChange?: (fileId: string, description: string) => void\n /** true → InfoPanel 的 description textarea 為 readOnly。預設 false。 */\n readOnly?: boolean\n /** 顯示底部 filmstrip。預設 false;files.length < 2 時自動隱藏。 */\n showFilmstrip?: boolean\n /** 是否提供 download 按鈕。預設 true。 */\n allowDownload?: boolean\n /** 自訂 download 行為;未傳則用 anchor download attribute。 */\n onDownload?: (file: FileInfo) => void\n /** i18n labels override. Partial — merged with DS defaults. */\n labels?: FileViewerLabels\n}\n\nconst FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function FileViewer({\n files,\n initialIndex = 0,\n open: openProp,\n defaultOpen,\n onOpenChange,\n index: indexProp,\n onIndexChange,\n onDescriptionChange,\n readOnly = false,\n showFilmstrip = false,\n allowDownload = true,\n onDownload,\n labels: labelsOverride,\n className,\n onOpenAutoFocus,\n ...props\n}, ref) {\n const labels = React.useMemo(\n () => ({ ...DEFAULT_LABELS, ...labelsOverride }) satisfies Required<FileViewerLabels>,\n [labelsOverride],\n )\n // Open:受控/非受控雙模式鏡像(消費 DS useControllable;select-menu.tsx 2026-06-11 同修先例)。\n // 原本 keyboard shortcuts effect 與 index-reset effect 直接讀 `open` prop ——\n // uncontrolled(defaultOpen)場景 open === undefined,兩 effect 永遠不啟動(快捷鍵全失效\n // + 重開不重置 index),Toolbar X / backdrop click 的 onOpenChange?.(false) 也是 no-op。\n // 鏡像後 effect / close 都走 mirror:controlled 時純 passthrough,\n // uncontrolled 時 internal state 為準 + onOpenChange 僅通知。\n const [open, setOpen] = useControllable<boolean>({\n value: openProp,\n defaultValue: defaultOpen ?? false,\n onChange: onOpenChange,\n })\n const contentRef = React.useRef<HTMLDivElement | null>(null)\n const setContentRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n contentRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref],\n )\n\n // Index:uncontrolled fallback\n const [internalIndex, setInternalIndex] = React.useState(initialIndex)\n const activeIndex = indexProp ?? internalIndex\n\n const setIndex = React.useCallback(\n (next: number) => {\n const clamped = Math.max(0, Math.min(files.length - 1, next))\n if (indexProp === undefined) setInternalIndex(clamped)\n onIndexChange?.(clamped)\n },\n [files.length, indexProp, onIndexChange],\n )\n\n // 開啟時若 uncontrolled,重置為 initialIndex\n React.useEffect(() => {\n if (open && indexProp === undefined) {\n setInternalIndex(Math.max(0, Math.min(files.length - 1, initialIndex)))\n }\n }, [open, initialIndex, files.length, indexProp])\n\n // Info panel open state(shell own)\n const [infoOpen, setInfoOpen] = React.useState(false)\n // 2026-07-06 D3 perf:抽 useCallback 穩定 identity——InfoPanel 上了 React.memo(見上),\n // inline arrow 每 render 新 identity 會打穿 memo shallow-compare。\n const handleInfoToggle = React.useCallback(() => setInfoOpen((o) => !o), [])\n const handleInfoClose = React.useCallback(() => setInfoOpen(false), [])\n\n // Zoom state(shell own,renderer 消費 + 回報)\n const [zoom, setZoom] = React.useState(100)\n // 切換檔案時不再 setZoom(100)— 把「下一張該怎麼初始化 zoom」的決定權交給 renderer:\n // image-renderer 自己 watch file.url change → reset loaded → onLoad → 重 fit-page。\n // 原本 setZoom(100) 在 cache 命中(onLoad 沒 fire)時會卡 100% 不 fit(user 抓的 bug)。\n\n // Fit request(shell → renderer 指令;nonce 遞增讓重複同 fit 也觸發 renderer)\n const [fitRequest, setFitRequest] = React.useState<{ fit: ZoomFit; nonce: number } | null>(null)\n\n // Renderer capabilities(mount 時 renderer emit)\n const [capabilities, setCapabilities] = React.useState<FileRendererCapabilities>({\n zoom: false,\n })\n\n const file = files[activeIndex]\n const Renderer = file ? resolveRenderer(file) : null\n\n // Fit-to-* 下指令給 renderer,renderer 算 container/image 比例後透過 onZoomChange 回報\n const handleFit = React.useCallback((fit: ZoomFit) => {\n setFitRequest((prev) => ({ fit, nonce: (prev?.nonce ?? 0) + 1 }))\n }, [])\n\n // Download handler\n const handleDownload = React.useCallback(() => {\n if (!file) return\n if (onDownload) {\n onDownload(file)\n return\n }\n // 預設:anchor download(同源檔案有效;跨域由 consumer 提供 onDownload)\n const a = document.createElement('a')\n a.href = file.url\n a.download = file.name\n a.target = '_blank'\n a.rel = 'noopener'\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n }, [file, onDownload])\n\n // Keyboard shortcuts(focus 在 input / textarea 時不觸發)\n React.useEffect(() => {\n if (!open) return\n const handler = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement | null\n const tag = target?.tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || target?.isContentEditable) return\n\n if (e.key === 'ArrowLeft' && files.length > 1) {\n e.preventDefault()\n setIndex(activeIndex - 1)\n } else if (e.key === 'ArrowRight' && files.length > 1) {\n e.preventDefault()\n setIndex(activeIndex + 1)\n } else if (e.key === '+' || e.key === '=') {\n if (capabilities.zoom) {\n e.preventDefault()\n setZoom((z) => nextZoomIn(z))\n }\n } else if (e.key === '-') {\n if (capabilities.zoom) {\n e.preventDefault()\n setZoom((z) => nextZoomOut(z))\n }\n } else if (e.key === '0') {\n if (capabilities.zoom) {\n e.preventDefault()\n setZoom(100)\n }\n } else if (e.key === 'f' || e.key === 'F') {\n if (capabilities.zoom) {\n e.preventDefault()\n handleFit('fit-page')\n }\n } else if (e.key === 'i' || e.key === 'I') {\n e.preventDefault()\n setInfoOpen((o) => !o)\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [open, activeIndex, files.length, setIndex, capabilities.zoom, handleFit])\n\n // Arrows idle auto-hide(世界級 lightbox canonical:Google Photos / Dropbox / PhotoSwipe)\n // 滑鼠移入 viewport → 顯示箭頭;持續 2.5 秒無 mouse move → 自動淡出(對齊世界級行為)\n const [armVisible, setArmVisible] = React.useState(false)\n const idleTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const handleViewportMouseMove = React.useCallback(() => {\n setArmVisible(true)\n if (idleTimerRef.current) clearTimeout(idleTimerRef.current)\n idleTimerRef.current = setTimeout(() => setArmVisible(false), 2500)\n }, [])\n const handleViewportMouseLeave = React.useCallback(() => {\n setArmVisible(false)\n if (idleTimerRef.current) clearTimeout(idleTimerRef.current)\n }, [])\n React.useEffect(() => () => {\n if (idleTimerRef.current) clearTimeout(idleTimerRef.current)\n }, [])\n\n if (!file || !Renderer) {\n // files 為空或 index 超界 — 不渲染\n return null\n }\n\n const showFilmstripResolved = showFilmstrip && files.length > 1\n const showArrows = files.length > 1\n\n // Root 永遠餵 mirror(Radix 視角 controlled);uncontrolled 的 defaultOpen 由\n // useControllable internal state 承載,Radix Esc / dismiss 經 setOpen 同步回 mirror。\n return (\n <DialogPrimitive.Root open={open} onOpenChange={setOpen}>\n <DialogPrimitive.Portal>\n {/* Overlay — FileViewer 固定深色氛圍,與 Dialog 共用 bg-overlay。\n **data-theme=\"dark\"**(2026-04-30):Overlay 在 Portal 內、是 Content 的 sibling,\n 不繼承 Content 內層的 dark 主題 → `--overlay` 默認 resolve 成 light theme α45 黑。\n FileViewer 永遠 dark(line 899 outer),mask 也須 dark token = α65 黑(更深)\n 才語意一致。同 DropdownMenuContent Portal 處理(line 245)。 */}\n <DialogPrimitive.Overlay\n data-theme=\"dark\"\n className={cn(\n 'fixed inset-0 z-50 bg-overlay',\n surfaceMotion,\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n )}\n />\n <DialogPrimitive.Content\n ref={setContentRef}\n {...props}\n tabIndex={-1}\n className={cn(\n // Edge-to-edge fullscreen,無 inset / 無 radius(與一般 Dialog 差別的所在)\n 'fixed inset-0 z-50 outline-none',\n surfaceMotion,\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n className,\n )}\n // FileViewer 是 fullscreen content-consumption dialog:開啟即把焦點放到 shell,\n // 讓 Radix FocusScope 立即接管、但不誤觸第一個 toolbar control/Tooltip。\n // Consumer handler 可先取消;未取消時由本元件阻止 Radix default 並聚焦 Content。\n onOpenAutoFocus={(event) => {\n onOpenAutoFocus?.(event)\n if (event.defaultPrevented) return\n event.preventDefault()\n contentRef.current?.focus({ preventScroll: true })\n }}\n >\n {/* 鎖 dark subtree。Density 繼承 page(不另設 data-density)。\n header 高度透過 `--chrome-header-height` 自動 density-aware(md=48 / lg=56)。\n ── Q1 mask 透明度(2026-04-30)──\n outer **不**設 bg → Overlay(`bg-overlay` α45/α65)透出 image 周圍區域,\n 對齊 Notion / Dropbox / Slack lightbox idiom 跟 Dialog mask 同 token 一致。\n chrome(Toolbar / Filmstrip / InfoPanel)各自 `bg-surface-raised` opaque dark\n (對齊 Apple Photos / Drive lightbox 派 — chrome opaque vs mask 半透明,\n 清楚區分 backdrop click 區 vs 互動區)。\n **不**用 bg-surface — dark mode `--surface = white α8` 半透明,outer 透明時\n 無 dark backdrop 撐 → 視覺洗白。 */}\n <div\n data-theme=\"dark\"\n className=\"w-full h-full flex flex-col text-foreground\"\n >\n {/* Accessible title — 視覺隱藏但 screen reader 讀得到 */}\n <DialogPrimitive.Title className=\"sr-only\">\n 檔案檢視器:{file.name}\n </DialogPrimitive.Title>\n\n <Toolbar\n file={file}\n capabilities={capabilities}\n zoom={zoom}\n onZoomChange={setZoom}\n onFit={handleFit}\n infoOpen={infoOpen}\n onInfoToggle={handleInfoToggle}\n onDownload={handleDownload}\n allowDownload={allowDownload}\n onClose={() => setOpen(false)}\n labels={labels}\n />\n\n {/* 主區:Viewport + 可選 InfoPanel(右側)\n Arrows visibility = armVisible(state)控制:mouse move 顯示 / 2.5s idle 隱藏 / mouse leave 立即隱藏\n 對齊 Google Photos / Dropbox lightbox / PhotoSwipe world-class canonical */}\n <div className=\"flex-1 min-h-0 flex\">\n <div\n className=\"relative flex-1 min-w-0\"\n onMouseMove={handleViewportMouseMove}\n onMouseLeave={handleViewportMouseLeave}\n // Backdrop click-to-close(對齊 Google Drive / Dropbox lightbox / Apple Photos canonical):\n // 點擊 image 周圍的暗色 backdrop 區關閉,跟 modal mask 同 idiom。\n //\n // 為何 geometric check 而非 closest('img')?\n // react-zoom-pan-pinch 的 TransformComponent 是 wrapper div 蓋在 image 之上(absorb\n // pan/zoom events),click target 是該 wrapper div 不是 <img>。closest('img') 檢查\n // ancestor 永遠 false。改 geometric check:看 click 座標是否落在 <img> 視覺 rect 內。\n onClick={(e) => {\n const t = e.target as HTMLElement\n // 互動元素(side arrows / chrome buttons 透過冒泡)→ 不關\n if (t.closest('button, [role=\"button\"]')) return\n // 點到 image 視覺範圍 → 不關(image 本體 click ≠ close)\n const img = e.currentTarget.querySelector('img')\n if (img) {\n const r = img.getBoundingClientRect()\n if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) return\n }\n // 否則 = 點到 backdrop(outer 不設 bg,半透明 bg-overlay 透出 — 見 Q1 註解)→ close\n setOpen(false)\n }}\n >\n {showArrows && activeIndex > 0 && (\n <div\n className={cn(\n 'absolute left-[var(--layout-space-loose)] top-1/2 -translate-y-1/2 z-10',\n 'transition-opacity duration-150 motion-reduce:duration-0',\n // armVisible state 控制,或 focus-within 時 a11y 強制顯示\n armVisible ? 'opacity-100' : 'opacity-0 pointer-events-none',\n 'focus-within:opacity-100 focus-within:pointer-events-auto',\n )}\n >\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n startIcon={ChevronLeft}\n aria-label={labels.previousFile}\n onClick={() => setIndex(activeIndex - 1)}\n />\n </div>\n )}\n <div className=\"w-full h-full\">\n {/* Suspense 兜 React.lazy renderer(ImageRenderer)chunk 載入。fallback=null\n = 既有 loading 行為——spec「Loading」段:shell 不提供全頁 loading state,\n loading 由 renderer 自理;chunk 載入期間的空 viewport 與 <img> 載入期間視覺一致。 */}\n <React.Suspense fallback={null}>\n <Renderer.component\n file={file}\n zoom={zoom}\n onZoomChange={setZoom}\n fitRequest={fitRequest}\n onCapabilitiesChange={setCapabilities}\n />\n </React.Suspense>\n </div>\n {showArrows && activeIndex < files.length - 1 && (\n <div\n className={cn(\n 'absolute right-[var(--layout-space-loose)] top-1/2 -translate-y-1/2 z-10',\n 'transition-opacity duration-150 motion-reduce:duration-0',\n armVisible ? 'opacity-100' : 'opacity-0 pointer-events-none',\n 'focus-within:opacity-100 focus-within:pointer-events-auto',\n )}\n >\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n startIcon={ChevronRight}\n aria-label={labels.nextFile}\n onClick={() => setIndex(activeIndex + 1)}\n />\n </div>\n )}\n </div>\n {infoOpen && (\n <InfoPanel\n file={file}\n readOnly={readOnly}\n onDescriptionChange={onDescriptionChange}\n onClose={handleInfoClose}\n labels={labels}\n />\n )}\n </div>\n\n {showFilmstripResolved && (\n <Filmstrip\n files={files}\n activeIndex={activeIndex}\n onSelect={setIndex}\n labels={labels}\n />\n )}\n </div>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n )\n})\nFileViewer.displayName = 'FileViewer'\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 fileViewerMeta = {\n component: 'FileViewer',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 'selected' = 縮圖列當前頁持續選中(bg-neutral-selected);'active' 移除 — 全檔無按壓視覺\n // (工具列按壓屬內嵌 Button meta;2026-07-07 詞彙統一 DS-wide 交叉掃補修)。\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-muted', 'bg-neutral-selected', 'bg-overlay', 'bg-surface-raised'],\n fg: ['text-fg-muted', 'text-foreground'],\n ring: ['ring-primary', 'ring-ring'],\n },\n} as const\n\nexport { FileViewer }\nexport type { FileInfo, FileRenderer, FileRendererCapabilities, FileRendererProps } from './file-viewer-types'\n"],"names":["FileIcon","XIcon","InfoPanel","Filmstrip","FileViewer"],"mappings":"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"}
1
+ {"version":3,"file":"file-viewer.js","sources":["../../../src/components/FileViewer/file-viewer.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — composite 拼裝(Toolbar / ZoomInput / InfoPanel / Filmstrip + Dialog shell + renderer registry);拆檔會把 useState/useEffect/key handler 跨檔同步過於複雜\nimport * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport {\n X as XIcon,\n Download,\n Info,\n ChevronLeft,\n ChevronRight,\n ChevronDown,\n Plus,\n Minus,\n File as FileIcon,\n FileText,\n} from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { OVERLAY_SIDE_OFFSET } from '@/design-system/tokens/elevation/overlay-geometry'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ButtonDivider } from '@/design-system/components/Button/button-group'\nimport { Input } from '@/design-system/components/Input/input'\nimport { Empty } from '@/design-system/components/Empty/empty'\nimport { AspectRatio } from '@/design-system/components/AspectRatio/aspect-ratio'\nimport { Textarea } from '@/design-system/components/Textarea/textarea'\nimport { Field, FieldLabel } from '@/design-system/components/Field/field'\nimport { DescriptionList, DescriptionItem } from '@/design-system/components/DescriptionList/description-list'\nimport { ItemInlineActionButton } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { ChromeHeader } from '@/design-system/patterns/header-canonical/chrome-header'\nimport { ScrollArea } from '@/design-system/components/ScrollArea/scroll-area'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\nimport { ARROW_BUTTON_WIDTH,\n useScrollEdges,\n useScrollByPage,\n buildFadeMask,\n OverflowScrollArrow,\n} from '@/design-system/patterns/horizontal-overflow/horizontal-overflow'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport { canRenderImage } from './can-render-image'\nimport type {\n FileInfo,\n FileRenderer,\n FileRendererCapabilities,\n FileRendererProps,\n} from './file-viewer-types'\nimport { surfaceMotion } from \"@/design-system/tokens/motion/overlay-motion\"\n\n/**\n * FileViewer — 可延伸的網頁檔案 preview shell(modal fullscreen)\n *\n * ── 定位 ──\n * 公開、composite 元件。consumer 傳 `files`,FileViewer 處理 overlay / toolbar /\n * keyboard / filmstrip / info panel 一切 chrome;檔案本體由 renderer registry\n * 按 file MIME 決定誰渲染(MVP 內建 ImageRenderer + FallbackRenderer)。\n *\n * ── 實作基礎 ──\n * 自建 composite,消費 DS primitives:\n * - Radix DialogPrimitive(焦點 trap / Esc / modal 隔離靠 hideOthers aria-hidden — react-dialog@1.1.x 不輸出 aria-modal,保有 shadcn 結構優勢)\n * - `<Empty>` / `<Button>` / `<Input size=\"sm\" autoWidth>`(ZoomInput,default variant)/ `<AspectRatio>` / `<Textarea>` / `<DropdownMenu>`\n * - `patterns/horizontal-overflow`(filmstrip 溢出捲動)\n * 不用 DS 的 `<Dialog>` wrapper:因為 FileViewer 需要 edge-to-edge fullscreen\n * (無 viewport inset / 無 rounded-lg / 無 maxWidth),Dialog 的這些預設都要覆寫。\n * 直接消費 Radix primitive 讓 shell 擁有完整 layout 控制權。\n *\n * ── Layout Family ──\n * 非 Family 1/2/3/4 — composite / multi-region(Toolbar / Viewport / Filmstrip +\n * 可選 InfoPanel)。見 `file-viewer.spec.md`「Layout Family」段。\n *\n * ── Extensibility ──\n * `registerFileRenderer(renderer)` 註冊新 renderer;shell 按註冊順序 iterate,\n * 第一個 `canRender(file)` 回 true 的渲染。FallbackRenderer 永遠兜底(未知檔案\n * 類型顯示 icon + 檔名 + download)。\n */\n\n// ─── Renderer Registry ────────────────────────────────────────────────────────\n\n/**\n * Fallback renderer — 無 renderer 能處理時兜底。\n * 顯示 Empty 佈局:icon + 檔名 + 「請下載檢視」提示。\n */\nconst FallbackRenderer: React.FC<FileRendererProps> = ({ file, onCapabilitiesChange }) => {\n // Contract(file-viewer-types.ts `onCapabilitiesChange` doc):「第一個 renderer mount\n // 時必須 emit」。fallback 不支援 zoom → emit { zoom: false };不 emit 的話 shell 會沿用\n // 上一個 renderer 的 capability(image → 未知檔切換時 toolbar 殘留 zoom controls,\n // `+`/`-`/`0`/`F` 快捷鍵也會對不支援 zoom 的檔案生效)。同 image-renderer.tsx mount emit pattern。\n React.useEffect(() => {\n onCapabilitiesChange({ zoom: false })\n }, [onCapabilitiesChange])\n\n return (\n <div className=\"w-full h-full flex items-center justify-center p-8\">\n <Empty\n icon={FileText}\n title={file.name}\n description={`無法在瀏覽器中預覽此檔案類型(${file.mimeType || 'unknown'})。請下載後檢視。`}\n />\n </div>\n )\n}\n\nconst fallbackRenderer: FileRenderer = {\n id: 'fallback',\n canRender: () => true,\n component: FallbackRenderer,\n}\n\n// 2026-07-06 D3 bundle fix:ImageRenderer 改 React.lazy 動態 import——FileViewer 是\n// rare-use modal overlay,react-zoom-pan-pinch(~12KB gz)不該在 viewer 從未打開時就進\n// consumer 首屏 bundle。canRenderImage(registry 判斷 + Filmstrip 分流需同步呼叫)已拆至\n// ./can-render-image 保持靜態 import。\nconst LazyImageRenderer = React.lazy(() =>\n import('./image-renderer').then((m) => ({ default: m.ImageRenderer })),\n)\n\nconst imageRenderer: FileRenderer = {\n id: 'image',\n canRender: canRenderImage,\n component: LazyImageRenderer,\n}\n\n// Registry 是 module-singleton:新 renderer 透過 registerFileRenderer 加入。\n// Fallback 永遠最後(兜底),因此用陣列第二段存放。\nconst userRegistered: FileRenderer[] = []\n\nexport function registerFileRenderer(renderer: FileRenderer): void {\n // 去重:同 id 則覆寫\n const existingIdx = userRegistered.findIndex((r) => r.id === renderer.id)\n if (existingIdx >= 0) {\n userRegistered[existingIdx] = renderer\n } else {\n userRegistered.push(renderer)\n }\n}\n\nfunction resolveRenderer(file: FileInfo): FileRenderer {\n // 先查 user registered,再 built-in,最後 fallback\n for (const r of userRegistered) {\n if (r.canRender(file)) return r\n }\n if (imageRenderer.canRender(file)) return imageRenderer\n return fallbackRenderer\n}\n\n// ─── Zoom presets ─────────────────────────────────────────────────────────────\n\ntype ZoomFit = 'fit-width' | 'fit-page'\n\nconst ZOOM_PRESETS: number[] = [10, 25, 50, 75, 100, 125, 150, 200, 400]\n// i18n-allow-block: DS defaults for zoom fit menu;consumer override via `labels.zoomFitOptions` (future) or fork\nconst ZOOM_FIT_OPTIONS: { value: ZoomFit; label: string }[] = [\n { value: 'fit-width', label: 'Fit to width' },\n { value: 'fit-page', label: 'Fit to page' },\n]\n\nfunction nextZoomIn(current: number): number {\n for (const p of ZOOM_PRESETS) {\n if (p > current) return p\n }\n return ZOOM_PRESETS[ZOOM_PRESETS.length - 1]\n}\nfunction nextZoomOut(current: number): number {\n for (let i = ZOOM_PRESETS.length - 1; i >= 0; i--) {\n if (ZOOM_PRESETS[i] < current) return ZOOM_PRESETS[i]\n }\n return ZOOM_PRESETS[0]\n}\n\n// ─── ZoomInput ────────────────────────────────────────────────────────────────\n\ninterface ZoomInputProps {\n value: number\n onChange: (next: number) => void\n onFit: (fit: ZoomFit) => void\n labels: Pick<Required<FileViewerLabels>, 'zoomInput' | 'zoomMenu'>\n}\n\n/**\n * ZoomInput — [−] [% input(default variant + autoWidth)with ⌄ menu trigger] [+]\n *\n * 世界級對照:Figma zoom control / Adobe Acrobat / Google Slides zoom。\n *\n * ── 消費 DS primitive ──\n * - `<Button>` iconOnly size=\"sm\" 作 ±按鈕\n * - `<Input size=\"sm\" autoWidth>` 作 %輸入(Toolbar inline editing canonical;autoWidth 隨文字寬)\n * - Input `endSlot` escape hatch 包 `<DropdownMenuTrigger asChild>` + chevron 觸發 DropdownMenu\n * - `<DropdownMenu>` 作 preset + fit 選單(取代原先 Popover + 手刻 button list)\n *\n * ── 為什麼 inline(不抽獨立 primitive)──\n * 目前只 FileViewer 消費;MVP 階段遵循 YAGNI。當 PDF / Video viewer 也需要相同\n * primitive 時,再依「建立前必查既有 pattern」原則從 FileViewer 抽出升級。\n */\nconst ZoomInput: React.FC<ZoomInputProps> = ({ value, onChange, onFit, labels }) => {\n const [draft, setDraft] = React.useState<string>(`${value}%`)\n const [menuOpen, setMenuOpen] = React.useState(false)\n\n React.useEffect(() => {\n setDraft(`${value}%`)\n }, [value])\n\n const commitDraft = () => {\n const parsed = parseInt(draft.replace(/[^0-9]/g, ''), 10)\n if (Number.isFinite(parsed) && parsed > 0) {\n // 限 10–400 範圍,對齊 ImageRenderer MIN_SCALE/MAX_SCALE\n const clamped = Math.min(400, Math.max(10, parsed))\n onChange(clamped)\n setDraft(`${clamped}%`)\n } else {\n setDraft(`${value}%`)\n }\n }\n\n return (\n // zoom group = toolbar 按鈕群組,`gap-2`(8px)對齊本 DS 按鈕 gap canonical。\n <div className=\"inline-flex items-center gap-2\">\n {/* 縮小 */}\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={Minus}\n aria-label=\"縮小\"\n disabled={value <= 10}\n onClick={() => onChange(nextZoomOut(value))}\n />\n\n {/* % Input + chevron 內嵌為 endSlot(ItemInlineActionButton 作 DropdownMenuTrigger):\n — Input body 可自由打字(chevron 是 Input 內部 element,body 區域 click 不觸發 menu)\n — Chevron 是 inline action,同時是 DropdownMenuTrigger → menu 精確 anchor 在 chevron 下方\n — 靠 Radix asChild + ItemInlineActionButton:視覺是 Input + endAction,行為是 chevron-as-trigger\n — 完全對齊 user AR:「只有 inline action 能觸發選單,menu 對齊 inline action」 */}\n <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>\n {/* maxLength 限縮(2026-07-09 user 要求「找合理最大值避免無限輸入」):autoWidth 下輸入框寬度隨字數長,\n 無上限則可無限打字撐爆 toolbar。max 合法值「400%」= 4 字(對齊 ZOOM_PRESETS / ImageRenderer MAX_SCALE 400;\n commitDraft 另 clamp 10–400),留 1 字編輯緩衝 → maxLength={5}。 */}\n <Input\n size=\"sm\"\n autoWidth\n maxLength={5}\n aria-label={labels.zoomInput}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commitDraft}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n commitDraft()\n ;(e.target as HTMLInputElement).blur()\n }\n }}\n className=\"text-center tabular-nums\"\n endSlot={\n <DropdownMenuTrigger asChild>\n <ItemInlineActionButton\n icon={ChevronDown}\n aria-label={labels.zoomMenu}\n size=\"sm\"\n overlayTrigger\n />\n </DropdownMenuTrigger>\n }\n />\n {/* data-theme=\"dark\":DropdownMenuContent 走 Portal 到 document body 外,\n 不繼承 FileViewer 外層 data-theme=\"dark\",需顯式打 dark 讓選單跟 chrome 一致。\n **加 bg-surface-raised 強制用 dark token**(純 data-theme attr 在 Portal 不夠,\n Tailwind 條件 class + CSS variable 都要一起帶) */}\n <DropdownMenuContent\n align=\"end\"\n sideOffset={OVERLAY_SIDE_OFFSET} // 2026-07-04:M17 — elevation.spec「primitive default 必 import 該 const」\n // minWidth 對齊 trigger(Input autoWidth),menu 寬度 fit-content 更貼近觸發點視覺中心\n className=\"min-w-[9rem] w-auto bg-surface-raised text-foreground border-divider\"\n data-theme=\"dark\"\n >\n {/* 內層 data-theme 再覆蓋一次 — 確保 DropdownMenuItem children 都 resolve dark token */}\n <div data-theme=\"dark\" className=\"contents\">\n {ZOOM_FIT_OPTIONS.map((opt) => (\n <DropdownMenuItem\n key={opt.value}\n onSelect={() => onFit(opt.value)}\n >\n {opt.label}\n </DropdownMenuItem>\n ))}\n <DropdownMenuSeparator />\n {ZOOM_PRESETS.map((p) => {\n const selected = p === value\n return (\n <DropdownMenuItem\n key={p}\n onSelect={() => onChange(p)}\n data-state={selected ? 'checked' : undefined}\n className={cn(\n 'tabular-nums',\n selected && 'bg-neutral-selected',\n )}\n >\n {p}%\n </DropdownMenuItem>\n )\n })}\n </div>\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* 放大 */}\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={Plus}\n aria-label=\"放大\"\n disabled={value >= 400}\n onClick={() => onChange(nextZoomIn(value))}\n />\n </div>\n )\n}\nZoomInput.displayName = 'ZoomInput'\n\n// ─── Toolbar ──────────────────────────────────────────────────────────────────\n\ninterface ToolbarProps {\n file: FileInfo\n capabilities: FileRendererCapabilities\n zoom: number\n onZoomChange: (z: number) => void\n onFit: (fit: ZoomFit) => void\n infoOpen: boolean\n onInfoToggle: () => void\n onDownload?: () => void\n allowDownload: boolean\n onClose: () => void\n labels: Required<FileViewerLabels>\n}\n\nconst Toolbar: React.FC<ToolbarProps> = ({\n file,\n capabilities,\n zoom,\n onZoomChange,\n onFit,\n infoOpen,\n onInfoToggle,\n onDownload,\n allowDownload,\n onClose,\n labels,\n}) => {\n return (\n <ChromeHeader\n // Density:繼承 page(2026-06-15 canonical)。原 lockDensity=\"lg\" 只鎖在 header、沒鎖 body →\n // InfoPanel header(px-loose@lg=24)與其 body(px-loose@page=16)左緣不對齊(圖二 bug)。移除 lock\n // → header + body 全繼承 page density,左緣對齊;FileViewer 全 surface 同一密度(non-special)。\n className={cn(\n // Chrome layer — `bg-surface-raised` 對齊 token semantic「遮蓋型浮層必須不透明」。\n // FileViewer 整體是 overlay,chrome 屬其 raised surface(同 DropdownMenuContent line 244)。\n // 不用 bg-surface(dark = white α8 半透明,outer 透明時失去 backdrop 洗白)。\n // 不用 bg-canvas(那是「頁面最底層」semantic,chrome 不是 page)。\n // ChromeHeader 自帶 flex/items-center/gap-2/shrink-0/h-chrome-header-height/border-b/px-loose\n 'bg-surface-raised',\n )}\n >\n {/* 檔名(左,佔據可用寬度,ellipsis)—— file-type icon 代表檔名的意象(這是什麼檔),\n 對齊 CLAUDE.md「icon 代表 label 意象時與 label 同色」原則:icon 走 text-foreground\n 不走 text-fg-muted(後者是裝飾性 / 輔助 icon 的色階) */}\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n <FileIcon size={16} className=\"text-foreground shrink-0\" aria-hidden />\n <span\n className=\"text-body-lg text-foreground truncate\"\n title={file.name}\n >\n {file.name}\n </span>\n </div>\n\n {/* 按鈕順序 canonical:zoom → info → download → close(影響力遞增)\n action-bar 三分區:zoom(data op)/ info+download(action group)/ close(dismiss)\n dismiss 前分隔線 = action-bar「dismiss 跟動作分群」canonical\n\n ── gap-2 canonical(2026-04-21 follow-up)──\n 按鈕間距 **8px**(gap-2),對齊 Dialog footer `gap-2` / CLAUDE 按鈕間距 SSOT。\n zoom group(ZoomInput)內部同樣是 gap-2(見 `ZoomInput` L197),\n 與這裡 action-group-to-action-group 的 gap-2 一致。 */}\n <div className=\"flex items-center gap-2 shrink-0\">\n {capabilities.zoom && (\n <>\n {/* Zoom group:-/%/+/▼ 屬同類「縮放」操作,群組並在右側加分隔線跟其他動作分群 */}\n <ZoomInput value={zoom} onChange={onZoomChange} onFit={onFit} labels={labels} />\n {/* zoom group → next action group divider。幾何 SSOT = action-bar.spec.md「分隔線幾何」:\n 線長由 ButtonDivider 依「該列最高元件 − inset」自動算,不再手寫高度(舊 h-6 是寫死值,\n 與 DS 主要實作的 ButtonDivider 形成兩套機制)。*/}\n <ButtonDivider />\n </>\n )}\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={Info}\n aria-label={infoOpen ? labels.infoToggleCollapse : labels.infoToggleExpand}\n pressed={infoOpen}\n onClick={onInfoToggle}\n />\n {allowDownload && (\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={Download}\n aria-label={labels.download}\n onClick={onDownload}\n />\n )}\n {/* action-bar canonical:dismiss 前加分隔線跟其他動作分群(info/download = action group,\n close = dismiss group)。幾何同上,由 ButtonDivider 自動算。*/}\n <ButtonDivider />\n {/* Close X 走 dismiss canonical(`<Button iconOnly dismiss />`)——對齊 CLAUDE.md\n `button.spec.md`「Dismiss 視覺類」+ `patterns/element-anatomy/item-anatomy.spec.md`\n 「Dismiss canonical」:chrome corner close X = Button dismiss,不是 Inline Action。 */}\n <Button\n iconOnly\n dismiss\n size=\"sm\"\n data-dismiss\n startIcon={XIcon}\n aria-label={labels.close}\n onClick={onClose}\n />\n </div>\n </ChromeHeader>\n )\n}\n\n// ─── InfoPanel ────────────────────────────────────────────────────────────────\n\ninterface InfoPanelProps {\n file: FileInfo\n readOnly: boolean\n onDescriptionChange?: (fileId: string, description: string) => void\n onClose: () => void\n labels: Required<FileViewerLabels>\n}\n\nfunction formatBytes(bytes: number | undefined): string | undefined {\n if (bytes == null) return undefined\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n}\n\n// React.memo(2026-07-06 D3):同 Filmstrip——wheel-zoom 每 tick 全 chrome re-render,InfoPanel\n// 開啟時(Field/Textarea/DescriptionList subtree)白付。onClose 走 shell handleInfoClose\n// useCallback;file(files[activeIndex] identity 穩定)/readOnly/onDescriptionChange/labels 本已穩定。\nconst InfoPanel = React.memo(function InfoPanel({\n file,\n readOnly,\n onDescriptionChange,\n onClose,\n labels,\n}: InfoPanelProps) {\n const [draft, setDraft] = React.useState(file.description ?? '')\n\n React.useEffect(() => {\n setDraft(file.description ?? '')\n }, [file.id, file.description])\n\n const commit = () => {\n if (readOnly) return\n if (draft !== (file.description ?? '')) {\n onDescriptionChange?.(file.id, draft)\n }\n }\n\n const sizeText = formatBytes(file.size)\n\n return (\n <aside\n className={cn(\n // Chrome — bg-surface-raised 同 Toolbar / Filmstrip(token semantic「遮蓋型浮層」)\n 'w-80 shrink-0 flex flex-col bg-surface-raised border-l border-divider',\n 'h-full',\n )}\n aria-label={labels.detailPanel}\n >\n {/* Panel header — 與 Toolbar 同 ChromeHeader,繼承 page density(2026-06-15:移除 lockDensity,\n 修 header(原 px-loose@lg=24)與 body(px-loose@page=16)左緣不對齊 = 圖二 bug;全 surface 同密度)。 */}\n <ChromeHeader className=\"justify-between\">\n <h3 className=\"text-body-lg font-medium text-foreground\">{labels.detailsHeading}</h3>\n {/* InfoPanel close 走 dismiss canonical `<Button iconOnly dismiss />`,對齊 button.spec.md\n 「Dismiss 視覺類」+ inline-action.spec.md「Dismiss canonical — X close only」。 */}\n <Button\n iconOnly\n dismiss\n size=\"sm\"\n data-dismiss\n startIcon={XIcon}\n aria-label={labels.detailPanelClose}\n onClick={onClose}\n />\n </ChromeHeader>\n\n {/* Panel body — header(shrink-0)上常駐 + body 走 ScrollArea(高度小時內容可捲動)。\n padding 對齊 layoutSpace v6 規則 4「bounded region → 容器底(無 action buttons)= loose」。\n gap 對齊 v6 規則 3「跨範疇 parallel = loose」(說明 vs 檔案資訊兩個獨立 functional sections,\n 屬「跨範疇 + 不相關」)— 從 gap-4 寫死改為 token-aware loose。 */}\n <ScrollArea className=\"flex-1 min-h-0\">\n <div className={cn(\n 'flex flex-col gap-[var(--layout-space-loose)]',\n 'px-[var(--layout-space-loose)]',\n 'pt-[var(--layout-space-tight)] pb-[var(--layout-space-loose)]',\n )}>\n {/* 說明 — 用 DS Field + FieldLabel + Textarea(2026-04-20 B12 決策:\n FileViewer 一律消費 DS Field 家族,不手刻 `<span>label` + raw control) */}\n <Field>\n <FieldLabel>說明</FieldLabel>\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onBlur={commit}\n readOnly={readOnly}\n placeholder={readOnly ? labels.descriptionPlaceholderReadOnly : labels.descriptionPlaceholderEdit}\n rows={5}\n />\n </Field>\n\n {/* 檔案資訊 — 用 DS DescriptionList horizontal + divided(Google Drive /\n Notion file info panel 模式):\n - section header 用 FieldLabel 同款 typography 保視覺一致\n - DescriptionList orientation=\"horizontal\" divided 提供 row 下底線\n 對齊格線,key 長度不一也易讀\n - 不再手刻 dl/dt/dd — canonical 由 DS primitive own */}\n {/* heading → first-item gap = item → item gap(Gestalt proximity,見 description-list.spec.md) */}\n <div className=\"flex flex-col gap-[var(--layout-space-tight)]\">\n <span className=\"text-body font-normal text-foreground\">{labels.fileInfoHeading}</span>\n <DescriptionList orientation=\"horizontal\" divided>\n <DescriptionItem label=\"檔名\">{file.name}</DescriptionItem>\n <DescriptionItem label=\"類型\">{file.mimeType || '-'}</DescriptionItem>\n {sizeText && (\n <DescriptionItem label=\"大小\">\n <span className=\"tabular-nums\">{sizeText}</span>\n </DescriptionItem>\n )}\n {file.metadata &&\n Object.entries(file.metadata).map(([k, v]) => (\n <DescriptionItem key={k} label={k}>{String(v)}</DescriptionItem>\n ))}\n </DescriptionList>\n </div>\n </div>\n </ScrollArea>\n </aside>\n )\n})\n\n// ─── Filmstrip ────────────────────────────────────────────────────────────────\n\ninterface FilmstripProps {\n files: FileInfo[]\n activeIndex: number\n onSelect: (index: number) => void\n labels: Pick<Required<FileViewerLabels>, 'filmstripLabel'>\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst THUMB_SIZE = 64 // px, 固定\n\n// React.memo(2026-07-06 D3):wheel-zoom 每 tick(trackpad 60Hz+)setZoom → shell 全 chrome\n// re-render,Filmstrip(N thumbs × cn + buildFadeMask)是最貴的無關 subtree。props 已全穩定\n// (files=prop / activeIndex=primitive / onSelect=setIndex useCallback / labels=useMemo)→ memo 真命中。\nconst Filmstrip = React.memo(function Filmstrip({ files, activeIndex, onSelect, labels }: FilmstripProps) {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n const scrollByPage = useScrollByPage(scrollRef)\n const maskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: ARROW_BUTTON_WIDTH }) // 2026-07-04:消 SSOT 硬寫(spec 禁止重新定義 ARROW_BUTTON_WIDTH)\n\n // 切換當前檔案時,自動 scroll 讓 active thumb 可見\n React.useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n const active = el.querySelector<HTMLButtonElement>(`[data-thumb-index=\"${activeIndex}\"]`)\n active?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' })\n }, [activeIndex, scrollRef])\n\n return (\n <div\n className={cn(\n // Chrome — bg-surface-raised 同 Toolbar / InfoPanel(token semantic「遮蓋型浮層」)\n 'relative shrink-0 h-24 bg-surface-raised border-t border-divider',\n 'flex items-center',\n 'px-[var(--layout-space-loose)]',\n )}\n >\n {canScroll && !atStart && (\n <OverflowScrollArrow direction=\"left\" onClick={() => scrollByPage('left')} />\n )}\n <div\n ref={scrollRef}\n className={cn(\n 'flex items-center',\n // 刻意隱藏 native scrollbar + 用 fade-mask(horizontal-overflow pattern)\n 'scrollbar-none overflow-x-auto overflow-y-hidden h-full py-2',\n 'w-full',\n )}\n style={{\n maskImage,\n WebkitMaskImage: maskImage,\n }}\n >\n {/* 內層 wrapper:mx-auto 讓 thumbs 在少量時水平置中,多量溢出時 mx-auto = 0 自然轉 scroll。\n gap-[var(--layout-space-tight)] 走 DS density-aware token(不用 raw gap-1)——\n 世界級 idiom:Google Drive / Dropbox / Notion file preview 的 filmstrip 都是\n 少量置中 / 多量靠 start scroll。\n ARIA role=\"group\"(非 tablist):filmstrip 是「選圖導航」不是「切換 N 個 tabpanel」——\n lightbox 縮圖膠卷只有單一 viewport,不符 W3C tabs pattern(tab MUST 對應自己的 tabpanel + roving tabindex)。\n 對齊 APG grouped-carousel + PhotoSwipe / codeaccessible.com lightbox 共識:group + button + aria-current。 */}\n <div\n role=\"group\"\n aria-label={labels.filmstripLabel}\n className=\"flex items-center gap-[var(--layout-space-tight)] mx-auto shrink-0\"\n >\n {files.map((file, i) => {\n const active = i === activeIndex\n const isImage = canRenderImage(file)\n const ext = file.name.split('.').pop()?.toUpperCase() ?? '檔'\n return (\n <button\n key={file.id}\n type=\"button\"\n aria-current={active ? 'true' : undefined}\n aria-label={`${i + 1} / ${files.length}:${file.name}`}\n data-thumb-index={i}\n onClick={() => onSelect(i)}\n className={cn(\n 'shrink-0 rounded-md bg-muted overflow-hidden',\n 'outline-none focus-visible:ring-2 focus-visible:ring-ring',\n 'transition-shadow duration-150',\n active\n ? 'ring-2 ring-primary'\n : 'ring-1 ring-border hover:ring-border-hover',\n )}\n style={{ width: THUMB_SIZE, height: THUMB_SIZE }}\n >\n <AspectRatio ratio={1} className=\"w-full h-full\">\n {isImage ? (\n <img\n src={file.url}\n alt=\"\"\n aria-hidden\n className=\"w-full h-full object-cover\"\n draggable={false}\n />\n ) : (\n <div className=\"w-full h-full flex flex-col items-center justify-center gap-0.5\">\n <FileText size={20} className=\"text-fg-muted\" aria-hidden />\n <span className=\"text-footnote text-fg-muted font-medium\">{ext}</span>\n </div>\n )}\n </AspectRatio>\n </button>\n )\n })}\n </div>\n </div>\n {canScroll && !atEnd && (\n <OverflowScrollArrow direction=\"right\" onClick={() => scrollByPage('right')} />\n )}\n </div>\n )\n})\n\n// ─── FileViewer (shell) ───────────────────────────────────────────────────────\n\n/**\n * i18n-able labels for FileViewer chrome / controls.\n * All keys are optional — defaults are CJK (see `DEFAULT_LABELS`).\n * Consumer typically spreads partial override:\n * `<FileViewer labels={{ close: 'Close', download: 'Download' }} />`\n */\n// code-quality-allow: dead-export — public API surface — consumer-exposed for future use\nexport interface FileViewerLabels {\n /** Zoom input ARIA label */\n zoomInput?: string\n /** Zoom menu trigger ARIA label */\n zoomMenu?: string\n /** Info panel toggle button — shown when panel is OPEN */\n infoToggleCollapse?: string\n /** Info panel toggle button — shown when panel is CLOSED */\n infoToggleExpand?: string\n /** Download button ARIA label */\n download?: string\n /** Close viewer button ARIA label */\n close?: string\n /** InfoPanel outer aside ARIA label */\n detailPanel?: string\n /** InfoPanel heading text */\n detailsHeading?: string\n /** InfoPanel close button ARIA label */\n detailPanelClose?: string\n /** Description textarea placeholder (readOnly) */\n descriptionPlaceholderReadOnly?: string\n /** Description textarea placeholder (editable) */\n descriptionPlaceholderEdit?: string\n /** Detail section — file info section heading */\n fileInfoHeading?: string\n /** Filmstrip group ARIA label(role=\"group\" + thumb button + aria-current,非 tablist)*/\n filmstripLabel?: string\n /** Previous-file nav button ARIA label */\n previousFile?: string\n /** Next-file nav button ARIA label */\n nextFile?: string\n}\n\n// i18n-allow: DS defaults;consumer override via `labels` prop\nconst DEFAULT_LABELS: Required<FileViewerLabels> = {\n zoomInput: '縮放比例',\n zoomMenu: '開啟縮放選單',\n infoToggleCollapse: '收合詳細資訊面板',\n infoToggleExpand: '展開詳細資訊面板',\n download: '下載檔案',\n close: '關閉檢視器',\n detailPanel: '檔案詳細資訊',\n detailsHeading: '詳細資訊',\n detailPanelClose: '關閉詳細資訊',\n descriptionPlaceholderReadOnly: '尚無說明',\n descriptionPlaceholderEdit: '為這個檔案加上說明…',\n fileInfoHeading: '檔案資訊',\n filmstripLabel: '檔案佇列',\n previousFile: '上一個檔案',\n nextFile: '下一個檔案',\n}\n\nexport interface FileViewerProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,\n 'onOpenChange' | 'tabIndex'\n > {\n files: FileInfo[]\n initialIndex?: number\n /** Controlled open state。與 `defaultOpen` 二擇一。 */\n open?: boolean\n /** Uncontrolled open 預設(2026-04-25 加,對齊 Dialog/Sheet/Popover dual-mode canonical)。 */\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** 當前索引(controlled);consumer 想自己控制 active file 時傳。不傳則 shell 管理。 */\n index?: number\n onIndexChange?: (index: number) => void\n /** 當前檔案 description 變化。consumer 負責持久化。readOnly 為 true 時不觸發。 */\n onDescriptionChange?: (fileId: string, description: string) => void\n /** true → InfoPanel 的 description textarea 為 readOnly。預設 false。 */\n readOnly?: boolean\n /** 顯示底部 filmstrip。預設 false;files.length < 2 時自動隱藏。 */\n showFilmstrip?: boolean\n /** 是否提供 download 按鈕。預設 true。 */\n allowDownload?: boolean\n /** 自訂 download 行為;未傳則用 anchor download attribute。 */\n onDownload?: (file: FileInfo) => void\n /** i18n labels override. Partial — merged with DS defaults. */\n labels?: FileViewerLabels\n}\n\nconst FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function FileViewer({\n files,\n initialIndex = 0,\n open: openProp,\n defaultOpen,\n onOpenChange,\n index: indexProp,\n onIndexChange,\n onDescriptionChange,\n readOnly = false,\n showFilmstrip = false,\n allowDownload = true,\n onDownload,\n labels: labelsOverride,\n className,\n onOpenAutoFocus,\n ...props\n}, ref) {\n const labels = React.useMemo(\n () => ({ ...DEFAULT_LABELS, ...labelsOverride }) satisfies Required<FileViewerLabels>,\n [labelsOverride],\n )\n // Open:受控/非受控雙模式鏡像(消費 DS useControllable;select-menu.tsx 2026-06-11 同修先例)。\n // 原本 keyboard shortcuts effect 與 index-reset effect 直接讀 `open` prop ——\n // uncontrolled(defaultOpen)場景 open === undefined,兩 effect 永遠不啟動(快捷鍵全失效\n // + 重開不重置 index),Toolbar X / backdrop click 的 onOpenChange?.(false) 也是 no-op。\n // 鏡像後 effect / close 都走 mirror:controlled 時純 passthrough,\n // uncontrolled 時 internal state 為準 + onOpenChange 僅通知。\n const [open, setOpen] = useControllable<boolean>({\n value: openProp,\n defaultValue: defaultOpen ?? false,\n onChange: onOpenChange,\n })\n const contentRef = React.useRef<HTMLDivElement | null>(null)\n const setContentRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n contentRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref],\n )\n\n // Index:uncontrolled fallback\n const [internalIndex, setInternalIndex] = React.useState(initialIndex)\n const activeIndex = indexProp ?? internalIndex\n\n const setIndex = React.useCallback(\n (next: number) => {\n const clamped = Math.max(0, Math.min(files.length - 1, next))\n if (indexProp === undefined) setInternalIndex(clamped)\n onIndexChange?.(clamped)\n },\n [files.length, indexProp, onIndexChange],\n )\n\n // 開啟時若 uncontrolled,重置為 initialIndex\n React.useEffect(() => {\n if (open && indexProp === undefined) {\n setInternalIndex(Math.max(0, Math.min(files.length - 1, initialIndex)))\n }\n }, [open, initialIndex, files.length, indexProp])\n\n // Info panel open state(shell own)\n const [infoOpen, setInfoOpen] = React.useState(false)\n // 2026-07-06 D3 perf:抽 useCallback 穩定 identity——InfoPanel 上了 React.memo(見上),\n // inline arrow 每 render 新 identity 會打穿 memo shallow-compare。\n const handleInfoToggle = React.useCallback(() => setInfoOpen((o) => !o), [])\n const handleInfoClose = React.useCallback(() => setInfoOpen(false), [])\n\n // Zoom state(shell own,renderer 消費 + 回報)\n const [zoom, setZoom] = React.useState(100)\n // 切換檔案時不再 setZoom(100)— 把「下一張該怎麼初始化 zoom」的決定權交給 renderer:\n // image-renderer 自己 watch file.url change → reset loaded → onLoad → 重 fit-page。\n // 原本 setZoom(100) 在 cache 命中(onLoad 沒 fire)時會卡 100% 不 fit(user 抓的 bug)。\n\n // Fit request(shell → renderer 指令;nonce 遞增讓重複同 fit 也觸發 renderer)\n const [fitRequest, setFitRequest] = React.useState<{ fit: ZoomFit; nonce: number } | null>(null)\n\n // Renderer capabilities(mount 時 renderer emit)\n const [capabilities, setCapabilities] = React.useState<FileRendererCapabilities>({\n zoom: false,\n })\n\n const file = files[activeIndex]\n const Renderer = file ? resolveRenderer(file) : null\n\n // Fit-to-* 下指令給 renderer,renderer 算 container/image 比例後透過 onZoomChange 回報\n const handleFit = React.useCallback((fit: ZoomFit) => {\n setFitRequest((prev) => ({ fit, nonce: (prev?.nonce ?? 0) + 1 }))\n }, [])\n\n // Download handler\n const handleDownload = React.useCallback(() => {\n if (!file) return\n if (onDownload) {\n onDownload(file)\n return\n }\n // 預設:anchor download(同源檔案有效;跨域由 consumer 提供 onDownload)\n const a = document.createElement('a')\n a.href = file.url\n a.download = file.name\n a.target = '_blank'\n a.rel = 'noopener'\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n }, [file, onDownload])\n\n // Keyboard shortcuts(focus 在 input / textarea 時不觸發)\n React.useEffect(() => {\n if (!open) return\n const handler = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement | null\n const tag = target?.tagName\n if (tag === 'INPUT' || tag === 'TEXTAREA' || target?.isContentEditable) return\n\n if (e.key === 'ArrowLeft' && files.length > 1) {\n e.preventDefault()\n setIndex(activeIndex - 1)\n } else if (e.key === 'ArrowRight' && files.length > 1) {\n e.preventDefault()\n setIndex(activeIndex + 1)\n } else if (e.key === '+' || e.key === '=') {\n if (capabilities.zoom) {\n e.preventDefault()\n setZoom((z) => nextZoomIn(z))\n }\n } else if (e.key === '-') {\n if (capabilities.zoom) {\n e.preventDefault()\n setZoom((z) => nextZoomOut(z))\n }\n } else if (e.key === '0') {\n if (capabilities.zoom) {\n e.preventDefault()\n setZoom(100)\n }\n } else if (e.key === 'f' || e.key === 'F') {\n if (capabilities.zoom) {\n e.preventDefault()\n handleFit('fit-page')\n }\n } else if (e.key === 'i' || e.key === 'I') {\n e.preventDefault()\n setInfoOpen((o) => !o)\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [open, activeIndex, files.length, setIndex, capabilities.zoom, handleFit])\n\n // Arrows idle auto-hide(世界級 lightbox canonical:Google Photos / Dropbox / PhotoSwipe)\n // 滑鼠移入 viewport → 顯示箭頭;持續 2.5 秒無 mouse move → 自動淡出(對齊世界級行為)\n const [armVisible, setArmVisible] = React.useState(false)\n const idleTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const handleViewportMouseMove = React.useCallback(() => {\n setArmVisible(true)\n if (idleTimerRef.current) clearTimeout(idleTimerRef.current)\n idleTimerRef.current = setTimeout(() => setArmVisible(false), 2500)\n }, [])\n const handleViewportMouseLeave = React.useCallback(() => {\n setArmVisible(false)\n if (idleTimerRef.current) clearTimeout(idleTimerRef.current)\n }, [])\n React.useEffect(() => () => {\n if (idleTimerRef.current) clearTimeout(idleTimerRef.current)\n }, [])\n\n if (!file || !Renderer) {\n // files 為空或 index 超界 — 不渲染\n return null\n }\n\n const showFilmstripResolved = showFilmstrip && files.length > 1\n const showArrows = files.length > 1\n\n // Root 永遠餵 mirror(Radix 視角 controlled);uncontrolled 的 defaultOpen 由\n // useControllable internal state 承載,Radix Esc / dismiss 經 setOpen 同步回 mirror。\n return (\n <DialogPrimitive.Root open={open} onOpenChange={setOpen}>\n <DialogPrimitive.Portal>\n {/* Overlay — FileViewer 固定深色氛圍,與 Dialog 共用 bg-overlay。\n **data-theme=\"dark\"**(2026-04-30):Overlay 在 Portal 內、是 Content 的 sibling,\n 不繼承 Content 內層的 dark 主題 → `--overlay` 默認 resolve 成 light theme α45 黑。\n FileViewer 永遠 dark(line 899 outer),mask 也須 dark token = α65 黑(更深)\n 才語意一致。同 DropdownMenuContent Portal 處理(line 245)。 */}\n <DialogPrimitive.Overlay\n data-theme=\"dark\"\n className={cn(\n 'fixed inset-0 z-50 bg-overlay',\n surfaceMotion,\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n )}\n />\n <DialogPrimitive.Content\n ref={setContentRef}\n {...props}\n tabIndex={-1}\n className={cn(\n // Edge-to-edge fullscreen,無 inset / 無 radius(與一般 Dialog 差別的所在)\n 'fixed inset-0 z-50 outline-none',\n surfaceMotion,\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n className,\n )}\n // FileViewer 是 fullscreen content-consumption dialog:開啟即把焦點放到 shell,\n // 讓 Radix FocusScope 立即接管、但不誤觸第一個 toolbar control/Tooltip。\n // Consumer handler 可先取消;未取消時由本元件阻止 Radix default 並聚焦 Content。\n onOpenAutoFocus={(event) => {\n onOpenAutoFocus?.(event)\n if (event.defaultPrevented) return\n event.preventDefault()\n contentRef.current?.focus({ preventScroll: true })\n }}\n >\n {/* 鎖 dark subtree。Density 繼承 page(不另設 data-density)。\n header 高度透過 `--chrome-header-height` 自動 density-aware(md=48 / lg=56)。\n ── Q1 mask 透明度(2026-04-30)──\n outer **不**設 bg → Overlay(`bg-overlay` α45/α65)透出 image 周圍區域,\n 對齊 Notion / Dropbox / Slack lightbox idiom 跟 Dialog mask 同 token 一致。\n chrome(Toolbar / Filmstrip / InfoPanel)各自 `bg-surface-raised` opaque dark\n (對齊 Apple Photos / Drive lightbox 派 — chrome opaque vs mask 半透明,\n 清楚區分 backdrop click 區 vs 互動區)。\n **不**用 bg-surface — dark mode `--surface = white α8` 半透明,outer 透明時\n 無 dark backdrop 撐 → 視覺洗白。 */}\n <div\n data-theme=\"dark\"\n className=\"w-full h-full flex flex-col text-foreground\"\n >\n {/* Accessible title — 視覺隱藏但 screen reader 讀得到 */}\n <DialogPrimitive.Title className=\"sr-only\">\n 檔案檢視器:{file.name}\n </DialogPrimitive.Title>\n\n <Toolbar\n file={file}\n capabilities={capabilities}\n zoom={zoom}\n onZoomChange={setZoom}\n onFit={handleFit}\n infoOpen={infoOpen}\n onInfoToggle={handleInfoToggle}\n onDownload={handleDownload}\n allowDownload={allowDownload}\n onClose={() => setOpen(false)}\n labels={labels}\n />\n\n {/* 主區:Viewport + 可選 InfoPanel(右側)\n Arrows visibility = armVisible(state)控制:mouse move 顯示 / 2.5s idle 隱藏 / mouse leave 立即隱藏\n 對齊 Google Photos / Dropbox lightbox / PhotoSwipe world-class canonical */}\n <div className=\"flex-1 min-h-0 flex\">\n <div\n className=\"relative flex-1 min-w-0\"\n onMouseMove={handleViewportMouseMove}\n onMouseLeave={handleViewportMouseLeave}\n // Backdrop click-to-close(對齊 Google Drive / Dropbox lightbox / Apple Photos canonical):\n // 點擊 image 周圍的暗色 backdrop 區關閉,跟 modal mask 同 idiom。\n //\n // 為何 geometric check 而非 closest('img')?\n // react-zoom-pan-pinch 的 TransformComponent 是 wrapper div 蓋在 image 之上(absorb\n // pan/zoom events),click target 是該 wrapper div 不是 <img>。closest('img') 檢查\n // ancestor 永遠 false。改 geometric check:看 click 座標是否落在 <img> 視覺 rect 內。\n onClick={(e) => {\n const t = e.target as HTMLElement\n // 互動元素(side arrows / chrome buttons 透過冒泡)→ 不關\n if (t.closest('button, [role=\"button\"]')) return\n // 點到 image 視覺範圍 → 不關(image 本體 click ≠ close)\n const img = e.currentTarget.querySelector('img')\n if (img) {\n const r = img.getBoundingClientRect()\n if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) return\n }\n // 否則 = 點到 backdrop(outer 不設 bg,半透明 bg-overlay 透出 — 見 Q1 註解)→ close\n setOpen(false)\n }}\n >\n {showArrows && activeIndex > 0 && (\n <div\n className={cn(\n 'absolute left-[var(--layout-space-loose)] top-1/2 -translate-y-1/2 z-10',\n 'transition-opacity duration-150 motion-reduce:duration-0',\n // armVisible state 控制,或 focus-within 時 a11y 強制顯示\n armVisible ? 'opacity-100' : 'opacity-0 pointer-events-none',\n 'focus-within:opacity-100 focus-within:pointer-events-auto',\n )}\n >\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n startIcon={ChevronLeft}\n aria-label={labels.previousFile}\n onClick={() => setIndex(activeIndex - 1)}\n />\n </div>\n )}\n <div className=\"w-full h-full\">\n {/* Suspense 兜 React.lazy renderer(ImageRenderer)chunk 載入。fallback=null\n = 既有 loading 行為——spec「Loading」段:shell 不提供全頁 loading state,\n loading 由 renderer 自理;chunk 載入期間的空 viewport 與 <img> 載入期間視覺一致。 */}\n <React.Suspense fallback={null}>\n <Renderer.component\n file={file}\n zoom={zoom}\n onZoomChange={setZoom}\n fitRequest={fitRequest}\n onCapabilitiesChange={setCapabilities}\n />\n </React.Suspense>\n </div>\n {showArrows && activeIndex < files.length - 1 && (\n <div\n className={cn(\n 'absolute right-[var(--layout-space-loose)] top-1/2 -translate-y-1/2 z-10',\n 'transition-opacity duration-150 motion-reduce:duration-0',\n armVisible ? 'opacity-100' : 'opacity-0 pointer-events-none',\n 'focus-within:opacity-100 focus-within:pointer-events-auto',\n )}\n >\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n startIcon={ChevronRight}\n aria-label={labels.nextFile}\n onClick={() => setIndex(activeIndex + 1)}\n />\n </div>\n )}\n </div>\n {infoOpen && (\n <InfoPanel\n file={file}\n readOnly={readOnly}\n onDescriptionChange={onDescriptionChange}\n onClose={handleInfoClose}\n labels={labels}\n />\n )}\n </div>\n\n {showFilmstripResolved && (\n <Filmstrip\n files={files}\n activeIndex={activeIndex}\n onSelect={setIndex}\n labels={labels}\n />\n )}\n </div>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n )\n})\nFileViewer.displayName = 'FileViewer'\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 fileViewerMeta = {\n component: 'FileViewer',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 'selected' = 縮圖列當前頁持續選中(bg-neutral-selected);'active' 移除 — 全檔無按壓視覺\n // (工具列按壓屬內嵌 Button meta;2026-07-07 詞彙統一 DS-wide 交叉掃補修)。\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-muted', 'bg-neutral-selected', 'bg-overlay', 'bg-surface-raised'],\n fg: ['text-fg-muted', 'text-foreground'],\n ring: ['ring-primary', 'ring-ring'],\n },\n} as const\n\nexport { FileViewer }\nexport type { FileInfo, FileRenderer, FileRendererCapabilities, FileRendererProps } from './file-viewer-types'\n"],"names":["FileIcon","XIcon","InfoPanel","Filmstrip","FileViewer"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqFA,MAAM,mBAAgD,CAAC,EAAE,MAAM,2BAA2B;AAKxF,QAAM,UAAU,MAAM;AACpB,yBAAqB,EAAE,MAAM,OAAO;AAAA,EACtC,GAAG,CAAC,oBAAoB,CAAC;AAEzB,SACE,oBAAC,OAAA,EAAI,WAAU,sDACb,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,aAAa,kBAAkB,KAAK,YAAY,SAAS;AAAA,IAAA;AAAA,EAAA,GAE7D;AAEJ;AAEA,MAAM,mBAAiC;AAAA,EACrC,IAAI;AAAA,EACJ,WAAW,MAAM;AAAA,EACjB,WAAW;AACb;AAMA,MAAM,oBAAoB,MAAM;AAAA,EAAK,MACnC,OAAO,qBAAkB,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,gBAAgB;AACvE;AAEA,MAAM,gBAA8B;AAAA,EAClC,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,WAAW;AACb;AAIA,MAAM,iBAAiC,CAAA;AAEhC,SAAS,qBAAqB,UAA8B;AAEjE,QAAM,cAAc,eAAe,UAAU,CAAC,MAAM,EAAE,OAAO,SAAS,EAAE;AACxE,MAAI,eAAe,GAAG;AACpB,mBAAe,WAAW,IAAI;AAAA,EAChC,OAAO;AACL,mBAAe,KAAK,QAAQ;AAAA,EAC9B;AACF;AAEA,SAAS,gBAAgB,MAA8B;AAErD,aAAW,KAAK,gBAAgB;AAC9B,QAAI,EAAE,UAAU,IAAI,EAAG,QAAO;AAAA,EAChC;AACA,MAAI,cAAc,UAAU,IAAI,EAAG,QAAO;AAC1C,SAAO;AACT;AAMA,MAAM,eAAyB,CAAC,IAAI,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,KAAK,GAAG;AAEvE,MAAM,mBAAwD;AAAA,EAC5D,EAAE,OAAO,aAAa,OAAO,eAAA;AAAA,EAC7B,EAAE,OAAO,YAAY,OAAO,cAAA;AAC9B;AAEA,SAAS,WAAW,SAAyB;AAC3C,aAAW,KAAK,cAAc;AAC5B,QAAI,IAAI,QAAS,QAAO;AAAA,EAC1B;AACA,SAAO,aAAa,aAAa,SAAS,CAAC;AAC7C;AACA,SAAS,YAAY,SAAyB;AAC5C,WAAS,IAAI,aAAa,SAAS,GAAG,KAAK,GAAG,KAAK;AACjD,QAAI,aAAa,CAAC,IAAI,QAAS,QAAO,aAAa,CAAC;AAAA,EACtD;AACA,SAAO,aAAa,CAAC;AACvB;AA0BA,MAAM,YAAsC,CAAC,EAAE,OAAO,UAAU,OAAO,aAAa;AAClF,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAiB,GAAG,KAAK,GAAG;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AAEpD,QAAM,UAAU,MAAM;AACpB,aAAS,GAAG,KAAK,GAAG;AAAA,EACtB,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,cAAc,MAAM;AACxB,UAAM,SAAS,SAAS,MAAM,QAAQ,WAAW,EAAE,GAAG,EAAE;AACxD,QAAI,OAAO,SAAS,MAAM,KAAK,SAAS,GAAG;AAEzC,YAAM,UAAU,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC;AAClD,eAAS,OAAO;AAChB,eAAS,GAAG,OAAO,GAAG;AAAA,IACxB,OAAO;AACL,eAAS,GAAG,KAAK,GAAG;AAAA,IACtB;AAAA,EACF;AAEA;AAAA;AAAA,IAEE,qBAAC,OAAA,EAAI,WAAU,kCAEb,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,UAAQ;AAAA,UACR,WAAW;AAAA,UACX,cAAW;AAAA,UACX,UAAU,SAAS;AAAA,UACnB,SAAS,MAAM,SAAS,YAAY,KAAK,CAAC;AAAA,QAAA;AAAA,MAAA;AAAA,MAQ5C,qBAAC,cAAA,EAAa,MAAM,UAAU,cAAc,aAI1C,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAS;AAAA,YACT,WAAW;AAAA,YACX,cAAY,OAAO;AAAA,YACnB,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,QAAQ;AAAA,YACR,WAAW,CAAC,MAAM;AAChB,kBAAI,EAAE,QAAQ,SAAS;AACrB,kBAAE,eAAA;AACF,4BAAA;AACE,kBAAE,OAA4B,KAAA;AAAA,cAClC;AAAA,YACF;AAAA,YACA,WAAU;AAAA,YACV,SACE,oBAAC,qBAAA,EAAoB,SAAO,MAC1B,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAM;AAAA,gBACN,cAAY,OAAO;AAAA,gBACnB,MAAK;AAAA,gBACL,gBAAc;AAAA,cAAA;AAAA,YAAA,EAChB,CACF;AAAA,UAAA;AAAA,QAAA;AAAA,QAOJ;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,OAAM;AAAA,YACN,YAAY;AAAA,YAEZ,WAAU;AAAA,YACV,cAAW;AAAA,YAGX,UAAA,qBAAC,OAAA,EAAI,cAAW,QAAO,WAAU,YAC9B,UAAA;AAAA,cAAA,iBAAiB,IAAI,CAAC,QACrB;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAEC,UAAU,MAAM,MAAM,IAAI,KAAK;AAAA,kBAE9B,UAAA,IAAI;AAAA,gBAAA;AAAA,gBAHA,IAAI;AAAA,cAAA,CAKZ;AAAA,kCACA,uBAAA,EAAsB;AAAA,cACtB,aAAa,IAAI,CAAC,MAAM;AACvB,sBAAM,WAAW,MAAM;AACvB,uBACE;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBAEC,UAAU,MAAM,SAAS,CAAC;AAAA,oBAC1B,cAAY,WAAW,YAAY;AAAA,oBACnC,WAAW;AAAA,sBACT;AAAA,sBACA,YAAY;AAAA,oBAAA;AAAA,oBAGb,UAAA;AAAA,sBAAA;AAAA,sBAAE;AAAA,oBAAA;AAAA,kBAAA;AAAA,kBARE;AAAA,gBAAA;AAAA,cAWX,CAAC;AAAA,YAAA,EAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,MACF,GACF;AAAA,MAGA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,UAAQ;AAAA,UACR,WAAW;AAAA,UACX,cAAW;AAAA,UACX,UAAU,SAAS;AAAA,UACnB,SAAS,MAAM,SAAS,WAAW,KAAK,CAAC;AAAA,QAAA;AAAA,MAAA;AAAA,IAC3C,EAAA,CACF;AAAA;AAEJ;AACA,UAAU,cAAc;AAkBxB,MAAM,UAAkC,CAAC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAIC,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMT;AAAA,MAAA;AAAA,MAMF,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,0CACb,UAAA;AAAA,UAAA,oBAACA,QAAS,MAAM,IAAI,WAAU,4BAA2B,eAAW,MAAC;AAAA,UACrE;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,KAAK;AAAA,cAEX,UAAA,KAAK;AAAA,YAAA;AAAA,UAAA;AAAA,QACR,GACF;AAAA,QAUA,qBAAC,OAAA,EAAI,WAAU,oCACZ,UAAA;AAAA,UAAA,aAAa,QACZ,qBAAA,UAAA,EAEE,UAAA;AAAA,YAAA,oBAAC,aAAU,OAAO,MAAM,UAAU,cAAc,OAAc,QAAgB;AAAA,gCAI7E,eAAA,CAAA,CAAc;AAAA,UAAA,GACjB;AAAA,UAEF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,UAAQ;AAAA,cACR,WAAW;AAAA,cACX,cAAY,WAAW,OAAO,qBAAqB,OAAO;AAAA,cAC1D,SAAS;AAAA,cACT,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,UAEV,iBACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,UAAQ;AAAA,cACR,WAAW;AAAA,cACX,cAAY,OAAO;AAAA,cACnB,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,8BAKZ,eAAA,EAAc;AAAA,UAIf;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,UAAQ;AAAA,cACR,SAAO;AAAA,cACP,MAAK;AAAA,cACL,gBAAY;AAAA,cACZ,WAAWC;AAAAA,cACX,cAAY,OAAO;AAAA,cACnB,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,QACX,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;AAYA,SAAS,YAAY,OAA+C;AAClE,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,QAAQ,KAAM,QAAO,GAAG,KAAK;AACjC,MAAI,QAAQ,OAAO,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC;AAC5D,MAAI,QAAQ,OAAO,OAAO,KAAM,QAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,CAAC,CAAC;AAC5E,SAAO,IAAI,SAAS,OAAO,OAAO,OAAO,QAAQ,CAAC,CAAC;AACrD;AAKA,MAAM,YAAY,MAAM,KAAK,SAASC,WAAU;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmB;AACjB,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,KAAK,eAAe,EAAE;AAE/D,QAAM,UAAU,MAAM;AACpB,aAAS,KAAK,eAAe,EAAE;AAAA,EACjC,GAAG,CAAC,KAAK,IAAI,KAAK,WAAW,CAAC;AAE9B,QAAM,SAAS,MAAM;AACnB,QAAI,SAAU;AACd,QAAI,WAAW,KAAK,eAAe,KAAK;AACtC,iEAAsB,KAAK,IAAI;AAAA,IACjC;AAAA,EACF;AAEA,QAAM,WAAW,YAAY,KAAK,IAAI;AAEtC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA;AAAA,QAET;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,cAAY,OAAO;AAAA,MAInB,UAAA;AAAA,QAAA,qBAAC,cAAA,EAAa,WAAU,mBACtB,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAG,WAAU,4CAA4C,UAAA,OAAO,gBAAe;AAAA,UAGhF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,UAAQ;AAAA,cACR,SAAO;AAAA,cACP,MAAK;AAAA,cACL,gBAAY;AAAA,cACZ,WAAWD;AAAAA,cACX,cAAY,OAAO;AAAA,cACnB,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,QACX,GACF;AAAA,4BAMC,YAAA,EAAW,WAAU,kBACpB,UAAA,qBAAC,SAAI,WAAW;AAAA,UACd;AAAA,UACA;AAAA,UACA;AAAA,QAAA,GAIF,UAAA;AAAA,UAAA,qBAAC,OAAA,EACC,UAAA;AAAA,YAAA,oBAAC,cAAW,UAAA,KAAA,CAAE;AAAA,YACd;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,gBACxC,QAAQ;AAAA,gBACR;AAAA,gBACA,aAAa,WAAW,OAAO,iCAAiC,OAAO;AAAA,gBACvE,MAAM;AAAA,cAAA;AAAA,YAAA;AAAA,UACR,GACF;AAAA,UASA,qBAAC,OAAA,EAAI,WAAU,iDACb,UAAA;AAAA,YAAA,oBAAC,QAAA,EAAK,WAAU,yCAAyC,UAAA,OAAO,iBAAgB;AAAA,YAChF,qBAAC,iBAAA,EAAgB,aAAY,cAAa,SAAO,MAC/C,UAAA;AAAA,cAAA,oBAAC,iBAAA,EAAgB,OAAM,MAAM,UAAA,KAAK,MAAK;AAAA,kCACtC,iBAAA,EAAgB,OAAM,MAAM,UAAA,KAAK,YAAY,KAAI;AAAA,cACjD,YACC,oBAAC,iBAAA,EAAgB,OAAM,MACrB,8BAAC,QAAA,EAAK,WAAU,gBAAgB,UAAA,SAAA,CAAS,EAAA,CAC3C;AAAA,cAED,KAAK,YACJ,OAAO,QAAQ,KAAK,QAAQ,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,0BACrC,iBAAA,EAAwB,OAAO,GAAI,UAAA,OAAO,CAAC,EAAA,GAAtB,CAAwB,CAC/C;AAAA,YAAA,EAAA,CACL;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,EAAA,CACA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN,CAAC;AAYD,MAAM,aAAa;AAKnB,MAAM,YAAY,MAAM,KAAK,SAASE,WAAU,EAAE,OAAO,aAAa,UAAU,UAA0B;AACxG,QAAM,EAAE,WAAW,SAAS,OAAO,UAAA,IAAc,eAAA;AACjD,QAAM,eAAe,gBAAgB,SAAS;AAC9C,QAAM,YAAY,cAAc,EAAE,WAAW,SAAS,OAAO,mBAAmB,oBAAoB;AAGpG,QAAM,UAAU,MAAM;AACpB,UAAM,KAAK,UAAU;AACrB,QAAI,CAAC,GAAI;AACT,UAAM,SAAS,GAAG,cAAiC,sBAAsB,WAAW,IAAI;AACxF,qCAAQ,eAAe,EAAE,UAAU,UAAU,QAAQ,UAAU,OAAO;EACxE,GAAG,CAAC,aAAa,SAAS,CAAC;AAE3B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA;AAAA,QAET;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAA,aAAa,CAAC,WACb,oBAAC,qBAAA,EAAoB,WAAU,QAAO,SAAS,MAAM,aAAa,MAAM,EAAA,CAAG;AAAA,QAE7E;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAW;AAAA,cACT;AAAA;AAAA,cAEA;AAAA,cACA;AAAA,YAAA;AAAA,YAEF,OAAO;AAAA,cACL;AAAA,cACA,iBAAiB;AAAA,YAAA;AAAA,YAUnB,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAK;AAAA,gBACL,cAAY,OAAO;AAAA,gBACnB,WAAU;AAAA,gBAEX,UAAA,MAAM,IAAI,CAAC,MAAM,MAAM;;AACtB,wBAAM,SAAS,MAAM;AACrB,wBAAM,UAAU,eAAe,IAAI;AACnC,wBAAM,QAAM,UAAK,KAAK,MAAM,GAAG,EAAE,IAAA,MAArB,mBAA4B,kBAAiB;AACzD,yBACE;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBAEC,MAAK;AAAA,sBACL,gBAAc,SAAS,SAAS;AAAA,sBAChC,cAAY,GAAG,IAAI,CAAC,MAAM,MAAM,MAAM,IAAI,KAAK,IAAI;AAAA,sBACnD,oBAAkB;AAAA,sBAClB,SAAS,MAAM,SAAS,CAAC;AAAA,sBACzB,WAAW;AAAA,wBACT;AAAA,wBACA;AAAA,wBACA;AAAA,wBACA,SACI,wBACA;AAAA,sBAAA;AAAA,sBAEN,OAAO,EAAE,OAAO,YAAY,QAAQ,WAAA;AAAA,sBAEpC,8BAAC,aAAA,EAAY,OAAO,GAAG,WAAU,iBAC9B,UAAA,UACC;AAAA,wBAAC;AAAA,wBAAA;AAAA,0BACC,KAAK,KAAK;AAAA,0BACV,KAAI;AAAA,0BACJ,eAAW;AAAA,0BACX,WAAU;AAAA,0BACV,WAAW;AAAA,wBAAA;AAAA,sBAAA,IAGb,qBAAC,OAAA,EAAI,WAAU,mEACb,UAAA;AAAA,wBAAA,oBAAC,YAAS,MAAM,IAAI,WAAU,iBAAgB,eAAW,MAAC;AAAA,wBAC1D,oBAAC,QAAA,EAAK,WAAU,2CAA2C,UAAA,IAAA,CAAI;AAAA,sBAAA,EAAA,CACjE,EAAA,CAEJ;AAAA,oBAAA;AAAA,oBA/BK,KAAK;AAAA,kBAAA;AAAA,gBAkChB,CAAC;AAAA,cAAA;AAAA,YAAA;AAAA,UACD;AAAA,QAAA;AAAA,QAED,aAAa,CAAC,SACb,oBAAC,qBAAA,EAAoB,WAAU,SAAQ,SAAS,MAAM,aAAa,OAAO,EAAA,CAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIrF,CAAC;AA6CD,MAAM,iBAA6C;AAAA,EACjD,WAAW;AAAA,EACX,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,UAAU;AAAA,EACV,OAAO;AAAA,EACP,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,gCAAgC;AAAA,EAChC,4BAA4B;AAAA,EAC5B,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,UAAU;AACZ;AA+BA,MAAM,aAAa,MAAM,WAA4C,SAASC,YAAW;AAAA,EACvF;AAAA,EACA,eAAe;AAAA,EACf,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAG,KAAK;AACN,QAAM,SAAS,MAAM;AAAA,IACnB,OAAO,EAAE,GAAG,gBAAgB,GAAG;IAC/B,CAAC,cAAc;AAAA,EAAA;AAQjB,QAAM,CAAC,MAAM,OAAO,IAAI,gBAAyB;AAAA,IAC/C,OAAO;AAAA,IACP,cAAc,eAAe;AAAA,IAC7B,UAAU;AAAA,EAAA,CACX;AACD,QAAM,aAAa,MAAM,OAA8B,IAAI;AAC3D,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,SAAgC;AAC/B,iBAAW,UAAU;AACrB,UAAI,OAAO,QAAQ,WAAY,KAAI,IAAI;AAAA,eAC9B,SAAS,UAAU;AAAA,IAC9B;AAAA,IACA,CAAC,GAAG;AAAA,EAAA;AAIN,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,YAAY;AACrE,QAAM,cAAc,aAAa;AAEjC,QAAM,WAAW,MAAM;AAAA,IACrB,CAAC,SAAiB;AAChB,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,SAAS,GAAG,IAAI,CAAC;AAC5D,UAAI,cAAc,OAAW,kBAAiB,OAAO;AACrD,qDAAgB;AAAA,IAClB;AAAA,IACA,CAAC,MAAM,QAAQ,WAAW,aAAa;AAAA,EAAA;AAIzC,QAAM,UAAU,MAAM;AACpB,QAAI,QAAQ,cAAc,QAAW;AACnC,uBAAiB,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC;AAAA,IACxE;AAAA,EACF,GAAG,CAAC,MAAM,cAAc,MAAM,QAAQ,SAAS,CAAC;AAGhD,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AAGpD,QAAM,mBAAmB,MAAM,YAAY,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE;AAC3E,QAAM,kBAAkB,MAAM,YAAY,MAAM,YAAY,KAAK,GAAG,EAAE;AAGtE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,GAAG;AAM1C,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAiD,IAAI;AAG/F,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAmC;AAAA,IAC/E,MAAM;AAAA,EAAA,CACP;AAED,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM,WAAW,OAAO,gBAAgB,IAAI,IAAI;AAGhD,QAAM,YAAY,MAAM,YAAY,CAAC,QAAiB;AACpD,kBAAc,CAAC,UAAU,EAAE,KAAK,SAAQ,6BAAM,UAAS,KAAK,EAAA,EAAI;AAAA,EAClE,GAAG,CAAA,CAAE;AAGL,QAAM,iBAAiB,MAAM,YAAY,MAAM;AAC7C,QAAI,CAAC,KAAM;AACX,QAAI,YAAY;AACd,iBAAW,IAAI;AACf;AAAA,IACF;AAEA,UAAM,IAAI,SAAS,cAAc,GAAG;AACpC,MAAE,OAAO,KAAK;AACd,MAAE,WAAW,KAAK;AAClB,MAAE,SAAS;AACX,MAAE,MAAM;AACR,aAAS,KAAK,YAAY,CAAC;AAC3B,MAAE,MAAA;AACF,aAAS,KAAK,YAAY,CAAC;AAAA,EAC7B,GAAG,CAAC,MAAM,UAAU,CAAC;AAGrB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,UAAU,CAAC,MAAqB;AACpC,YAAM,SAAS,EAAE;AACjB,YAAM,MAAM,iCAAQ;AACpB,UAAI,QAAQ,WAAW,QAAQ,eAAc,iCAAQ,mBAAmB;AAExE,UAAI,EAAE,QAAQ,eAAe,MAAM,SAAS,GAAG;AAC7C,UAAE,eAAA;AACF,iBAAS,cAAc,CAAC;AAAA,MAC1B,WAAW,EAAE,QAAQ,gBAAgB,MAAM,SAAS,GAAG;AACrD,UAAE,eAAA;AACF,iBAAS,cAAc,CAAC;AAAA,MAC1B,WAAW,EAAE,QAAQ,OAAO,EAAE,QAAQ,KAAK;AACzC,YAAI,aAAa,MAAM;AACrB,YAAE,eAAA;AACF,kBAAQ,CAAC,MAAM,WAAW,CAAC,CAAC;AAAA,QAC9B;AAAA,MACF,WAAW,EAAE,QAAQ,KAAK;AACxB,YAAI,aAAa,MAAM;AACrB,YAAE,eAAA;AACF,kBAAQ,CAAC,MAAM,YAAY,CAAC,CAAC;AAAA,QAC/B;AAAA,MACF,WAAW,EAAE,QAAQ,KAAK;AACxB,YAAI,aAAa,MAAM;AACrB,YAAE,eAAA;AACF,kBAAQ,GAAG;AAAA,QACb;AAAA,MACF,WAAW,EAAE,QAAQ,OAAO,EAAE,QAAQ,KAAK;AACzC,YAAI,aAAa,MAAM;AACrB,YAAE,eAAA;AACF,oBAAU,UAAU;AAAA,QACtB;AAAA,MACF,WAAW,EAAE,QAAQ,OAAO,EAAE,QAAQ,KAAK;AACzC,UAAE,eAAA;AACF,oBAAY,CAAC,MAAM,CAAC,CAAC;AAAA,MACvB;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,OAAO;AAC1C,WAAO,MAAM,OAAO,oBAAoB,WAAW,OAAO;AAAA,EAC5D,GAAG,CAAC,MAAM,aAAa,MAAM,QAAQ,UAAU,aAAa,MAAM,SAAS,CAAC;AAI5E,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,QAAM,eAAe,MAAM,OAA6C,IAAI;AAC5E,QAAM,0BAA0B,MAAM,YAAY,MAAM;AACtD,kBAAc,IAAI;AAClB,QAAI,aAAa,QAAS,cAAa,aAAa,OAAO;AAC3D,iBAAa,UAAU,WAAW,MAAM,cAAc,KAAK,GAAG,IAAI;AAAA,EACpE,GAAG,CAAA,CAAE;AACL,QAAM,2BAA2B,MAAM,YAAY,MAAM;AACvD,kBAAc,KAAK;AACnB,QAAI,aAAa,QAAS,cAAa,aAAa,OAAO;AAAA,EAC7D,GAAG,CAAA,CAAE;AACL,QAAM,UAAU,MAAM,MAAM;AAC1B,QAAI,aAAa,QAAS,cAAa,aAAa,OAAO;AAAA,EAC7D,GAAG,CAAA,CAAE;AAEL,MAAI,CAAC,QAAQ,CAAC,UAAU;AAEtB,WAAO;AAAA,EACT;AAEA,QAAM,wBAAwB,iBAAiB,MAAM,SAAS;AAC9D,QAAM,aAAa,MAAM,SAAS;AAIlC,SACE,oBAAC,gBAAgB,MAAhB,EAAqB,MAAY,cAAc,SAC9C,UAAA,qBAAC,gBAAgB,QAAhB,EAMC,UAAA;AAAA,IAAA;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,cAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,IAEF;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,KAAK;AAAA,QACJ,GAAG;AAAA,QACJ,UAAU;AAAA,QACV,WAAW;AAAA;AAAA,UAET;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAKF,iBAAiB,CAAC,UAAU;;AAC1B,6DAAkB;AAClB,cAAI,MAAM,iBAAkB;AAC5B,gBAAM,eAAA;AACN,2BAAW,YAAX,mBAAoB,MAAM,EAAE,eAAe;QAC7C;AAAA,QAYA,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,cAAW;AAAA,YACX,WAAU;AAAA,YAGV,UAAA;AAAA,cAAA,qBAAC,gBAAgB,OAAhB,EAAsB,WAAU,WAAU,UAAA;AAAA,gBAAA;AAAA,gBAClC,KAAK;AAAA,cAAA,GACd;AAAA,cAEA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA,cAAc;AAAA,kBACd,OAAO;AAAA,kBACP;AAAA,kBACA,cAAc;AAAA,kBACd,YAAY;AAAA,kBACZ;AAAA,kBACA,SAAS,MAAM,QAAQ,KAAK;AAAA,kBAC5B;AAAA,gBAAA;AAAA,cAAA;AAAA,cAMF,qBAAC,OAAA,EAAI,WAAU,uBACb,UAAA;AAAA,gBAAA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,WAAU;AAAA,oBACV,aAAa;AAAA,oBACb,cAAc;AAAA,oBAQd,SAAS,CAAC,MAAM;AACd,4BAAM,IAAI,EAAE;AAEZ,0BAAI,EAAE,QAAQ,yBAAyB,EAAG;AAE1C,4BAAM,MAAM,EAAE,cAAc,cAAc,KAAK;AAC/C,0BAAI,KAAK;AACP,8BAAM,IAAI,IAAI,sBAAA;AACd,4BAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,OAAQ;AAAA,sBAClG;AAEA,8BAAQ,KAAK;AAAA,oBACf;AAAA,oBAEC,UAAA;AAAA,sBAAA,cAAc,cAAc,KAC3B;AAAA,wBAAC;AAAA,wBAAA;AAAA,0BACC,WAAW;AAAA,4BACT;AAAA,4BACA;AAAA;AAAA,4BAEA,aAAa,gBAAgB;AAAA,4BAC7B;AAAA,0BAAA;AAAA,0BAGF,UAAA;AAAA,4BAAC;AAAA,4BAAA;AAAA,8BACC,SAAQ;AAAA,8BACR,MAAK;AAAA,8BACL,UAAQ;AAAA,8BACR,WAAW;AAAA,8BACX,cAAY,OAAO;AAAA,8BACnB,SAAS,MAAM,SAAS,cAAc,CAAC;AAAA,4BAAA;AAAA,0BAAA;AAAA,wBACzC;AAAA,sBAAA;AAAA,sBAGJ,oBAAC,SAAI,WAAU,iBAIb,8BAAC,MAAM,UAAN,EAAe,UAAU,MACxB,UAAA;AAAA,wBAAC,SAAS;AAAA,wBAAT;AAAA,0BACC;AAAA,0BACA;AAAA,0BACA,cAAc;AAAA,0BACd;AAAA,0BACA,sBAAsB;AAAA,wBAAA;AAAA,sBAAA,GAE1B,EAAA,CACF;AAAA,sBACC,cAAc,cAAc,MAAM,SAAS,KAC1C;AAAA,wBAAC;AAAA,wBAAA;AAAA,0BACC,WAAW;AAAA,4BACT;AAAA,4BACA;AAAA,4BACA,aAAa,gBAAgB;AAAA,4BAC7B;AAAA,0BAAA;AAAA,0BAGF,UAAA;AAAA,4BAAC;AAAA,4BAAA;AAAA,8BACC,SAAQ;AAAA,8BACR,MAAK;AAAA,8BACL,UAAQ;AAAA,8BACR,WAAW;AAAA,8BACX,cAAY,OAAO;AAAA,8BACnB,SAAS,MAAM,SAAS,cAAc,CAAC;AAAA,4BAAA;AAAA,0BAAA;AAAA,wBACzC;AAAA,sBAAA;AAAA,oBACF;AAAA,kBAAA;AAAA,gBAAA;AAAA,gBAGH,YACC;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA,SAAS;AAAA,oBACT;AAAA,kBAAA;AAAA,gBAAA;AAAA,cACF,GAEJ;AAAA,cAEC,yBACC;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC;AAAA,kBACA;AAAA,kBACA,UAAU;AAAA,kBACV;AAAA,gBAAA;AAAA,cAAA;AAAA,YACF;AAAA,UAAA;AAAA,QAAA;AAAA,MAEJ;AAAA,IAAA;AAAA,EACF,EAAA,CACF,EAAA,CACF;AAEJ,CAAC;AACD,WAAW,cAAc;AAIlB,MAAM,iBAAiB;AAAA,EAC5B,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EACR,UAAU,CAAA;AAAA,EAGV,OAAO,CAAA;AAAA;AAAA;AAAA,EAKP,QAAQ,CAAC,WAAW,SAAS,YAAY,iBAAiB,UAAU;AAAA,EACpE,QAAQ;AAAA,IACN,IAAI,CAAC,YAAY,uBAAuB,cAAc,mBAAmB;AAAA,IACzE,IAAI,CAAC,iBAAiB,iBAAiB;AAAA,IACvC,MAAM,CAAC,gBAAgB,WAAW;AAAA,EAAA;AAEtC;"}
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export type { BreadcrumbEllipsisProps, BreadcrumbItemSpec, BreadcrumbListProps,
16
16
  export { BULK_ACTION_BAR_DEFAULT_LABELS, BulkActionBar, } from './components/BulkActionBar/index';
17
17
  export type { BulkActionBarLabels, BulkActionBarProps, } from './components/BulkActionBar/index';
18
18
  export { Button, ButtonDivider, ButtonGroup, buttonVariants, } from './components/Button/index';
19
- export type { ButtonProps, } from './components/Button/index';
19
+ export type { ButtonDividerProps, ButtonProps, } from './components/Button/index';
20
20
  export { Calendar, } from './components/Calendar/index';
21
21
  export type { CalendarEvent, CalendarProps, } from './components/Calendar/index';
22
22
  export { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious, } from './components/Carousel/index';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { cva } from "class-variance-authority";
4
- import { cn, lineClampClass } from "../../lib/utils.js";
4
+ import { lineClampClass, cn } from "../../lib/utils.js";
5
5
  import { Avatar } from "../../components/Avatar/avatar.js";
6
6
  import { Tooltip, TooltipTrigger, TooltipContent } from "../../components/Tooltip/tooltip.js";
7
7
  import { useTruncated } from "../../hooks/use-truncated.js";
@@ -3,8 +3,8 @@
3
3
  "schemaVersion": 1,
4
4
  "role": "template-consumer",
5
5
  "release": {
6
- "designSystem": "0.1.0-beta.117",
7
- "storybookConfig": "0.1.0-beta.117"
6
+ "designSystem": "0.1.0-beta.119",
7
+ "storybookConfig": "0.1.0-beta.119"
8
8
  },
9
9
  "upgradeTrust": {
10
10
  "schemaVersion": 1,
@@ -42,10 +42,10 @@
42
42
  "implementationSha256": "98b74118fb5b410b77ad2994aa9105fa7609c7353822f8c5213cce54d94f0af4"
43
43
  },
44
44
  "payload": {
45
- "forkCorpusLockSha256": "92a7a449773f392fb29ef81c5709e240acd72c96b30343d656fdb63b01af730a",
45
+ "forkCorpusLockSha256": "b6d683290975478a8934c2ca47af813defcded09de5cf4c168ba31a47d5b90e8",
46
46
  "discoveryPolicySha256": "fba289aeeb5a3f11139b9589f7ab08b5e8c8799c7bdf8f0ea57ebfeb8e7c9da6",
47
47
  "providerMaterializersSha256": "732e85b01a12e7dd19469e26e4427f0e0cfe5117a78fc9f69c2f0f89702f463a",
48
- "providerLifecycleSha256": "9a9f921c2dc064d4cf3d0dd0b7fc45e3e322d373ff5c29222bde9e25f33a7223",
48
+ "providerLifecycleSha256": "d7b8cb9a66df18be111df4034b89b38a0f49664abe80528e4dd1996d03dd0cc7",
49
49
  "providerInventorySha256": "00adf1c4f6b3367055a9cc3fdfe658660771b4c9931f696e12761a95076ad92c",
50
50
  "executionRuntimeSha256": "d9377cc45308731f287d7b1711b7c38fc4e02ff8f84729bcbbf01bb52d136904",
51
51
  "commonInstructionSha256": "a72c51a412d6cdb3961d691744013c8362e826626a330cecf4a296a6f4d93a5c",
@@ -107,8 +107,8 @@
107
107
  "scripts/lib/a11y-static-server.mjs": "3ae8ca74a66ca5f155d1a1d0c97280d41f07556274c8c077705a489ebe6a7018",
108
108
  "scripts/lib/canonical-path-containment.mjs": "0244e31dc3be5015bed3efd65a5a77086a2e4676e9ac79cd0801c8cde6a74fcd",
109
109
  "scripts/lib/closed-tool-execution.mjs": "eacd0cd92c5bff03e40ff4e82c02fae9bf2e6f543f157eef3d41c3ed8b7d3cb6",
110
- "scripts/lib/consumer-control-plane-policy.mjs": "ea8b11ddc5c964ea742ccba5e6a3bc2f215db5d165afe2cca96192812287750c",
111
- "scripts/lib/exact-workspace-dependencies.mjs": "3c9453e5228d7eaf1bfef3350585746e1aff1475411cb7cf8696bf9acdb048cf",
110
+ "scripts/lib/consumer-control-plane-policy.mjs": "51aba211bb2f755eff7fa48bb060f9835356a62d2643ab5049147a9896c7bb72",
111
+ "scripts/lib/exact-workspace-dependencies.mjs": "b4921ad903a3ed3d901187d406a87270190945c5ca1874b089e80df898d47a97",
112
112
  "scripts/lib/governance-dependency-bootstrap.mjs": "8b37cf68731810ad665e02b7ac4a2a5a4ad55b01c6d9e0cbb37f33f3b00118f3",
113
113
  "scripts/lib/provider-lifecycle.mjs": "9aabb2c98efc454b8555308a34830e6590b034b10a63f76f788fbd4cf37ce7e7",
114
114
  "scripts/lib/verified-exact-npm-runtime.mjs": "811ed3390a4ff6b4e818a5ce8b5c0ce34e10f48fb443b6177d6e4c8e14fa9670",
@@ -17,8 +17,14 @@ export const PROTECTED_CONTROL_PLANE_PATH_PREFIXES = Object.freeze([
17
17
  export const REVIEWED_CONTROL_PLANE_UPDATE_COMMAND =
18
18
  'node infra/governance/bin/consumerctl.mjs plan-control-plane-update'
19
19
 
20
+ // 兩條路都要講,只講前者會把 legacy-profile 的 consumer 導進死路:recurring lane 要求 profile 在
21
+ // consumer-upgrade-protocol.json 的 reviewedControlPlaneUpdate.entryProfiles 內,legacy profile 不在,
22
+ // 而要進去得先完成 promotion —— promotion 又要求一個「非作者」的獨立簽署,單人 fleet 無法滿足。
23
+ // legacy profile 自己的 entry mode(one-time reviewed full-snapshot PR)一直是通的,由第二條指令固化。
20
24
  export const REVIEWED_CONTROL_PLANE_UPDATE_ROUTE =
21
25
  `use the recurring reviewed control-plane update protocol in the DS authority checkout; start with \`${REVIEWED_CONTROL_PLANE_UPDATE_COMMAND}\``
26
+ + '. If the consumer is still on a legacy bootstrap profile (not in reviewedControlPlaneUpdate.entryProfiles),'
27
+ + ' use its own one-time reviewed full-snapshot route instead: `node scripts/consumer-fullsnapshot-upgrade.mjs`'
22
28
 
23
29
  export function requiresReviewedControlPlaneUpdate(path) {
24
30
  invariant(typeof path === 'string' && path.length > 0, 'protected control-plane path must be a non-empty string')
@@ -171,6 +171,29 @@ export function pinExactWorkspaceDependencies(rootPath, expectedVersion) {
171
171
  }
172
172
  }
173
173
 
174
+ // An upgrade transaction bumps only the repository root, so its reconstruction nests the previous
175
+ // release under every workspace that still asked for it. Realigning the manifests above is not
176
+ // enough: `npm install --package-lock-only` then reports "up to date" and leaves those nested
177
+ // entries behind, because npm only adds what is missing — it never prunes a satisfied-but-
178
+ // extraneous node — and the exact-version verification below would fail on a lock nobody can fix
179
+ // by re-running the install. Dropping the off-version nested entries lets that install re-validate
180
+ // against a consistent lock; a workspace that genuinely needs its own copy simply gets it back,
181
+ // and verifyExactWorkspaceDependencies stays the authority either way.
182
+ const lockPath = discoverDependencyLockPath(root)
183
+ const lockAbsolute = resolve(root, lockPath)
184
+ const packageLock = readJson(lockAbsolute, lockPath)
185
+ if (packageLock.packages && typeof packageLock.packages === 'object' && !Array.isArray(packageLock.packages)) {
186
+ const stale = Object.keys(packageLock.packages).filter((entryPath) => (
187
+ GOVERNED_WORKSPACE_PACKAGES.some((packageName) => entryPath.endsWith(`/node_modules/${packageName}`))
188
+ && packageLock.packages[entryPath]?.version !== expectedVersion
189
+ ))
190
+ if (stale.length) {
191
+ for (const entryPath of stale) delete packageLock.packages[entryPath]
192
+ writeFileSync(lockAbsolute, `${JSON.stringify(packageLock, null, 2)}\n`)
193
+ changed.push(lockPath)
194
+ }
195
+ }
196
+
174
197
  return Object.freeze(changed)
175
198
  }
176
199
 
@@ -303,7 +303,7 @@
303
303
  {
304
304
  "schemaVersion": 1,
305
305
  "file": "consumer/managed-files/145aab4b4fc470fc9ba7679b4a1d87d4f6a6d604940bd40f8d675c8af696498f.blob",
306
- "sha256": "ea8b11ddc5c964ea742ccba5e6a3bc2f215db5d165afe2cca96192812287750c",
306
+ "sha256": "51aba211bb2f755eff7fa48bb060f9835356a62d2643ab5049147a9896c7bb72",
307
307
  "source": "scripts/lib/consumer-control-plane-policy.mjs",
308
308
  "classification": null,
309
309
  "destination": "scripts/lib/consumer-control-plane-policy.mjs"
@@ -375,7 +375,7 @@
375
375
  {
376
376
  "schemaVersion": 1,
377
377
  "file": "consumer/managed-files/5aeda1df235815f40dd755951f2b868d4e02035653d4536a8dde209cdc75b493.blob",
378
- "sha256": "3c9453e5228d7eaf1bfef3350585746e1aff1475411cb7cf8696bf9acdb048cf",
378
+ "sha256": "b4921ad903a3ed3d901187d406a87270190945c5ca1874b089e80df898d47a97",
379
379
  "source": "scripts/lib/exact-workspace-dependencies.mjs",
380
380
  "classification": null,
381
381
  "destination": "scripts/lib/exact-workspace-dependencies.mjs"
@@ -663,7 +663,7 @@
663
663
  {
664
664
  "schemaVersion": 1,
665
665
  "file": "hooks/check_consumer_app_invariants.sh",
666
- "sha256": "b9205c06d6f3ab5ab1ce46a62bfbaedda02c62cd474bcb5f9931bbb54acae985",
666
+ "sha256": "0cfd6f321e4de7e778be5869129aa82c846da3564d6913c75b7f66dd014f3191",
667
667
  "source": "check_consumer_app_invariants.sh",
668
668
  "classification": "SHIP_AS_IS",
669
669
  "destination": null
@@ -727,7 +727,7 @@
727
727
  {
728
728
  "schemaVersion": 1,
729
729
  "file": "hooks/check_layout_space_magic_numbers.sh",
730
- "sha256": "9cc344cce5898c98f00e61861cd483559c28fbb92e3784127ddd7cc2979bc746",
730
+ "sha256": "7a3d47170344945f6b07e331413b13d868f0655c82375f15bc6f1fde0b9a5a1e",
731
731
  "source": "check_layout_space_magic_numbers.sh",
732
732
  "classification": "SHIP_AS_IS",
733
733
  "destination": null
@@ -919,7 +919,7 @@
919
919
  {
920
920
  "schemaVersion": 1,
921
921
  "file": "manifest.json",
922
- "sha256": "8927ec4334bdd1cbc4c7bc7d41b1153b83db245070e328c2433867270aa7c5ec",
922
+ "sha256": "2490f73687425558ad7728eaae2c685a7b963687a0bc3d4e26ec6a44c3c83fde",
923
923
  "source": null,
924
924
  "classification": null,
925
925
  "destination": null
@@ -524,12 +524,13 @@ if grep -qE '>[^<]*\{[a-zA-Z0-9_.]+\} *\(\{[a-zA-Z0-9_.]*(count|Count|length|tot
524
524
  ! grep -q '@count-in-label-ok:' <<<"$CONTENT"; then
525
525
  VIOLATIONS="${VIOLATIONS} - 計數括號串接 {label} ({count}) → row header 用「標題左、純數字右 space-between + tabular-nums」(item-anatomy.spec.md:176);互動元素(tab/chip/button)用 badge slot。aria/純文字匯出層 → @count-in-label-ok: <rationale>\n"
526
526
  fi
527
- # C17 分隔線幾何:vertical Separator 無 mx- = 黏按鈕(action-bar.spec.md「分隔線幾何」h-6 mx-1;
528
- # ButtonGroup 內用 <ButtonDivider/>(mx built-in))。
527
+ # C17 分隔線幾何:vertical Separator 無 mx- = 黏按鈕。2026-08-07 起 action region 群組線的
528
+ # 唯一實作是 <ButtonDivider/>(長度由「該列最高元件 − inset」自動算,mx 內建);Separator 只剩
529
+ # 版面切分用途,那類仍需自帶左右間距(action-bar.spec.md「分隔線幾何」)。
529
530
  SEP_TAGS=$(echo "$CONTENT" | grep -oE '<(DS\.)?Separator[^>]*orientation=["'"'"']vertical["'"'"'][^>]*' || true)
530
531
  if [ -n "$SEP_TAGS" ] && grep -qv 'mx-' <<<"$SEP_TAGS" && \
531
532
  ! grep -q '@separator-geometry-ok:' <<<"$CONTENT"; then
532
- VIOLATIONS="${VIOLATIONS} - vertical <Separator> 無 mx- = 分隔線黏按鈕 → toolbar className=\"h-6 mx-1\"(dense h-5 mx-1);ButtonGroup 內用 <ButtonDivider/>(action-bar.spec.md「分隔線幾何」)。版面切分 divider → @separator-geometry-ok: <rationale>\n"
533
+ VIOLATIONS="${VIOLATIONS} - vertical <Separator> 無 mx- = 分隔線黏按鈕 → action region 群組線改用 <ButtonDivider/>(長度自動、間距內建);純版面切分才用 Separator 並自帶 mx-(action-bar.spec.md「分隔線幾何」)。版面切分 divider → @separator-geometry-ok: <rationale>\n"
533
534
  fi
534
535
  # C20 連結字手刻 underline:color.spec.md「Action — Primary」= hover 換色不用底線;
535
536
  # dropzone 連結走 FileUpload children slot。no-underline / hover:underline 皆不中此 regex 形。
@@ -85,8 +85,9 @@ EOF_PH
85
85
  exit 2
86
86
  fi
87
87
 
88
- # 2026-07-10 批次 A:divider 幾何 canonical(h-6 mx-1 / h-5 mx-1,action-bar.spec.md「分隔線幾何」)
89
- # 是**正解**,不該被 magic-number 攔(原本懲罰 canonical 修法、放行違規 = 治理自我打架)。
88
+ # 2026-07-10 批次 A:divider 幾何是**正解**,不該被 magic-number 攔(原本懲罰 canonical 修法、
89
+ # 放行違規 = 治理自我打架)。2026-08-07 更新:action region 群組線的長度已由 ButtonDivider
90
+ # --action-divider-* token 自動算,不再出現硬值;此處放行的只剩版面切分 Separator 的間距寫法。
90
91
  governance_hook_grep_capture MAGIC_LINES_FILTERED 'layout-space canonical-divider filter failed' \
91
92
  "$MAGIC_LINES" -vE '(Separator|ButtonDivider)'
92
93
  MAGIC_LINES="$MAGIC_LINES_FILTERED"
@@ -104,20 +104,20 @@
104
104
  },
105
105
  "providerLifecycle": {
106
106
  "schemaVersion": 4,
107
- "releaseVersion": "0.1.0-beta.117",
107
+ "releaseVersion": "0.1.0-beta.119",
108
108
  "immutableHead": {
109
109
  "providerInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
110
- "releaseVersion": "0.1.0-beta.116",
111
- "snapshotSha256": "f022897462d32b7e633c5f9d8e58ee9efe41e641ae42ab52668c4e3f360e2f9d"
110
+ "releaseVersion": "0.1.0-beta.118",
111
+ "snapshotSha256": "1967a89f9de7202ac0596965af2d8fa5af9cacf7f3e19c1be5f48be047d805fc"
112
112
  },
113
- "ledgerSha256": "a3d9e38cc1ed799f058ae771d35d951004158ac2874eba0fddb52b509de33dda",
114
- "previousSnapshotSha256": "f022897462d32b7e633c5f9d8e58ee9efe41e641ae42ab52668c4e3f360e2f9d",
115
- "currentSnapshotSha256": "e03b95342944512f9c084c0ac5fe5fba4b688924758ce725f16504eaec8af5a4",
113
+ "ledgerSha256": "d183167f2c64fe6f4d00ac13f0d35976b79dc15c2d3d0ad3573b82476a70fe1c",
114
+ "previousSnapshotSha256": "1967a89f9de7202ac0596965af2d8fa5af9cacf7f3e19c1be5f48be047d805fc",
115
+ "currentSnapshotSha256": "89a97b3c47da235aee5ef83fab8836d26952facc015a9d98b17b2194a07d31ec",
116
116
  "currentProviderInventorySha256": "86e9d0668cb54ad1bf05c36e78b4aa80417d5f468c929dd3ddcf69c1afbf32e4",
117
117
  "retiredProviders": [],
118
118
  "currentSnapshot": {
119
- "releaseVersion": "0.1.0-beta.117",
120
- "previousSnapshotSha256": "f022897462d32b7e633c5f9d8e58ee9efe41e641ae42ab52668c4e3f360e2f9d",
119
+ "releaseVersion": "0.1.0-beta.119",
120
+ "previousSnapshotSha256": "1967a89f9de7202ac0596965af2d8fa5af9cacf7f3e19c1be5f48be047d805fc",
121
121
  "providers": [
122
122
  {
123
123
  "id": "claude",
@@ -193,8 +193,8 @@
193
193
  "retiredProviders": []
194
194
  },
195
195
  "immutableHeadSnapshot": {
196
- "releaseVersion": "0.1.0-beta.116",
197
- "previousSnapshotSha256": "25bf1bb5c751623812af2c5b35b8c47bac9a93a718aa2b9222a6b3294296446d",
196
+ "releaseVersion": "0.1.0-beta.118",
197
+ "previousSnapshotSha256": "e03b95342944512f9c084c0ac5fe5fba4b688924758ce725f16504eaec8af5a4",
198
198
  "providers": [
199
199
  {
200
200
  "id": "claude",
@@ -524,12 +524,13 @@ if grep -qE '>[^<]*\{[a-zA-Z0-9_.]+\} *\(\{[a-zA-Z0-9_.]*(count|Count|length|tot
524
524
  ! grep -q '@count-in-label-ok:' <<<"$CONTENT"; then
525
525
  VIOLATIONS="${VIOLATIONS} - 計數括號串接 {label} ({count}) → row header 用「標題左、純數字右 space-between + tabular-nums」(item-anatomy.spec.md:176);互動元素(tab/chip/button)用 badge slot。aria/純文字匯出層 → @count-in-label-ok: <rationale>\n"
526
526
  fi
527
- # C17 分隔線幾何:vertical Separator 無 mx- = 黏按鈕(action-bar.spec.md「分隔線幾何」h-6 mx-1;
528
- # ButtonGroup 內用 <ButtonDivider/>(mx built-in))。
527
+ # C17 分隔線幾何:vertical Separator 無 mx- = 黏按鈕。2026-08-07 起 action region 群組線的
528
+ # 唯一實作是 <ButtonDivider/>(長度由「該列最高元件 − inset」自動算,mx 內建);Separator 只剩
529
+ # 版面切分用途,那類仍需自帶左右間距(action-bar.spec.md「分隔線幾何」)。
529
530
  SEP_TAGS=$(echo "$CONTENT" | grep -oE '<(DS\.)?Separator[^>]*orientation=["'"'"']vertical["'"'"'][^>]*' || true)
530
531
  if [ -n "$SEP_TAGS" ] && grep -qv 'mx-' <<<"$SEP_TAGS" && \
531
532
  ! grep -q '@separator-geometry-ok:' <<<"$CONTENT"; then
532
- VIOLATIONS="${VIOLATIONS} - vertical <Separator> 無 mx- = 分隔線黏按鈕 → toolbar className=\"h-6 mx-1\"(dense h-5 mx-1);ButtonGroup 內用 <ButtonDivider/>(action-bar.spec.md「分隔線幾何」)。版面切分 divider → @separator-geometry-ok: <rationale>\n"
533
+ VIOLATIONS="${VIOLATIONS} - vertical <Separator> 無 mx- = 分隔線黏按鈕 → action region 群組線改用 <ButtonDivider/>(長度自動、間距內建);純版面切分才用 Separator 並自帶 mx-(action-bar.spec.md「分隔線幾何」)。版面切分 divider → @separator-geometry-ok: <rationale>\n"
533
534
  fi
534
535
  # C20 連結字手刻 underline:color.spec.md「Action — Primary」= hover 換色不用底線;
535
536
  # dropzone 連結走 FileUpload children slot。no-underline / hover:underline 皆不中此 regex 形。
@@ -85,8 +85,9 @@ EOF_PH
85
85
  exit 2
86
86
  fi
87
87
 
88
- # 2026-07-10 批次 A:divider 幾何 canonical(h-6 mx-1 / h-5 mx-1,action-bar.spec.md「分隔線幾何」)
89
- # 是**正解**,不該被 magic-number 攔(原本懲罰 canonical 修法、放行違規 = 治理自我打架)。
88
+ # 2026-07-10 批次 A:divider 幾何是**正解**,不該被 magic-number 攔(原本懲罰 canonical 修法、
89
+ # 放行違規 = 治理自我打架)。2026-08-07 更新:action region 群組線的長度已由 ButtonDivider
90
+ # --action-divider-* token 自動算,不再出現硬值;此處放行的只剩版面切分 Separator 的間距寫法。
90
91
  governance_hook_grep_capture MAGIC_LINES_FILTERED 'layout-space canonical-divider filter failed' \
91
92
  "$MAGIC_LINES" -vE '(Separator|ButtonDivider)'
92
93
  MAGIC_LINES="$MAGIC_LINES_FILTERED"
@@ -253,7 +253,7 @@ expect_silent "34. C18 aria-label 括號形 → silent"
253
253
  run_hook "$PROD_TSX" 'export const B = () => <><Button>A</Button><Separator orientation="vertical" className="h-6" /><Button>B</Button></>'
254
254
  expect_block "35. C17 vertical Separator 無 mx → BLOCK"
255
255
 
256
- # 36. C17 NEGATIVE:h-6 mx-1 canonical → silent
256
+ # 36. C17 NEGATIVE:版面切分 Separator 自帶 mx- → silent(action region 群組線改用 ButtonDivider)
257
257
  run_hook "$PROD_TSX" 'export const B = () => <><Button>A</Button><Separator orientation="vertical" className="h-6 mx-1" /><Button>B</Button></>'
258
258
  expect_silent "36. C17 h-6 mx-1 canonical → silent"
259
259
 
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "git+https://github.com/ajenchen/design-system.git"
6
6
  },
7
- "version": "0.1.0-beta.117",
7
+ "version": "0.1.0-beta.119",
8
8
  "private": false,
9
9
  "description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
10
10
  "type": "module",
@@ -64,10 +64,37 @@ const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
64
64
  )
65
65
  ButtonGroup.displayName = 'ButtonGroup'
66
66
 
67
+ export interface ButtonDividerProps extends React.HTMLAttributes<HTMLDivElement> {
68
+ /**
69
+ * 群組排列方向。橫排群組畫直線(預設),直排群組畫橫線。寫死方向會讓
70
+ * `<ButtonGroup orientation="vertical">` 出現方向錯誤的線。
71
+ */
72
+ orientation?: 'horizontal' | 'vertical'
73
+ /**
74
+ * 是否對輔助科技宣告語意。預設 `true`(純視覺)→ `role="none"`,與 Separator primitive
75
+ * 預設一致(separator.spec.md:102「純視覺分隔,SR 不朗讀」)。動作群組的分組語意已由排列
76
+ * 順序與間距表達,線本身不切分內容,故不朗讀。
77
+ */
78
+ decorative?: boolean
79
+ }
80
+
67
81
  /**
68
- * ButtonDivider — 按鈕群組內的分隔線
82
+ * ButtonDivider — action region 群組分隔線
83
+ *
84
+ * **幾何 SSOT** → `patterns/action-bar/action-bar.spec.md`「分隔線幾何」:
85
+ * 線長 = 所在那一列**最高元件**的高度 − `--action-divider-inset`(8px),不短於
86
+ * `--action-divider-min`(16px),垂直置中。
69
87
  *
70
- * 自身左右各 4px,與相鄰按鈕形成 8px(gap)+ 4px(自身)= 12px 的視覺距離。
88
+ * 兩層構造是必要的而非包裝:外層 `self-stretch` 取「該列最高元件」當基準——同一列的每條線
89
+ * 因此必然等長、不會參差;內層畫線並套 inset 與地板,置中由外層 `items-center` 負責,所以
90
+ * 地板生效時線仍置中。單層寫法(`self-stretch` + `min-h`)會被釘在行首,地板一啟動線就歪掉。
91
+ * 外層佔位與舊寫法完全相同,改動不位移任何同列元素。
92
+ *
93
+ * **前提**:必須放在自動高度的 cluster 內。直接置於固定高度容器(如 48px chrome header)時
94
+ * 基準會退化成容器高——CSS 無法在容器有明確高度時看見「最高手足」。由
95
+ * `scripts/test-action-divider-placement.mjs` 機械把關。
96
+ *
97
+ * **水平間距**:自身兩端各 4px,與 `gap-2` 容器合成 12px 視覺距離。
71
98
  *
72
99
  * @example
73
100
  * <ButtonGroup>
@@ -76,21 +103,37 @@ ButtonGroup.displayName = 'ButtonGroup'
76
103
  * <Button variant="primary" danger size="sm" iconOnly startIcon={Trash2} aria-label="刪除" />
77
104
  * </ButtonGroup>
78
105
  */
79
- const ButtonDivider = React.forwardRef<
80
- HTMLDivElement,
81
- React.HTMLAttributes<HTMLDivElement>
82
- >(({ className, ...props }, ref) => (
83
- <div
84
- ref={ref}
85
- role="separator"
86
- className={cn(
87
- 'self-stretch w-px mx-1 my-1',
88
- 'bg-divider',
89
- className,
90
- )}
91
- {...props}
92
- />
93
- ))
106
+ const ButtonDivider = React.forwardRef<HTMLDivElement, ButtonDividerProps>(
107
+ ({ className, orientation = 'vertical', decorative = true, ...props }, ref) => {
108
+ const isVertical = orientation === 'vertical'
109
+ return (
110
+ <div
111
+ ref={ref}
112
+ role={decorative ? 'none' : 'separator'}
113
+ aria-orientation={decorative ? undefined : orientation}
114
+ data-orientation={orientation}
115
+ // 穩定標記:讓像素量測(scripts/measure-action-dividers.mjs)只鎖 action region 群組線,
116
+ // 不會誤抓版面切分家族(DataTable 欄界 / 欄寬把手 / Steps connector — 那些不適用本幾何)。
117
+ data-action-divider=""
118
+ className={cn(
119
+ 'flex shrink-0 self-stretch',
120
+ isVertical ? 'w-px mx-1 items-center' : 'h-px my-1 justify-center',
121
+ className,
122
+ )}
123
+ {...props}
124
+ >
125
+ <div
126
+ className={cn(
127
+ 'bg-divider',
128
+ isVertical
129
+ ? 'w-px h-[calc(100%-var(--action-divider-inset))] min-h-[var(--action-divider-min)]'
130
+ : 'h-px w-[calc(100%-var(--action-divider-inset))] min-w-[var(--action-divider-min)]',
131
+ )}
132
+ />
133
+ </div>
134
+ )
135
+ },
136
+ )
94
137
  ButtonDivider.displayName = 'ButtonDivider'
95
138
 
96
139
  export { ButtonGroup, ButtonDivider }
@@ -17,7 +17,7 @@ import {
17
17
  import { cn } from '@/lib/utils'
18
18
  import { OVERLAY_SIDE_OFFSET } from '@/design-system/tokens/elevation/overlay-geometry'
19
19
  import { Button } from '@/design-system/components/Button/button'
20
- import { Separator } from '@/design-system/components/Separator/separator'
20
+ import { ButtonDivider } from '@/design-system/components/Button/button-group'
21
21
  import { Input } from '@/design-system/components/Input/input'
22
22
  import { Empty } from '@/design-system/components/Empty/empty'
23
23
  import { AspectRatio } from '@/design-system/components/AspectRatio/aspect-ratio'
@@ -390,9 +390,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
390
390
  <>
391
391
  {/* Zoom group:-/%/+/▼ 屬同類「縮放」操作,群組並在右側加分隔線跟其他動作分群 */}
392
392
  <ZoomInput value={zoom} onChange={onZoomChange} onFit={onFit} labels={labels} />
393
- {/* zoom group → next action group divider(action-bar canonical;v11 升級成 Separator
394
- 元件,對齊 separator.spec.md「consumer 手動放置 toolbar 群組分隔線 = 用 Separator」)*/}
395
- <Separator orientation="vertical" className="h-6 mx-1" />
393
+ {/* zoom group → next action group divider。幾何 SSOT = action-bar.spec.md「分隔線幾何」:
394
+ 線長由 ButtonDivider 依「該列最高元件 inset」自動算,不再手寫高度(舊 h-6 是寫死值,
395
+ DS 主要實作的 ButtonDivider 形成兩套機制)。*/}
396
+ <ButtonDivider />
396
397
  </>
397
398
  )}
398
399
  <Button
@@ -415,8 +416,8 @@ const Toolbar: React.FC<ToolbarProps> = ({
415
416
  />
416
417
  )}
417
418
  {/* action-bar canonical:dismiss 前加分隔線跟其他動作分群(info/download = action group,
418
- close = dismiss group;v11 升級成 Separator,對齊 separator.spec.md canonical)*/}
419
- <Separator orientation="vertical" className="h-6 mx-1" />
419
+ close = dismiss group)。幾何同上,由 ButtonDivider 自動算。*/}
420
+ <ButtonDivider />
420
421
  {/* Close X 走 dismiss canonical(`<Button iconOnly dismiss />`)——對齊 CLAUDE.md
421
422
  `button.spec.md`「Dismiss 視覺類」+ `patterns/element-anatomy/item-anatomy.spec.md`
422
423
  「Dismiss canonical」:chrome corner close X = Button dismiss,不是 Inline Action。 */}
@@ -78,7 +78,7 @@ Consumer 在 JSX 裡明確放置分隔線的場景:
78
78
 
79
79
  ## 邊界案例
80
80
 
81
- - **Vertical 方向**:`h-full` 取父容器高度——父容器無確定高度(auto)時 separator 高度為 0、不可見;需父容器有確定高度或 flex row 的 stretch 對齊。**例外(toolbar / action region 群組分隔)**:實務上一律**縮短高度**(`h-6 mx-1`),不 full-height 幾何 SSOT → `patterns/action-bar/action-bar.spec.md`「分隔線幾何」段(2026-07-08 codify;版面切分 divider 不適用、零 gap)
81
+ - **Vertical 方向**:`h-full` 取父容器高度——父容器無確定高度(auto)時 separator 高度為 0、不可見;需父容器有確定高度或 flex row 的 stretch 對齊。**例外(toolbar / action region 群組分隔)**:不走 Separator —— 該情境的唯一實作是 `<ButtonDivider />`(`components/Button/button-group.tsx`),線長由「該列最高元件 inset」自動算,幾何 SSOT → `patterns/action-bar/action-bar.spec.md`「分隔線幾何」段(2026-08-07 改寫;版面切分 divider 不適用、零 gap)
82
82
  - **空容器 / 0 寬**:horizontal `w-full` 隨父寬,父寬為 0 時無可見線;Separator 不自帶 min-width / min-height
83
83
  - **厚度固定 1px**(`h-px` / `w-px`)、非互動無 hover / focus——無 size / state 變體(見下「為何無 …」段)
84
84
 
@@ -88,7 +88,7 @@ Separator 是**視覺分隔 primitive**(一條 1px 線),結構極薄:
88
88
 
89
89
  - **無 Inspector**:Separator 唯一變因是 `orientation`(horizontal / vertical),已在 `TokenMatrix` 對照呈現。互動 Inspector 無進一步可調 prop。
90
90
  - **無 ColorMatrix**:Separator 固定用 `--divider` token(比 border 更淡的語意分隔色),dark mode 由 semantic token 自動切換。無變體。加 color variant 會誤用成「狀態訊號」(分隔應是中性的)。
91
- - **無 SizeMatrix**:Separator 固定 1px 厚度(線應該是線),長度由 container 決定(`w-full` / `h-full`;toolbar 群組分隔例外縮短,見「邊界案例」+ action-bar.spec.md「分隔線幾何」)。無 sm/md/lg tier。
91
+ - **無 SizeMatrix**:Separator 固定 1px 厚度(線應該是線),長度由 container 決定(`w-full` / `h-full`;toolbar 群組分隔不走本元件,見「邊界案例」+ action-bar.spec.md「分隔線幾何」)。無 sm/md/lg tier。
92
92
  - **無 StateBehavior**:非互動元件,無 hover / focus / active / selected / disabled。
93
93
 
94
94
  對應 anatomy story:保留 `Overview` + `TokenMatrix`(horizontal × vertical + `--divider` vs `--border` 用法對照)。
package/src/index.ts CHANGED
@@ -86,6 +86,7 @@ export {
86
86
  buttonVariants,
87
87
  } from './components/Button/index'
88
88
  export type {
89
+ ButtonDividerProps,
89
90
  ButtonProps,
90
91
  } from './components/Button/index'
91
92
  export {
@@ -285,10 +285,13 @@ Icon 的目的是幫助辨識,不是視覺對稱。
285
285
 
286
286
  **版面切分的 divider(pane `border-l` / 全高欄界 / 區塊邊界)不適用本段 — 那類是 border,兩側零 gap、貼齊版面。**
287
287
 
288
- - **幾何** = `<Separator orientation="vertical" className="h-6 mx-1" />` 於 `gap-2` 容器 → 兩側各 8+4 = 12px 對稱。DS 自家雙錨:ChromeHeader 容器自帶 gap-2(chrome-header.tsx:116)+ FileViewer toolbar(file-viewer.tsx:390,414)/ header-canonical.stories.tsx:68;ButtonGroup 同款幾何 = ButtonDivider `mx-1` 於 gap-2 群組(button-group.tsx:26「8px(gap)+ 4px(自身)= 12px」)。
289
- - **高度縮短、不 full-height**:`h-6`(24px)配 sm 控件(28px)矮一級;dense 情境(xs 控件 / gap-1 容器)降 `h-5 mx-1`(item-anatomy.stories.tsx inline-action 群先例)。原則:「divider 高 ≈ 相鄰控件內容高,比控件盒矮一級」。
290
- - **世界級對照**(對稱 + 縮短 = 4/4 共識):Ant Divider vertical 兩側 8px + 0.9em(https://github.com/ant-design/ant-design/blob/master/components/divider/style/index.ts)/ MUI toolbar demo `mx:0.5 my:1`(https://github.com/mui/material-ui/blob/master/docs/data/material/components/toggle-button/CustomizedDividers.tsx)/ Radix Toolbar demo `margin: 0 10px`(https://github.com/radix-ui/website/blob/main/components/demos/toolbar/css/styles.css)/ shadcn `h-4 mx-2`(sidebar-02 block)。DS 取自家既有雙錨值(M23 DS canonical 優先)。
291
- - **禁**:`h-full`(觸頂觸底)/ 單側 margin(不對稱)
288
+ **唯一實作 = `<ButtonDivider />`**(`components/Button/button-group.tsx`)。consumer DS 元件都不得手寫高度,也不再用 `<Separator>` action region 群組線 —— 兩套機制是 2026-08-07 之前長度四散(13/16/24px)的根因。
289
+
290
+ - **長度**(垂直軸唯一定義):**線長 = 所在那一列「最高元件」的高度 `--action-divider-inset`(8px),不短於 `--action-divider-min`(16px),垂直置中**。基準是最高元件而非容器,故同列每條線必然等長(不參差),換尺寸階時自動跟著走:xs 24→16 / sm 28→20 / md 32→24 / lg 密度 36→28,比值 0.67–0.78。inset 8px 是我們尺寸階唯一可行值(世界級「撐滿再減」的兩家各減 16px,但最小控件 32px;套到我們 24px xs 階只剩 8px,比 Button 圖示 16px 還矮一半)。min 16px 不是新常數,是**本公式在最小控件階的取值**(`--field-height-xs` 24px − 8px),故地板永不覆寫公式,只在「容器宣稱高度比自身內容還矮」時(如 `ItemSuffix` `h-[1lh]`=21px 卻裝 24px 控件)修回誠實值;`--field-height-xs` 不隨 density 縮放,min 在任何密度成立。Token SSOT = `tokens/uiSize/uiSize.css`。
291
+ - **前提**:必須住在**自動高度的 cluster** 內。直接置於固定高度容器(48/56px chrome header)時基準會退化成容器高 —— CSS 無法在容器有明確高度時看見「最高手足」。
292
+ - **水平間距** = 自身兩端各 4px 於 `gap-2` 容器 → 兩側各 8+4 = 12px 對稱。
293
+ - **世界級對照**(長度):做過真工具列的四家全部矮於控件,無一等高 —— VS Code 固定 16px(`height:16px`,同檔 `.codicon` 亦 16px,https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/ui/actionbar/actionbar.css)/ Primer `ActionBar.VerticalDivider` 固定 20px + `margin-top: calc((控件高 − 20px)/2)` 置中(https://github.com/primer/react/blob/main/packages/react/src/ActionBar/ActionBar.module.css)/ Fluent `ToolbarDivider` 因 Toolbar `alignItems:center` 落在 `minHeight: 20px` 地板(https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-divider/library/src/components/Divider/useDividerStyles.styles.ts)/ JetBrains `ActionToolbarImpl` 以最高按鈕反推(maxButtonHeight − 5px)。等長於控件的先例(Ant `Space.Compact` / Chakra `Group attached` / SLDS button-group / Atlassian split button)全屬**控件內部接縫**——線就是控件自己的邊框,與群組分隔線是不同問題。語意依據:Fluent「block dividers → strict distinction;inset → imply closer relationships」+ Material「full width 分隔不相關大區塊,inset 分隔區塊內相關內容」;工具列的兩個 action 群是同列近親 → 屬 inset 定義域。
294
+ - **禁**:`h-full` / 手寫任何高度 class / 單側 margin / 用 `<Separator>` 畫 action region 群組線。
292
295
 
293
296
  ---
294
297
 
@@ -35,6 +35,22 @@
35
35
  --field-control-py-md: calc((var(--field-height-md) - 1lh) / 2 - 1px);
36
36
  --field-control-py-lg: calc((var(--field-height-lg) - 1lh) / 2 - 1px);
37
37
 
38
+ /* Action-region 群組分隔線幾何 — 線長 = 該列最高元件 − inset,不短於 min,垂直置中。
39
+ 基準是「該列最高元件」而非容器:同一列的每條線因此必然等長(不參差),換一個尺寸階的控件
40
+ 時線自動跟著走,比值維持 0.67–0.78,落在世界級 action bar 帶內(VS Code 0.73 /
41
+ Primer ActionBar 0.63 / JetBrains 0.77–0.86)。
42
+ inset 取 8px 是我們尺寸階唯一可行值:世界級「撐滿再減」的兩家(MUI variant="middle"、
43
+ Primer SegmentedControl)各減 16px,但他們最小控件 32px;套到我們 24px 的 xs 階只剩 8px,
44
+ 比 Button 圖示(16px)還矮一半。
45
+ min 不是新常數,它就是本公式在最小控件階的取值(--field-height-xs 24px − inset 8px = 16px),
46
+ 所以地板永遠不覆寫公式,只在「容器宣稱的高度比它自己的內容還矮」時把結果修回誠實值。
47
+ --field-height-xs 明文不隨 density 縮放,故 min 在任何密度都成立;16px 同時等於 Button 的
48
+ 圖示尺寸(button.tsx:416),與 VS Code 工具列分隔線同值(actionbar.css height:16px,同檔
49
+ .codicon 亦 16px)。
50
+ 消費者:ButtonDivider(唯一實作點)。由 scripts/test-action-divider-placement.mjs 機械鎖。 */
51
+ --action-divider-inset: 0.5rem; /* 8px — 兩端各 4px */
52
+ --action-divider-min: calc(var(--field-height-xs) - var(--action-divider-inset)); /* 16px */
53
+
38
54
  /* Field horizontal padding — 固定 12px(不隨 size/density);form-context field 左右內距 SSOT。
39
55
  跟 --table-cell-px(cell-context)同 -px 慣例;Input/Select/Combobox/DatePicker/TimePicker/Textarea
40
56
  + tag 模式右緣 re-assert 全消費此 token(取代散落的 px-3 / inline 0.75rem,M17 SSOT 可傳播)。 */