@lobehub/ui 6.0.0-beta.5 → 6.0.0-beta.7

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 (172) hide show
  1. package/es/ActionIcon/style.mjs +1 -1
  2. package/es/ActionIcon/style.mjs.map +1 -1
  3. package/es/ColorSwatches/ColorSwatches.mjs +17 -22
  4. package/es/ColorSwatches/ColorSwatches.mjs.map +1 -1
  5. package/es/ColorSwatches/CustomPicker.mjs +101 -0
  6. package/es/ColorSwatches/CustomPicker.mjs.map +1 -0
  7. package/es/ColorSwatches/index.d.mts +2 -2
  8. package/es/ColorSwatches/marker.stylex.mjs +9 -0
  9. package/es/ColorSwatches/marker.stylex.mjs.map +1 -0
  10. package/es/ColorSwatches/style.mjs +36 -0
  11. package/es/ColorSwatches/style.mjs.map +1 -1
  12. package/es/ColorSwatches/type.d.mts +11 -2
  13. package/es/EditableText/EditableText.mjs +1 -1
  14. package/es/Grid/style.mjs +1 -1
  15. package/es/Grid/style.mjs.map +1 -1
  16. package/es/Image/type.d.mts +1 -0
  17. package/es/Image/viewer/ImageViewer.mjs +1 -0
  18. package/es/Image/viewer/ImageViewer.mjs.map +1 -1
  19. package/es/Image/viewer/Toolbar.mjs +19 -6
  20. package/es/Image/viewer/Toolbar.mjs.map +1 -1
  21. package/es/Image/viewer/ViewerChrome.mjs.map +1 -1
  22. package/es/Input/style.mjs +3 -3
  23. package/es/Input/style.mjs.map +1 -1
  24. package/es/Text/style.d.mts +3 -3
  25. package/es/awesome/Grainient/Grainient.d.mts +11 -0
  26. package/es/awesome/Grainient/Grainient.mjs +314 -0
  27. package/es/awesome/Grainient/Grainient.mjs.map +1 -0
  28. package/es/awesome/Grainient/index.d.mts +3 -0
  29. package/es/awesome/Grainient/style.mjs +23 -0
  30. package/es/awesome/Grainient/style.mjs.map +1 -0
  31. package/es/awesome/Grainient/type.d.mts +121 -0
  32. package/es/awesome/index.d.mts +4 -1
  33. package/es/awesome/index.mjs +2 -1
  34. package/es/dashboard/AuthLayout/Aside.d.mts +10 -0
  35. package/es/dashboard/AuthLayout/Aside.mjs +31 -0
  36. package/es/dashboard/AuthLayout/Aside.mjs.map +1 -0
  37. package/es/dashboard/AuthLayout/AuthLayout.d.mts +3 -1
  38. package/es/dashboard/AuthLayout/AuthLayout.mjs +93 -37
  39. package/es/dashboard/AuthLayout/AuthLayout.mjs.map +1 -1
  40. package/es/dashboard/AuthLayout/index.d.mts +3 -2
  41. package/es/dashboard/AuthLayout/style.mjs +98 -1
  42. package/es/dashboard/AuthLayout/style.mjs.map +1 -1
  43. package/es/dashboard/AuthLayout/type.d.mts +38 -2
  44. package/es/dashboard/AuthTools/AuthLocaleSwitch.d.mts +10 -0
  45. package/es/dashboard/AuthTools/AuthLocaleSwitch.mjs +33 -0
  46. package/es/dashboard/AuthTools/AuthLocaleSwitch.mjs.map +1 -0
  47. package/es/dashboard/AuthTools/AuthThemeSwitch.d.mts +10 -0
  48. package/es/dashboard/AuthTools/AuthThemeSwitch.mjs +59 -0
  49. package/es/dashboard/AuthTools/AuthThemeSwitch.mjs.map +1 -0
  50. package/es/dashboard/AuthTools/index.d.mts +4 -0
  51. package/es/dashboard/AuthTools/style.mjs +27 -0
  52. package/es/dashboard/AuthTools/style.mjs.map +1 -0
  53. package/es/dashboard/AuthTools/type.d.mts +29 -0
  54. package/es/dashboard/ChartGrid/ChartGrid.d.mts +10 -0
  55. package/es/dashboard/ChartGrid/ChartGrid.mjs +19 -0
  56. package/es/dashboard/ChartGrid/ChartGrid.mjs.map +1 -0
  57. package/es/dashboard/ChartGrid/index.d.mts +3 -0
  58. package/es/dashboard/ChartGrid/style.mjs +18 -0
  59. package/es/dashboard/ChartGrid/style.mjs.map +1 -0
  60. package/es/dashboard/ChartGrid/type.d.mts +8 -0
  61. package/es/dashboard/ConsoleNav/Skeleton.d.mts +15 -0
  62. package/es/dashboard/ConsoleNav/Skeleton.mjs +82 -0
  63. package/es/dashboard/ConsoleNav/Skeleton.mjs.map +1 -0
  64. package/es/dashboard/ConsoleNav/index.d.mts +3 -2
  65. package/es/dashboard/ConsoleNav/style.mjs +18 -0
  66. package/es/dashboard/ConsoleNav/style.mjs.map +1 -1
  67. package/es/dashboard/ConsoleNav/type.d.mts +13 -0
  68. package/es/dashboard/ConsoleShell/ConsoleShell.d.mts +1 -1
  69. package/es/dashboard/ConsoleShell/ConsoleShell.mjs +26 -6
  70. package/es/dashboard/ConsoleShell/ConsoleShell.mjs.map +1 -1
  71. package/es/dashboard/ConsoleShell/style.mjs +10 -0
  72. package/es/dashboard/ConsoleShell/style.mjs.map +1 -1
  73. package/es/dashboard/ConsoleShell/type.d.mts +11 -0
  74. package/es/dashboard/FilterSidebar/FilterSidebar.mjs +18 -17
  75. package/es/dashboard/FilterSidebar/FilterSidebar.mjs.map +1 -1
  76. package/es/dashboard/FilterStrip/FilterStrip.d.mts +11 -0
  77. package/es/dashboard/FilterStrip/FilterStrip.mjs +55 -0
  78. package/es/dashboard/FilterStrip/FilterStrip.mjs.map +1 -0
  79. package/es/dashboard/FilterStrip/index.d.mts +3 -0
  80. package/es/dashboard/FilterStrip/style.mjs +78 -0
  81. package/es/dashboard/FilterStrip/style.mjs.map +1 -0
  82. package/es/dashboard/FilterStrip/type.d.mts +25 -0
  83. package/es/dashboard/LabelWithKey/LabelWithKey.d.mts +10 -0
  84. package/es/dashboard/LabelWithKey/LabelWithKey.mjs +18 -0
  85. package/es/dashboard/LabelWithKey/LabelWithKey.mjs.map +1 -0
  86. package/es/dashboard/LabelWithKey/index.d.mts +3 -0
  87. package/es/dashboard/LabelWithKey/type.d.mts +11 -0
  88. package/es/dashboard/MetricStrip/MetricCell.mjs +81 -0
  89. package/es/dashboard/MetricStrip/MetricCell.mjs.map +1 -0
  90. package/es/dashboard/MetricStrip/MetricStrip.d.mts +13 -0
  91. package/es/dashboard/MetricStrip/MetricStrip.mjs +47 -0
  92. package/es/dashboard/MetricStrip/MetricStrip.mjs.map +1 -0
  93. package/es/dashboard/MetricStrip/index.d.mts +3 -0
  94. package/es/dashboard/MetricStrip/style.mjs +156 -0
  95. package/es/dashboard/MetricStrip/style.mjs.map +1 -0
  96. package/es/dashboard/MetricStrip/type.d.mts +36 -0
  97. package/es/dashboard/PageSkeleton/PageSkeleton.mjs +20 -18
  98. package/es/dashboard/PageSkeleton/PageSkeleton.mjs.map +1 -1
  99. package/es/dashboard/PageSkeleton/style.mjs +1 -1
  100. package/es/dashboard/PageSkeleton/style.mjs.map +1 -1
  101. package/es/dashboard/PageToolbar/PageToolbar.d.mts +10 -0
  102. package/es/dashboard/PageToolbar/PageToolbar.mjs +22 -0
  103. package/es/dashboard/PageToolbar/PageToolbar.mjs.map +1 -0
  104. package/es/dashboard/PageToolbar/index.d.mts +3 -0
  105. package/es/dashboard/PageToolbar/style.mjs +36 -0
  106. package/es/dashboard/PageToolbar/style.mjs.map +1 -0
  107. package/es/dashboard/PageToolbar/type.d.mts +11 -0
  108. package/es/dashboard/PanelSurface/PanelSurface.d.mts +10 -0
  109. package/es/dashboard/PanelSurface/PanelSurface.mjs +40 -0
  110. package/es/dashboard/PanelSurface/PanelSurface.mjs.map +1 -0
  111. package/es/dashboard/PanelSurface/index.d.mts +3 -0
  112. package/es/dashboard/PanelSurface/style.mjs +25 -0
  113. package/es/dashboard/PanelSurface/style.mjs.map +1 -0
  114. package/es/dashboard/PanelSurface/type.d.mts +17 -0
  115. package/es/dashboard/Section/Section.d.mts +10 -0
  116. package/es/dashboard/Section/Section.mjs +81 -0
  117. package/es/dashboard/Section/Section.mjs.map +1 -0
  118. package/es/dashboard/Section/index.d.mts +3 -0
  119. package/es/dashboard/Section/style.mjs +31 -0
  120. package/es/dashboard/Section/style.mjs.map +1 -0
  121. package/es/dashboard/Section/type.d.mts +23 -0
  122. package/es/dashboard/StatCard/StatCard.d.mts +2 -1
  123. package/es/dashboard/StatCard/StatCard.mjs +28 -91
  124. package/es/dashboard/StatCard/StatCard.mjs.map +1 -1
  125. package/es/dashboard/StatCard/type.d.mts +16 -5
  126. package/es/dashboard/Surface/Surface.d.mts +4 -3
  127. package/es/dashboard/Surface/Surface.mjs +91 -8
  128. package/es/dashboard/Surface/Surface.mjs.map +1 -1
  129. package/es/dashboard/Surface/index.d.mts +3 -2
  130. package/es/dashboard/Surface/link.d.mts +16 -0
  131. package/es/dashboard/Surface/link.mjs +40 -0
  132. package/es/dashboard/Surface/link.mjs.map +1 -0
  133. package/es/dashboard/Surface/marker.stylex.mjs +9 -0
  134. package/es/dashboard/Surface/marker.stylex.mjs.map +1 -0
  135. package/es/dashboard/Surface/style.d.mts +2 -0
  136. package/es/dashboard/Surface/style.mjs +155 -14
  137. package/es/dashboard/Surface/style.mjs.map +1 -1
  138. package/es/dashboard/Surface/type.d.mts +29 -1
  139. package/es/dashboard/TableIconActions/TableIconActions.d.mts +17 -0
  140. package/es/dashboard/TableIconActions/TableIconActions.mjs +39 -0
  141. package/es/dashboard/TableIconActions/TableIconActions.mjs.map +1 -0
  142. package/es/dashboard/TableIconActions/index.d.mts +3 -0
  143. package/es/dashboard/TableIconActions/type.d.mts +17 -0
  144. package/es/dashboard/TitleWithTips/TitleWithTips.d.mts +10 -0
  145. package/es/dashboard/TitleWithTips/TitleWithTips.mjs +28 -0
  146. package/es/dashboard/TitleWithTips/TitleWithTips.mjs.map +1 -0
  147. package/es/dashboard/TitleWithTips/index.d.mts +3 -0
  148. package/es/dashboard/TitleWithTips/type.d.mts +10 -0
  149. package/es/dashboard/index.d.mts +38 -4
  150. package/es/dashboard/index.mjs +17 -3
  151. package/es/index.d.mts +3 -2
  152. package/es/index.mjs +5 -4
  153. package/es/style.css +339 -32
  154. package/es/styles/createGlobalStyle.mjs +1 -1
  155. package/es/styles/index.d.mts +3 -2
  156. package/es/styles/index.mjs +5 -4
  157. package/es/styles/theme/createLobeToken.d.mts +3 -2
  158. package/es/styles/theme/createLobeToken.mjs.map +1 -1
  159. package/es/styles/theme/customPrimary.d.mts +7 -0
  160. package/es/styles/theme/customPrimary.mjs +66 -0
  161. package/es/styles/theme/customPrimary.mjs.map +1 -0
  162. package/es/styles/theme/scope.d.mts +4 -3
  163. package/es/styles/theme/scope.mjs +30 -8
  164. package/es/styles/theme/scope.mjs.map +1 -1
  165. package/es/styles/theme/themeCss.d.mts +2 -0
  166. package/es/styles/theme/themeCss.mjs +9 -2
  167. package/es/styles/theme/themeCss.mjs.map +1 -1
  168. package/es/styles/theme/token/primary.mjs +6 -2
  169. package/es/styles/theme/token/primary.mjs.map +1 -1
  170. package/package.json +1 -1
  171. package/es/dashboard/StatCard/style.mjs +0 -105
  172. package/es/dashboard/StatCard/style.mjs.map +0 -1
@@ -14,7 +14,7 @@ const styles = {
14
14
  $$css: true
15
15
  },
16
16
  dangerRoot: {
17
- kMwMTN: "lb53gy9q lby09ll3 lbuvp0lo",
17
+ kMwMTN: "lbnn15ax lby09ll3 lbuvp0lo",
18
18
  $$css: true
19
19
  },
20
20
  glass: {
@@ -1 +1 @@
1
- {"version":3,"file":"style.mjs","names":[],"sources":["../../src/ActionIcon/style.ts"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\n\nimport { cssVar } from '@/styles/stylex/cssVar.stylex';\n\nconst hover = ':hover:not(:active)';\nconst pressed = ':active:not(:disabled, [aria-disabled=\"true\"])';\n\nexport const styles = stylex.create({\n active: {\n backgroundColor: cssVar.colorFillSecondary,\n },\n activeFill: {\n backgroundColor: { default: cssVar.colorFillSecondary, [pressed]: cssVar.colorFill },\n },\n activeOutlined: {\n backgroundColor: { 'default': cssVar.colorFillSecondary, ':hover': cssVar.colorFill },\n },\n dangerRoot: {\n color: {\n 'default': cssVar.colorTextTertiary,\n [hover]: cssVar.colorError,\n ':active': cssVar.colorErrorActive,\n },\n },\n glass: {\n backdropFilter: 'saturate(150%) blur(10px)',\n },\n root: {\n color: {\n 'default': cssVar.colorTextTertiary,\n [hover]: cssVar.colorTextSecondary,\n ':active': cssVar.colorText,\n },\n },\n shadow: {\n boxShadow: `0 1px 0 -1px ${cssVar.colorBorder}, 0 1px 2px -0.5px ${cssVar.colorBorder}, 0 2px 2px -1px ${cssVar.colorBorderSecondary}, 0 3px 6px -4px ${cssVar.colorBorderSecondary}`,\n },\n});\n\nexport const actionIconStyles = Object.fromEntries(\n Object.entries(styles).map(([key, value]) => [key, stylex.props(value).className ?? '']),\n) as Record<keyof typeof styles, string>;\n"],"mappings":";;AAKA,MAAa,SAAS;CACpB,QAAQ;EACN,QAAQ;EACR,OAAO;CACT;CACA,YAAY;EACV,QAAQ;EACR,OAAO;CACT;CACA,gBAAgB;EACd,QAAQ;EACR,OAAO;CACT;CACA,YAAY;EACV,QAAQ;EACR,OAAO;CACT;CACA,OAAO;EACL,OAAO;EACP,OAAO;CACT;CACA,MAAM;EACJ,QAAQ;EACR,OAAO;CACT;CACA,QAAQ;EACN,QAAQ;EACR,OAAO;CACT;AACF;AACA,MAAa,mBAAmB,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,OAAO,MAAM,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"style.mjs","names":[],"sources":["../../src/ActionIcon/style.ts"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\n\nimport { cssVar } from '@/styles/stylex/cssVar.stylex';\n\nconst hover = ':hover:not(:active)';\nconst pressed = ':active:not(:disabled, [aria-disabled=\"true\"])';\n\nexport const styles = stylex.create({\n active: {\n backgroundColor: cssVar.colorFillSecondary,\n },\n activeFill: {\n backgroundColor: { default: cssVar.colorFillSecondary, [pressed]: cssVar.colorFill },\n },\n activeOutlined: {\n backgroundColor: { 'default': cssVar.colorFillSecondary, ':hover': cssVar.colorFill },\n },\n dangerRoot: {\n color: {\n 'default': `color-mix(in srgb, ${cssVar.colorError} 66%, transparent)`,\n [hover]: cssVar.colorError,\n ':active': cssVar.colorErrorActive,\n },\n },\n glass: {\n backdropFilter: 'saturate(150%) blur(10px)',\n },\n root: {\n color: {\n 'default': cssVar.colorTextTertiary,\n [hover]: cssVar.colorTextSecondary,\n ':active': cssVar.colorText,\n },\n },\n shadow: {\n boxShadow: `0 1px 0 -1px ${cssVar.colorBorder}, 0 1px 2px -0.5px ${cssVar.colorBorder}, 0 2px 2px -1px ${cssVar.colorBorderSecondary}, 0 3px 6px -4px ${cssVar.colorBorderSecondary}`,\n },\n});\n\nexport const actionIconStyles = Object.fromEntries(\n Object.entries(styles).map(([key, value]) => [key, stylex.props(value).className ?? '']),\n) as Record<keyof typeof styles, string>;\n"],"mappings":";;AAKA,MAAa,SAAS;CACpB,QAAQ;EACN,QAAQ;EACR,OAAO;CACT;CACA,YAAY;EACV,QAAQ;EACR,OAAO;CACT;CACA,gBAAgB;EACd,QAAQ;EACR,OAAO;CACT;CACA,YAAY;EACV,QAAQ;EACR,OAAO;CACT;CACA,OAAO;EACL,OAAO;EACP,OAAO;CACT;CACA,MAAM;EACJ,QAAQ;EACR,OAAO;CACT;CACA,QAAQ;EACN,QAAQ;EACR,OAAO;CACT;AACF;AACA,MAAa,mBAAmB,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,OAAO,MAAM,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC"}
@@ -6,15 +6,15 @@ import Center from "../Flex/Center.mjs";
6
6
  import { Tooltip } from "../Tooltip/Tooltip.mjs";
7
7
  import { useControlledState } from "../hooks/useControlledState.mjs";
8
8
  import { safeReadableColor } from "../utils/safeReadableColor.mjs";
9
- import ColorPicker from "../ColorPicker/ColorPicker.mjs";
10
9
  import { styles } from "./style.mjs";
10
+ import CustomPicker from "./CustomPicker.mjs";
11
11
  import { useMemo } from "react";
12
12
  import { jsx, jsxs } from "react/jsx-runtime";
13
13
  import * as stylex from "@stylexjs/stylex";
14
14
  import { CheckIcon } from "lucide-react";
15
15
  import chroma from "chroma-js";
16
16
  //#region src/ColorSwatches/ColorSwatches.tsx
17
- const ColorSwatches = ({ enableColorPicker, enableColorSwatches = true, defaultValue, value, style, colors, onChange, size = 24, shape = "circle", texts, ref, ...rest }) => {
17
+ const ColorSwatches = ({ enableColorPicker, enableColorSwatches = true, defaultValue, value, style, colors, onChange, size = 24, shape = "circle", texts, renderPicker, ref, ...rest }) => {
18
18
  const [active, setActive] = useControlledState(defaultValue, {
19
19
  defaultValue,
20
20
  onChange,
@@ -22,6 +22,20 @@ const ColorSwatches = ({ enableColorPicker, enableColorSwatches = true, defaultV
22
22
  });
23
23
  const cssVariables = useMemo(() => ({ "--color-swatches-size": `${size}px` }), [size]);
24
24
  const isCustomActive = useMemo(() => active && active !== cssVar.colorPrimary && !colors.some((c) => c.color === active), [active, colors]);
25
+ const pickerProps = {
26
+ active: !!isCustomActive,
27
+ onChange: setActive,
28
+ presets: colors.map((c) => c.color),
29
+ shape,
30
+ size,
31
+ value: isCustomActive ? active : void 0
32
+ };
33
+ const defaultPicker = enableColorPicker && /* @__PURE__ */ jsx(CustomPicker, {
34
+ ...pickerProps,
35
+ current: active,
36
+ label: texts?.custom || "Custom",
37
+ standalone: !enableColorSwatches
38
+ });
25
39
  return /* @__PURE__ */ jsxs(FlexBasic_default, {
26
40
  horizontal: true,
27
41
  gap: 6,
@@ -62,26 +76,7 @@ const ColorSwatches = ({ enableColorPicker, enableColorSwatches = true, defaultV
62
76
  })
63
77
  })
64
78
  }, c?.key || i);
65
- }), enableColorPicker && /* @__PURE__ */ jsx(Tooltip, {
66
- title: texts?.custom || "Custom",
67
- children: /* @__PURE__ */ jsx("span", {
68
- style: { display: "inline-flex" },
69
- children: /* @__PURE__ */ jsx(ColorPicker, {
70
- presets: enableColorSwatches ? void 0 : colors.map((c) => c.color),
71
- value: isCustomActive ? active : void 0,
72
- onChangeComplete: setActive,
73
- children: /* @__PURE__ */ jsx("button", {
74
- "aria-label": texts?.custom || "Custom",
75
- type: "button",
76
- ...stylex.props(styles.picker, enableColorSwatches && styles.conic, !!isCustomActive && styles.activePicker),
77
- style: {
78
- background: enableColorSwatches ? void 0 : active,
79
- borderRadius: shape === "circle" ? "50%" : cssVar.borderRadius
80
- }
81
- })
82
- })
83
- })
84
- })]
79
+ }), enableColorPicker && (renderPicker ? renderPicker(pickerProps, defaultPicker) : defaultPicker)]
85
80
  });
86
81
  };
87
82
  ColorSwatches.displayName = "ColorSwatches";
@@ -1 +1 @@
1
- {"version":3,"file":"ColorSwatches.mjs","names":["Flexbox"],"sources":["../../src/ColorSwatches/ColorSwatches.tsx"],"sourcesContent":["'use client';\n\nimport * as stylex from '@stylexjs/stylex';\nimport chroma from 'chroma-js';\nimport { CheckIcon } from 'lucide-react';\nimport { type FC, useMemo } from 'react';\n\nimport { ColorPicker } from '@/ColorPicker';\nimport { Center, Flexbox } from '@/Flex';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport Icon from '@/Icon';\nimport { cssVar } from '@/styles/stylex/cssVar.stylex';\nimport Tooltip from '@/Tooltip';\nimport { safeReadableColor } from '@/utils/safeReadableColor';\n\nimport { styles } from './style';\nimport type { ColorSwatchesProps } from './type';\n\nconst ColorSwatches: FC<ColorSwatchesProps> = ({\n enableColorPicker,\n enableColorSwatches = true,\n defaultValue,\n value,\n style,\n colors,\n onChange,\n size = 24,\n shape = 'circle',\n texts,\n ref,\n ...rest\n}) => {\n const [active, setActive] = useControlledState(defaultValue, {\n defaultValue,\n onChange,\n value,\n });\n\n // Convert size prop to CSS variable\n const cssVariables = useMemo<Record<string, string>>(\n () => ({\n '--color-swatches-size': `${size}px`,\n }),\n [size],\n );\n\n const isCustomActive = useMemo(\n () => active && active !== cssVar.colorPrimary && !colors.some((c) => c.color === active),\n [active, colors],\n );\n\n return (\n <Flexbox\n horizontal\n gap={6}\n ref={ref}\n style={{\n ...cssVariables,\n flexWrap: 'wrap',\n ...style,\n }}\n {...rest}\n >\n {enableColorSwatches &&\n colors.map((c, i) => {\n const color = c.color || cssVar.colorPrimary;\n const isActive = (!active && !c.color) || color === active;\n // Check if color is transparent or CSS variable (which chroma can't parse)\n const isTransparent =\n c.color === 'transparent' ||\n (c.color &&\n !c.color.startsWith('var(') &&\n (() => {\n try {\n return chroma(c.color).alpha() === 0;\n } catch {\n return false;\n }\n })());\n return (\n <Tooltip key={c?.key || i} title={c.title}>\n <Center\n {...stylex.props(\n styles.container,\n !!isTransparent && styles.transparent,\n isActive && styles.activeContainer,\n )}\n style={{\n background: isTransparent ? undefined : color,\n borderRadius: shape === 'circle' ? '50%' : cssVar.borderRadius,\n }}\n onClick={() => setActive(c.color || undefined)}\n >\n {isActive && (\n <Icon\n color={`color-mix(in srgb, ${safeReadableColor(color)} 33%, transparent)`}\n icon={CheckIcon}\n size={{ size: 14, strokeWidth: 4 }}\n style={{\n pointerEvents: 'none',\n }}\n />\n )}\n </Center>\n </Tooltip>\n );\n })}\n {enableColorPicker && (\n <Tooltip title={texts?.custom || 'Custom'}>\n <span style={{ display: 'inline-flex' }}>\n <ColorPicker\n presets={enableColorSwatches ? undefined : colors.map((c) => c.color)}\n value={isCustomActive ? active : undefined}\n onChangeComplete={setActive}\n >\n <button\n aria-label={texts?.custom || 'Custom'}\n type=\"button\"\n {...stylex.props(\n styles.picker,\n enableColorSwatches && styles.conic,\n !!isCustomActive && styles.activePicker,\n )}\n style={{\n background: enableColorSwatches ? undefined : active,\n borderRadius: shape === 'circle' ? '50%' : cssVar.borderRadius,\n }}\n />\n </ColorPicker>\n </span>\n </Tooltip>\n )}\n </Flexbox>\n );\n};\n\nColorSwatches.displayName = 'ColorSwatches';\n\nexport default ColorSwatches;\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,MAAM,iBAAyC,EAC7C,mBACA,sBAAsB,MACtB,cACA,OACA,OACA,QACA,UACA,OAAO,IACP,QAAQ,UACR,OACA,KACA,GAAG,WACC;CACJ,MAAM,CAAC,QAAQ,aAAa,mBAAmB,cAAc;EAC3D;EACA;EACA;CACF,CAAC;CAGD,MAAM,eAAe,eAAuC,EAC1D,yBAAyB,GAAG,KAAK,IACnC,IAAI,CAAC,IAAI,CAAC;CACV,MAAM,iBAAiB,cAAc,UAAU,WAAW,OAAO,gBAAgB,CAAC,OAAO,MAAK,MAAK,EAAE,UAAU,MAAM,GAAG,CAAC,QAAQ,MAAM,CAAC;CACxI,OAAO,qBAACA,mBAAD;EAAS,YAAA;EAAW,KAAK;EAAQ;EAAK,OAAO;GAClD,GAAG;GACH,UAAU;GACV,GAAG;EACL;EAAG,GAAI;EAJA,UAAA,CAKF,uBAAuB,OAAO,KAAK,GAAG,MAAM;GAC7C,MAAM,QAAQ,EAAE,SAAS,OAAO;GAChC,MAAM,WAAW,CAAC,UAAU,CAAC,EAAE,SAAS,UAAU;GAElD,MAAM,gBAAgB,EAAE,UAAU,iBAAiB,EAAE,SAAS,CAAC,EAAE,MAAM,WAAW,MAAM,YAAY;IAClG,IAAI;KACF,OAAO,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,MAAM;IACrC,QAAQ;KACN,OAAO;IACT;GACF,EAAA,CAAG;GACH,OAAO,oBAAC,SAAD;IAA2B,OAAO,EAAE;IACnC,UAAA,oBAAC,QAAD;KAAQ,GAAI,OAAO,MAAM,OAAO,WAAW,CAAC,CAAC,iBAAiB,OAAO,aAAa,YAAY,OAAO,eAAe;KAAG,OAAO;MAClI,YAAY,gBAAgB,KAAA,IAAY;MACxC,cAAc,UAAU,WAAW,QAAQ,OAAO;KACpD;KAAG,eAAe,UAAU,EAAE,SAAS,KAAA,CAAS;KACvC,UAAA,YAAY,oBAAC,MAAD;MAAM,OAAO,sBAAsB,kBAAkB,KAAK,EAAE;MAAqB,MAAM;MAAW,MAAM;OACzH,MAAM;OACN,aAAa;MACf;MAAG,OAAO,EACR,eAAe,OACjB;KAAI,CAAA;IACQ,CAAA;GACD,GAZM,GAAG,OAAO,CAYhB;EACjB,CAAC,GACE,qBAAqB,oBAAC,SAAD;GAAS,OAAO,OAAO,UAAU;GACnD,UAAA,oBAAC,QAAD;IAAM,OAAO,EACf,SAAS,cACX;IACM,UAAA,oBAAC,aAAD;KAAa,SAAS,sBAAsB,KAAA,IAAY,OAAO,KAAI,MAAK,EAAE,KAAK;KAAG,OAAO,iBAAiB,SAAS,KAAA;KAAW,kBAAkB;KAC9I,UAAA,oBAAC,UAAD;MAAQ,cAAY,OAAO,UAAU;MAAU,MAAK;MAAS,GAAI,OAAO,MAAM,OAAO,QAAQ,uBAAuB,OAAO,OAAO,CAAC,CAAC,kBAAkB,OAAO,YAAY;MAAG,OAAO;OACrL,YAAY,sBAAsB,KAAA,IAAY;OAC9C,cAAc,UAAU,WAAW,QAAQ,OAAO;MACpD;KAAI,CAAA;IACW,CAAA;GACT,CAAA;EACC,CAAA,CACJ;;AACb;AACA,cAAc,cAAc"}
1
+ {"version":3,"file":"ColorSwatches.mjs","names":["Flexbox"],"sources":["../../src/ColorSwatches/ColorSwatches.tsx"],"sourcesContent":["'use client';\n\nimport * as stylex from '@stylexjs/stylex';\nimport chroma from 'chroma-js';\nimport { CheckIcon } from 'lucide-react';\nimport { type FC, useMemo } from 'react';\n\nimport { Center, Flexbox } from '@/Flex';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport Icon from '@/Icon';\nimport { cssVar } from '@/styles/stylex/cssVar.stylex';\nimport Tooltip from '@/Tooltip';\nimport { safeReadableColor } from '@/utils/safeReadableColor';\n\nimport CustomPicker from './CustomPicker';\nimport { styles } from './style';\nimport type { ColorSwatchesPickerRenderProps, ColorSwatchesProps } from './type';\n\nconst ColorSwatches: FC<ColorSwatchesProps> = ({\n enableColorPicker,\n enableColorSwatches = true,\n defaultValue,\n value,\n style,\n colors,\n onChange,\n size = 24,\n shape = 'circle',\n texts,\n renderPicker,\n ref,\n ...rest\n}) => {\n const [active, setActive] = useControlledState(defaultValue, {\n defaultValue,\n onChange,\n value,\n });\n\n // Convert size prop to CSS variable\n const cssVariables = useMemo<Record<string, string>>(\n () => ({\n '--color-swatches-size': `${size}px`,\n }),\n [size],\n );\n\n const isCustomActive = useMemo(\n () => active && active !== cssVar.colorPrimary && !colors.some((c) => c.color === active),\n [active, colors],\n );\n\n const pickerProps: ColorSwatchesPickerRenderProps = {\n active: !!isCustomActive,\n onChange: setActive,\n presets: colors.map((c) => c.color),\n shape,\n size,\n value: isCustomActive ? active : undefined,\n };\n\n const defaultPicker = enableColorPicker && (\n <CustomPicker\n {...pickerProps}\n current={active}\n label={texts?.custom || 'Custom'}\n standalone={!enableColorSwatches}\n />\n );\n\n return (\n <Flexbox\n horizontal\n gap={6}\n ref={ref}\n style={{\n ...cssVariables,\n flexWrap: 'wrap',\n ...style,\n }}\n {...rest}\n >\n {enableColorSwatches &&\n colors.map((c, i) => {\n const color = c.color || cssVar.colorPrimary;\n const isActive = (!active && !c.color) || color === active;\n // Check if color is transparent or CSS variable (which chroma can't parse)\n const isTransparent =\n c.color === 'transparent' ||\n (c.color &&\n !c.color.startsWith('var(') &&\n (() => {\n try {\n return chroma(c.color).alpha() === 0;\n } catch {\n return false;\n }\n })());\n return (\n <Tooltip key={c?.key || i} title={c.title}>\n <Center\n {...stylex.props(\n styles.container,\n !!isTransparent && styles.transparent,\n isActive && styles.activeContainer,\n )}\n style={{\n background: isTransparent ? undefined : color,\n borderRadius: shape === 'circle' ? '50%' : cssVar.borderRadius,\n }}\n onClick={() => setActive(c.color || undefined)}\n >\n {isActive && (\n <Icon\n color={`color-mix(in srgb, ${safeReadableColor(color)} 33%, transparent)`}\n icon={CheckIcon}\n size={{ size: 14, strokeWidth: 4 }}\n style={{\n pointerEvents: 'none',\n }}\n />\n )}\n </Center>\n </Tooltip>\n );\n })}\n {enableColorPicker &&\n (renderPicker ? renderPicker(pickerProps, defaultPicker) : defaultPicker)}\n </Flexbox>\n );\n};\n\nColorSwatches.displayName = 'ColorSwatches';\n\nexport default ColorSwatches;\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,MAAM,iBAAyC,EAC7C,mBACA,sBAAsB,MACtB,cACA,OACA,OACA,QACA,UACA,OAAO,IACP,QAAQ,UACR,OACA,cACA,KACA,GAAG,WACC;CACJ,MAAM,CAAC,QAAQ,aAAa,mBAAmB,cAAc;EAC3D;EACA;EACA;CACF,CAAC;CAGD,MAAM,eAAe,eAAuC,EAC1D,yBAAyB,GAAG,KAAK,IACnC,IAAI,CAAC,IAAI,CAAC;CACV,MAAM,iBAAiB,cAAc,UAAU,WAAW,OAAO,gBAAgB,CAAC,OAAO,MAAK,MAAK,EAAE,UAAU,MAAM,GAAG,CAAC,QAAQ,MAAM,CAAC;CACxI,MAAM,cAA8C;EAClD,QAAQ,CAAC,CAAC;EACV,UAAU;EACV,SAAS,OAAO,KAAI,MAAK,EAAE,KAAK;EAChC;EACA;EACA,OAAO,iBAAiB,SAAS,KAAA;CACnC;CACA,MAAM,gBAAgB,qBAAqB,oBAAC,cAAD;EAAc,GAAI;EAAa,SAAS;EAAQ,OAAO,OAAO,UAAU;EAAU,YAAY,CAAC;CAAsB,CAAA;CAChK,OAAO,qBAACA,mBAAD;EAAS,YAAA;EAAW,KAAK;EAAQ;EAAK,OAAO;GAClD,GAAG;GACH,UAAU;GACV,GAAG;EACL;EAAG,GAAI;EAJA,UAAA,CAKF,uBAAuB,OAAO,KAAK,GAAG,MAAM;GAC7C,MAAM,QAAQ,EAAE,SAAS,OAAO;GAChC,MAAM,WAAW,CAAC,UAAU,CAAC,EAAE,SAAS,UAAU;GAElD,MAAM,gBAAgB,EAAE,UAAU,iBAAiB,EAAE,SAAS,CAAC,EAAE,MAAM,WAAW,MAAM,YAAY;IAClG,IAAI;KACF,OAAO,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,MAAM;IACrC,QAAQ;KACN,OAAO;IACT;GACF,EAAA,CAAG;GACH,OAAO,oBAAC,SAAD;IAA2B,OAAO,EAAE;IACnC,UAAA,oBAAC,QAAD;KAAQ,GAAI,OAAO,MAAM,OAAO,WAAW,CAAC,CAAC,iBAAiB,OAAO,aAAa,YAAY,OAAO,eAAe;KAAG,OAAO;MAClI,YAAY,gBAAgB,KAAA,IAAY;MACxC,cAAc,UAAU,WAAW,QAAQ,OAAO;KACpD;KAAG,eAAe,UAAU,EAAE,SAAS,KAAA,CAAS;KACvC,UAAA,YAAY,oBAAC,MAAD;MAAM,OAAO,sBAAsB,kBAAkB,KAAK,EAAE;MAAqB,MAAM;MAAW,MAAM;OACzH,MAAM;OACN,aAAa;MACf;MAAG,OAAO,EACR,eAAe,OACjB;KAAI,CAAA;IACQ,CAAA;GACD,GAZM,GAAG,OAAO,CAYhB;EACjB,CAAC,GACE,sBAAsB,eAAe,aAAa,aAAa,aAAa,IAAI,cAC1E;;AACb;AACA,cAAc,cAAc"}
@@ -0,0 +1,101 @@
1
+ "use client";
2
+ import { cssVar } from "../styles/stylex/cssVar.stylex.mjs";
3
+ import Icon from "../Icon/Icon.mjs";
4
+ import { Tooltip } from "../Tooltip/Tooltip.mjs";
5
+ import { safeReadableColor } from "../utils/safeReadableColor.mjs";
6
+ import ColorPicker from "../ColorPicker/ColorPicker.mjs";
7
+ import { chipMarker } from "./marker.stylex.mjs";
8
+ import { styles } from "./style.mjs";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ import * as stylex from "@stylexjs/stylex";
11
+ import { CheckIcon, PencilIcon, PlusIcon } from "lucide-react";
12
+ //#region src/ColorSwatches/CustomPicker.tsx
13
+ const CustomPicker = ({ active, current, label, onChange, presets, shape, standalone, value }) => {
14
+ const borderRadius = shape === "circle" ? "50%" : cssVar.borderRadius;
15
+ if (standalone) return /* @__PURE__ */ jsx(Tooltip, {
16
+ title: label,
17
+ children: /* @__PURE__ */ jsx("span", {
18
+ style: { display: "inline-flex" },
19
+ children: /* @__PURE__ */ jsx(ColorPicker, {
20
+ presets,
21
+ value,
22
+ onChangeComplete: onChange,
23
+ children: /* @__PURE__ */ jsx("button", {
24
+ "aria-label": label,
25
+ type: "button",
26
+ ...stylex.props(styles.picker, active && styles.activePicker),
27
+ style: {
28
+ background: current,
29
+ borderRadius
30
+ }
31
+ })
32
+ })
33
+ })
34
+ });
35
+ const iconColor = value ? `color-mix(in srgb, ${safeReadableColor(value)} 33%, transparent)` : void 0;
36
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
37
+ /* @__PURE__ */ jsx("span", {
38
+ "aria-hidden": true,
39
+ ...stylex.props(styles.divider)
40
+ }),
41
+ active && value && /* @__PURE__ */ jsx(ColorPicker, {
42
+ value,
43
+ onChangeComplete: onChange,
44
+ children: /* @__PURE__ */ jsxs("button", {
45
+ "aria-label": value,
46
+ "aria-pressed": "true",
47
+ type: "button",
48
+ ...stylex.props(styles.picker, styles.chip, styles.activePicker, chipMarker),
49
+ style: {
50
+ background: value,
51
+ borderRadius
52
+ },
53
+ children: [/* @__PURE__ */ jsx(Icon, {
54
+ className: stylex.props(styles.chipIcon, styles.chipCheck).className,
55
+ color: iconColor,
56
+ icon: CheckIcon,
57
+ size: {
58
+ size: 14,
59
+ strokeWidth: 4
60
+ }
61
+ }), /* @__PURE__ */ jsx(Icon, {
62
+ className: stylex.props(styles.chipIcon, styles.chipEdit).className,
63
+ color: iconColor,
64
+ icon: PencilIcon,
65
+ size: {
66
+ size: 12,
67
+ strokeWidth: 3
68
+ }
69
+ })]
70
+ })
71
+ }),
72
+ /* @__PURE__ */ jsx(Tooltip, {
73
+ title: label,
74
+ children: /* @__PURE__ */ jsx("span", {
75
+ style: { display: "inline-flex" },
76
+ children: /* @__PURE__ */ jsx(ColorPicker, {
77
+ value,
78
+ onChangeComplete: onChange,
79
+ children: /* @__PURE__ */ jsx("button", {
80
+ "aria-label": label,
81
+ type: "button",
82
+ ...stylex.props(styles.picker, styles.conic, styles.add),
83
+ style: { borderRadius },
84
+ children: /* @__PURE__ */ jsx(Icon, {
85
+ icon: PlusIcon,
86
+ size: {
87
+ size: 12,
88
+ strokeWidth: 3
89
+ }
90
+ })
91
+ })
92
+ })
93
+ })
94
+ })
95
+ ] });
96
+ };
97
+ CustomPicker.displayName = "ColorSwatchesCustomPicker";
98
+ //#endregion
99
+ export { CustomPicker as default };
100
+
101
+ //# sourceMappingURL=CustomPicker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomPicker.mjs","names":[],"sources":["../../src/ColorSwatches/CustomPicker.tsx"],"sourcesContent":["'use client';\n\nimport * as stylex from '@stylexjs/stylex';\nimport { CheckIcon, PencilIcon, PlusIcon } from 'lucide-react';\nimport { type FC } from 'react';\n\nimport { ColorPicker } from '@/ColorPicker';\nimport Icon from '@/Icon';\nimport { cssVar } from '@/styles/stylex/cssVar.stylex';\nimport Tooltip from '@/Tooltip';\nimport { safeReadableColor } from '@/utils/safeReadableColor';\n\nimport { chipMarker } from './marker.stylex';\nimport { styles } from './style';\nimport type { ColorSwatchesPickerRenderProps } from './type';\n\ninterface CustomPickerProps extends ColorSwatchesPickerRenderProps {\n current?: string;\n label: string;\n standalone: boolean;\n}\n\nconst CustomPicker: FC<CustomPickerProps> = ({\n active,\n current,\n label,\n onChange,\n presets,\n shape,\n standalone,\n value,\n}) => {\n const borderRadius = shape === 'circle' ? '50%' : cssVar.borderRadius;\n\n if (standalone)\n return (\n <Tooltip title={label}>\n <span style={{ display: 'inline-flex' }}>\n <ColorPicker presets={presets} value={value} onChangeComplete={onChange}>\n <button\n aria-label={label}\n type=\"button\"\n {...stylex.props(styles.picker, active && styles.activePicker)}\n style={{ background: current, borderRadius }}\n />\n </ColorPicker>\n </span>\n </Tooltip>\n );\n\n const iconColor = value\n ? `color-mix(in srgb, ${safeReadableColor(value)} 33%, transparent)`\n : undefined;\n\n return (\n <>\n <span aria-hidden {...stylex.props(styles.divider)} />\n {active && value && (\n <ColorPicker value={value} onChangeComplete={onChange}>\n <button\n aria-label={value}\n aria-pressed=\"true\"\n type=\"button\"\n {...stylex.props(styles.picker, styles.chip, styles.activePicker, chipMarker)}\n style={{ background: value, borderRadius }}\n >\n <Icon\n className={stylex.props(styles.chipIcon, styles.chipCheck).className}\n color={iconColor}\n icon={CheckIcon}\n size={{ size: 14, strokeWidth: 4 }}\n />\n <Icon\n className={stylex.props(styles.chipIcon, styles.chipEdit).className}\n color={iconColor}\n icon={PencilIcon}\n size={{ size: 12, strokeWidth: 3 }}\n />\n </button>\n </ColorPicker>\n )}\n <Tooltip title={label}>\n <span style={{ display: 'inline-flex' }}>\n <ColorPicker value={value} onChangeComplete={onChange}>\n <button\n aria-label={label}\n type=\"button\"\n {...stylex.props(styles.picker, styles.conic, styles.add)}\n style={{ borderRadius }}\n >\n <Icon icon={PlusIcon} size={{ size: 12, strokeWidth: 3 }} />\n </button>\n </ColorPicker>\n </span>\n </Tooltip>\n </>\n );\n};\n\nCustomPicker.displayName = 'ColorSwatchesCustomPicker';\n\nexport default CustomPicker;\n"],"mappings":";;;;;;;;;;;;AAkBA,MAAM,gBAAuC,EAC3C,QACA,SACA,OACA,UACA,SACA,OACA,YACA,YACI;CACJ,MAAM,eAAe,UAAU,WAAW,QAAQ,OAAO;CACzD,IAAI,YAAY,OAAO,oBAAC,SAAD;EAAS,OAAO;EACjC,UAAA,oBAAC,QAAD;GAAM,OAAO,EACf,SAAS,cACX;GACM,UAAA,oBAAC,aAAD;IAAsB;IAAgB;IAAO,kBAAkB;IAC7D,UAAA,oBAAC,UAAD;KAAQ,cAAY;KAAO,MAAK;KAAS,GAAI,OAAO,MAAM,OAAO,QAAQ,UAAU,OAAO,YAAY;KAAG,OAAO;MAClH,YAAY;MACZ;KACF;IAAI,CAAA;GACW,CAAA;EACT,CAAA;CACC,CAAA;CACb,MAAM,YAAY,QAAQ,sBAAsB,kBAAkB,KAAK,EAAE,sBAAsB,KAAA;CAC/F,OAAO,qBAAA,UAAA,EAAA,UAAA;EACH,oBAAC,QAAD;GAAM,eAAA;GAAY,GAAI,OAAO,MAAM,OAAO,OAAO;EAAI,CAAA;EACpD,UAAU,SAAS,oBAAC,aAAD;GAAoB;GAAO,kBAAkB;GAC7D,UAAA,qBAAC,UAAD;IAAQ,cAAY;IAAO,gBAAa;IAAO,MAAK;IAAS,GAAI,OAAO,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,cAAc,UAAU;IAAG,OAAO;KACrJ,YAAY;KACZ;IACF;IAHI,UAAA,CAIE,oBAAC,MAAD;KAAM,WAAW,OAAO,MAAM,OAAO,UAAU,OAAO,SAAS,CAAC,CAAC;KAAW,OAAO;KAAW,MAAM;KAAW,MAAM;MACvH,MAAM;MACN,aAAa;KACf;IAAI,CAAA,GACA,oBAAC,MAAD;KAAM,WAAW,OAAO,MAAM,OAAO,UAAU,OAAO,QAAQ,CAAC,CAAC;KAAW,OAAO;KAAW,MAAM;KAAY,MAAM;MACvH,MAAM;MACN,aAAa;KACf;IAAI,CAAA,CACM;;EACG,CAAA;EACf,oBAAC,SAAD;GAAS,OAAO;GACd,UAAA,oBAAC,QAAD;IAAM,OAAO,EACb,SAAS,cACX;IACI,UAAA,oBAAC,aAAD;KAAoB;KAAO,kBAAkB;KAC3C,UAAA,oBAAC,UAAD;MAAQ,cAAY;MAAO,MAAK;MAAS,GAAI,OAAO,MAAM,OAAO,QAAQ,OAAO,OAAO,OAAO,GAAG;MAAG,OAAO,EAC3G,aACF;MACI,UAAA,oBAAC,MAAD;OAAM,MAAM;OAAU,MAAM;QAC5B,MAAM;QACN,aAAa;OACf;MAAI,CAAA;KACI,CAAA;IACG,CAAA;GACT,CAAA;EACC,CAAA;CACT,EAAA,CAAA;AACN;AACA,aAAa,cAAc"}
@@ -1,3 +1,3 @@
1
- import { ColorSwatchesProps } from "./type.mjs";
1
+ import { ColorSwatchesPickerRenderProps, ColorSwatchesProps } from "./type.mjs";
2
2
  import ColorSwatches from "./ColorSwatches.mjs";
3
- export { ColorSwatchesProps, ColorSwatches as default };
3
+ export { ColorSwatchesPickerRenderProps, ColorSwatchesProps, ColorSwatches as default };
@@ -0,0 +1,9 @@
1
+ //#region src/ColorSwatches/marker.stylex.ts
2
+ const chipMarker = {
3
+ lb1pgf9nm: "lb1pgf9nm",
4
+ $$css: true
5
+ };
6
+ //#endregion
7
+ export { chipMarker };
8
+
9
+ //# sourceMappingURL=marker.stylex.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marker.stylex.mjs","names":[],"sources":["../../src/ColorSwatches/marker.stylex.ts"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\n\nexport const chipMarker = stylex.defineMarker();\n"],"mappings":";AACA,MAAa,aAAa;CACxB,WAAW;CACX,OAAO;AACT"}
@@ -11,6 +11,33 @@ const styles = {
11
11
  kGVxlE: "lb1tp5pxa lb9jile0",
12
12
  $$css: true
13
13
  },
14
+ add: {
15
+ kGNEyG: "lb6s0dn4",
16
+ kMwMTN: "lbfungia",
17
+ k1xSpc: "lb3nfvp2",
18
+ kjj79g: "lbl56j7k",
19
+ $$css: true
20
+ },
21
+ chip: {
22
+ kgQiWS: "lb1ku5rj1",
23
+ k1xSpc: "lbrvj5dj",
24
+ $$css: true
25
+ },
26
+ chipCheck: {
27
+ kSiTet: "lb1hc1fzr lb1fz9fy1 lb1ilng6x",
28
+ $$css: true
29
+ },
30
+ chipEdit: {
31
+ kSiTet: "lbg01cxk lb1de8irz lbjygscn",
32
+ $$css: true
33
+ },
34
+ chipIcon: {
35
+ kBCFzs: "lbgkxs2y",
36
+ kbNqZ1: "lb1ms6mhf",
37
+ kmkexE: "lb13zw1ji",
38
+ kfzvcC: "lb47corl",
39
+ $$css: true
40
+ },
14
41
  conic: {
15
42
  kWkggS: "lbjbqb8w",
16
43
  kKwaWg: "lb9i5qys",
@@ -27,6 +54,15 @@ const styles = {
27
54
  kzqmXN: "lb4fkmq8",
28
55
  $$css: true
29
56
  },
57
+ divider: {
58
+ kUk6DE: "lb1okw0bk",
59
+ kUOVxO: "lb1dhlr3c",
60
+ kSGwAc: "lbamitd3",
61
+ kWkggS: "lbjjy28o",
62
+ kZKoxP: "lb8akbf1",
63
+ kzqmXN: "lb1i1rx1s",
64
+ $$css: true
65
+ },
30
66
  picker: {
31
67
  kmVPX3: "lb1717udv",
32
68
  kVAM5u: "lb181nr7w",
@@ -1 +1 @@
1
- {"version":3,"file":"style.mjs","names":[],"sources":["../../src/ColorSwatches/style.ts"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\n\nimport { cssVar } from '@/styles/stylex/cssVar.stylex';\n\nconst size = 'var(--color-swatches-size, 24px)';\nconst containerHover = `inset 0 0 0 1px rgb(0 0 0 / 5%), 0 0 0 2px ${cssVar.colorText}`;\nconst pickerHover = `inset 0 0 0 1px ${cssVar.colorFillSecondary}, 0 0 0 2px ${cssVar.colorText}`;\n\nexport const styles = stylex.create({\n activeContainer: {\n boxShadow: { 'default': `inset 0 0 0 1px ${cssVar.colorFill}`, ':hover': containerHover },\n },\n activePicker: {\n boxShadow: { 'default': `inset 0 0 0 1px ${cssVar.colorFill}`, ':hover': pickerHover },\n },\n conic: {\n backgroundColor: 'transparent',\n backgroundImage: `conic-gradient(${cssVar.red}, ${cssVar.volcano}, ${cssVar.orange}, ${cssVar.gold}, ${cssVar.yellow}, ${cssVar.lime}, ${cssVar.green}, ${cssVar.cyan}, ${cssVar.blue}, ${cssVar.geekblue}, ${cssVar.purple}, ${cssVar.magenta}, ${cssVar.red})`,\n },\n container: {\n flex: 'none',\n backgroundColor: cssVar.colorBgContainer,\n boxShadow: {\n 'default': `inset 0 0 0 1px ${cssVar.colorFillSecondary}`,\n ':hover': containerHover,\n },\n cursor: 'pointer',\n height: size,\n minHeight: size,\n minWidth: size,\n width: size,\n },\n picker: {\n padding: 0,\n borderColor: 'currentcolor',\n borderStyle: 'none',\n borderWidth: 'medium',\n flex: 'none',\n overflow: 'hidden',\n boxShadow: { 'default': `inset 0 0 0 1px ${cssVar.colorFillSecondary}`, ':hover': pickerHover },\n cursor: 'pointer',\n height: size,\n minHeight: size,\n minWidth: size,\n width: size,\n },\n transparent: {\n backgroundImage: `conic-gradient(${cssVar.colorFillSecondary} 25%, transparent 25% 50%, ${cssVar.colorFillSecondary} 50% 75%, transparent 75% 100%)`,\n backgroundSize: '50% 50%',\n },\n});\n"],"mappings":";;AAIuB,GAA8C,OAAO;AACxD,GAAmB,OAAO,qBAAiC,OAAO;AACtF,MAAa,SAAS;CACpB,iBAAiB;EACf,QAAQ;EACR,OAAO;CACT;CACA,cAAc;EACZ,QAAQ;EACR,OAAO;CACT;CACA,OAAO;EACL,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,WAAW;EACT,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,QAAQ;EACN,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,aAAa;EACX,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"style.mjs","names":[],"sources":["../../src/ColorSwatches/style.ts"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\n\nimport { cssVar } from '@/styles/stylex/cssVar.stylex';\n\nimport { chipMarker } from './marker.stylex';\n\nconst size = 'var(--color-swatches-size, 24px)';\nconst containerHover = `inset 0 0 0 1px rgb(0 0 0 / 5%), 0 0 0 2px ${cssVar.colorText}`;\nconst pickerHover = `inset 0 0 0 1px ${cssVar.colorFillSecondary}, 0 0 0 2px ${cssVar.colorText}`;\n\nexport const styles = stylex.create({\n activeContainer: {\n boxShadow: { 'default': `inset 0 0 0 1px ${cssVar.colorFill}`, ':hover': containerHover },\n },\n activePicker: {\n boxShadow: { 'default': `inset 0 0 0 1px ${cssVar.colorFill}`, ':hover': pickerHover },\n },\n add: {\n alignItems: 'center',\n color: '#fff',\n display: 'inline-flex',\n justifyContent: 'center',\n },\n chip: {\n placeItems: 'center',\n display: 'grid',\n },\n chipCheck: {\n opacity: {\n default: 1,\n [stylex.when.ancestor(':focus-visible', chipMarker)]: 0,\n [stylex.when.ancestor(':hover', chipMarker)]: 0,\n },\n },\n chipEdit: {\n opacity: {\n default: 0,\n [stylex.when.ancestor(':focus-visible', chipMarker)]: 1,\n [stylex.when.ancestor(':hover', chipMarker)]: 1,\n },\n },\n chipIcon: {\n gridColumn: 1,\n gridRow: 1,\n transition: `opacity 0.15s ${cssVar.motionEaseInOut}`,\n pointerEvents: 'none',\n },\n conic: {\n backgroundColor: 'transparent',\n backgroundImage: `conic-gradient(${cssVar.red}, ${cssVar.volcano}, ${cssVar.orange}, ${cssVar.gold}, ${cssVar.yellow}, ${cssVar.lime}, ${cssVar.green}, ${cssVar.cyan}, ${cssVar.blue}, ${cssVar.geekblue}, ${cssVar.purple}, ${cssVar.magenta}, ${cssVar.red})`,\n },\n container: {\n flex: 'none',\n backgroundColor: cssVar.colorBgContainer,\n boxShadow: {\n 'default': `inset 0 0 0 1px ${cssVar.colorFillSecondary}`,\n ':hover': containerHover,\n },\n cursor: 'pointer',\n height: size,\n minHeight: size,\n minWidth: size,\n width: size,\n },\n divider: {\n flex: 'none',\n marginInline: 2,\n alignSelf: 'center',\n backgroundColor: cssVar.colorFillSecondary,\n height: `calc(${size} * 0.75)`,\n width: 1,\n },\n picker: {\n padding: 0,\n borderColor: 'currentcolor',\n borderStyle: 'none',\n borderWidth: 'medium',\n flex: 'none',\n overflow: 'hidden',\n boxShadow: { 'default': `inset 0 0 0 1px ${cssVar.colorFillSecondary}`, ':hover': pickerHover },\n cursor: 'pointer',\n height: size,\n minHeight: size,\n minWidth: size,\n width: size,\n },\n transparent: {\n backgroundImage: `conic-gradient(${cssVar.colorFillSecondary} 25%, transparent 25% 50%, ${cssVar.colorFillSecondary} 50% 75%, transparent 75% 100%)`,\n backgroundSize: '50% 50%',\n },\n});\n"],"mappings":";;AAMuB,GAA8C,OAAO;AACxD,GAAmB,OAAO,qBAAiC,OAAO;AACtF,MAAa,SAAS;CACpB,iBAAiB;EACf,QAAQ;EACR,OAAO;CACT;CACA,cAAc;EACZ,QAAQ;EACR,OAAO;CACT;CACA,KAAK;EACH,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,MAAM;EACJ,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,WAAW;EACT,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,OAAO;EACL,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,WAAW;EACT,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,QAAQ;EACN,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,aAAa;EACX,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF"}
@@ -7,6 +7,14 @@ interface ColorSwatchesItemType {
7
7
  key?: Key;
8
8
  title?: ReactNode;
9
9
  }
10
+ export interface ColorSwatchesPickerRenderProps {
11
+ active: boolean;
12
+ onChange: (color?: string) => void;
13
+ presets: string[];
14
+ shape: 'circle' | 'square';
15
+ size: number;
16
+ value?: string;
17
+ }
10
18
  export interface ColorSwatchesProps extends Omit<FlexboxProps, 'onChange'> {
11
19
  colors: ColorSwatchesItemType[];
12
20
  defaultValue?: string;
@@ -14,11 +22,12 @@ export interface ColorSwatchesProps extends Omit<FlexboxProps, 'onChange'> {
14
22
  enableColorSwatches?: boolean;
15
23
  onChange?: (color?: string) => void;
16
24
  ref?: Ref<HTMLDivElement>;
25
+ renderPicker?: (props: ColorSwatchesPickerRenderProps, defaultPicker: ReactNode) => ReactNode;
17
26
  shape?: 'circle' | 'square';
18
27
  size?: number;
19
28
  texts?: {
20
- custom: string;
21
- presets: string;
29
+ custom?: string;
30
+ presets?: string;
22
31
  };
23
32
  value?: string;
24
33
  }
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import FlexBasic_default from "../Flex/FlexBasic.mjs";
3
+ import { cx } from "../styles/css.mjs";
3
4
  import ActionIcon from "../ActionIcon/ActionIcon.mjs";
4
5
  import { useControlledState } from "../hooks/useControlledState.mjs";
5
- import { cx } from "../styles/css.mjs";
6
6
  import ControlInput from "./ControlInput.mjs";
7
7
  import { memo, useMemo } from "react";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
package/es/Grid/style.mjs CHANGED
@@ -4,7 +4,7 @@ const styles = { root: {
4
4
  "--max-item-width": "lb18upadf",
5
5
  "--rows": "lbb27uya",
6
6
  k1xSpc: "lbrvj5dj",
7
- kumcoG: "lb1h17qkj",
7
+ kumcoG: "lbrwvf1q",
8
8
  $$css: true
9
9
  } };
10
10
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"style.mjs","names":[],"sources":["../../src/Grid/style.ts"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\n\nexport const styles = stylex.create({\n root: {\n '--gap': 'var(--grid-gap, 1em)',\n '--max-item-width': 'var(--grid-max-item-width, 240px)',\n '--rows': 'var(--grid-rows, 3)',\n 'display': 'grid',\n 'gridTemplateColumns':\n 'repeat(auto-fill, minmax(max(var(--max-item-width), calc((100% - var(--gap) * (var(--rows) - 1)) / var(--rows))), 1fr))',\n },\n});\n"],"mappings":";AACA,MAAa,SAAS,EACpB,MAAM;CACJ,SAAS;CACT,oBAAoB;CACpB,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,OAAO;AACT,EACF"}
1
+ {"version":3,"file":"style.mjs","names":[],"sources":["../../src/Grid/style.ts"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\n\nexport const gridTemplateColumns =\n 'repeat(auto-fill, minmax(max(min(var(--max-item-width), 100%), calc((100% - var(--gap) * (var(--rows) - 1)) / var(--rows))), 1fr))';\n\nexport const styles = stylex.create({\n root: {\n '--gap': 'var(--grid-gap, 1em)',\n '--max-item-width': 'var(--grid-max-item-width, 240px)',\n '--rows': 'var(--grid-rows, 3)',\n 'display': 'grid',\n // eslint-disable-next-line @stylexjs/valid-styles -- the rule cannot resolve a module constant; the compiler inlines it\n 'gridTemplateColumns': gridTemplateColumns,\n },\n});\n"],"mappings":";AAEA,MAAa,SAAS,EACpB,MAAM;CACJ,SAAS;CACT,oBAAoB;CACpB,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,OAAO;AACT,EACF"}
@@ -4,6 +4,7 @@ import { CSSProperties, ComponentProps, ReactNode, Ref } from "react";
4
4
  export interface ImagePreviewOptions {
5
5
  autoZoomThreshold?: number;
6
6
  defaultZoom?: ZoomPolicy;
7
+ fetchImage?: (source: string) => Promise<Blob>;
7
8
  maxScale?: number;
8
9
  onDownload?: (source: string) => void | Promise<void>;
9
10
  onOpenChange?: (open: boolean) => void;
@@ -250,6 +250,7 @@ const ImageViewer = memo(({ entries, index, openerFocusElement, token }) => {
250
250
  canZoomOut,
251
251
  chromeRef,
252
252
  current: currentIndex,
253
+ fetchImage: currentEntry.options.fetchImage,
253
254
  fitRect,
254
255
  flipHorizontal,
255
256
  flipVertical,
@@ -1 +1 @@
1
- {"version":3,"file":"ImageViewer.mjs","names":[],"sources":["../../../src/Image/viewer/ImageViewer.tsx"],"sourcesContent":["'use client';\n\nimport '../style.css';\n\nimport { Dialog, type DialogRootProps } from '@base-ui/react/dialog';\nimport {\n memo,\n type SyntheticEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useAppElement } from '@/ConfigProvider/AppElementContext';\nimport { useLayerZIndex } from '@/internal/zIndex';\nimport { styleProps } from '@/styles/stylex/props';\nimport { ToastHost } from '@/Toast';\nimport { useMergeRefs } from '@/utils/mergeRefs';\n\nimport { styles } from '../style';\nimport { computeFit, type Size, unrotatedRect } from './geometry';\nimport { beginClosePreview, endClosePreview, type PreviewEntry } from './registry';\nimport { useFinalFocus } from './useFinalFocus';\nimport { useFlipTransition } from './useFlipTransition';\nimport { readNatural, useGalleryNav } from './useGalleryNav';\nimport { useRefitTransition } from './useRefitTransition';\nimport { useViewerGestures } from './useViewerGestures';\nimport { useZoomPan } from './useZoomPan';\nimport ViewerChrome from './ViewerChrome';\n\nexport interface ImageViewerProps {\n entries: PreviewEntry[];\n index: number;\n openerFocusElement: HTMLElement | null;\n token: number;\n}\n\nconst readViewport = (): Size => ({ height: window.innerHeight, width: window.innerWidth });\n\nconst prefersReducedMotion = () =>\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n\nconst ImageViewer = memo<ImageViewerProps>(({ entries, index, openerFocusElement, token }) => {\n const openerEntry = entries[index];\n const appElement = useAppElement();\n const [animated] = useState(() => !prefersReducedMotion());\n\n const { ref: layerRef, zIndex } = useLayerZIndex<HTMLDivElement>('modal');\n\n const [currentIndex, setCurrentIndex] = useState(index);\n const currentEntry = entries[currentIndex];\n const currentEntryRef = useRef(currentEntry);\n currentEntryRef.current = currentEntry;\n\n const [source, setSource] = useState(openerEntry.src);\n const [natural, setNatural] = useState<Size>(() => readNatural(openerEntry.element));\n const [viewport, setViewport] = useState<Size>(readViewport);\n\n const closeRef = useRef<(() => void) | null>(null);\n const requestClose = useCallback(() => closeRef.current?.(), []);\n\n // useZoomPan needs to gate its wheel/toolbar entry points on close-in-flight,\n // but that state only exists once useFlipTransition (below) has run — and it\n // needs useZoomPan's own transform values first. Break the cycle with a\n // stable indirection: the ref is repointed at the real isClosing once it's\n // available, written directly during render like the other *Ref fields here.\n const isClosingIndirectRef = useRef<() => boolean>(() => false);\n const isClosingIndirect = useCallback(() => isClosingIndirectRef.current(), []);\n\n const isTransitioningIndirectRef = useRef<() => boolean>(() => true);\n const isTransitioningIndirect = useCallback(() => isTransitioningIndirectRef.current(), []);\n\n const fillViewport = Boolean(\n currentEntry.previewSrc && currentEntry.previewSrc !== currentEntry.src,\n );\n\n const {\n canZoomIn,\n canZoomOut,\n dragBy,\n dragEnd,\n escIntent,\n flipHorizontal,\n flipVertical,\n flipX,\n flipY,\n getInitialScale,\n handleDoubleClick,\n handleWheel,\n isClean,\n isZoomed,\n reset,\n resetCloseTracking,\n rotate,\n rotateLeft,\n rotateRight,\n rotation,\n scale,\n setNatural: syncZoomNatural,\n setViewport: syncZoomViewport,\n toggleActualSize,\n x,\n y,\n zoomIn,\n zoomOut,\n } = useZoomPan({\n autoZoomThreshold: currentEntry.options.autoZoomThreshold,\n defaultZoom: currentEntry.options.defaultZoom,\n fillViewport,\n isClosing: isClosingIndirect,\n isTransitioning: isTransitioningIndirect,\n maxScale: currentEntry.options.maxScale,\n natural,\n onCloseRequest: requestClose,\n viewport,\n });\n\n const fitRect = useMemo(\n () => computeFit(natural, viewport, rotation, fillViewport),\n [natural, viewport, rotation, fillViewport],\n );\n const fitRectRef = useRef(fitRect);\n fitRectRef.current = fitRect;\n const getFitRect = useCallback(() => fitRectRef.current, []);\n\n const imageRect = useMemo(() => unrotatedRect(fitRect, rotation), [fitRect, rotation]);\n\n const viewportRef = useRef(viewport);\n viewportRef.current = viewport;\n const getViewportWidth = useCallback(() => viewportRef.current.width, []);\n\n const getCloseSource = useCallback(() => currentEntryRef.current.element, []);\n\n const transform = useMemo(\n () => ({ flipX, flipY, rotate, scale, x, y }),\n [flipX, flipY, rotate, scale, x, y],\n );\n const handleClosed = useCallback(() => endClosePreview(token), [token]);\n\n const {\n backdropRef,\n chromeRef,\n close,\n imageRef: flipImageRef,\n isClosing,\n isTransitioning,\n switchTo,\n } = useFlipTransition({\n animated,\n getCloseSource,\n getFitRect,\n getInitialScale,\n getViewportWidth,\n onClosed: handleClosed,\n source: openerEntry.element,\n transform,\n });\n isClosingIndirectRef.current = isClosing;\n isTransitioningIndirectRef.current = isTransitioning;\n\n useRefitTransition({\n animated,\n fillViewport,\n isTransitioning,\n natural,\n rotation,\n scale,\n viewport,\n x,\n y,\n });\n const finalFocus = useFinalFocus(openerFocusElement);\n\n const isCleanRef = useRef(isClean);\n isCleanRef.current = isClean;\n\n const handleClose = useCallback(() => {\n beginClosePreview(token);\n close({ fade: !isCleanRef.current });\n }, [close, token]);\n closeRef.current = handleClose;\n\n const handleDialogOpenChange = useCallback<NonNullable<DialogRootProps['onOpenChange']>>(\n (open, eventDetails) => {\n if (open) return;\n // Base UI fires this on every Esc regardless of whether we're already\n // mid-close (our Dialog.Root's open prop never actually flips to\n // false), so without this guard a second Esc during the close window\n // reads escIntent() off the close spring's mid-flight scale, decides\n // 'reset', and cancels the close animation out from under itself.\n if (isClosing()) return;\n if (eventDetails.reason === 'escape-key' && escIntent() === 'reset') {\n eventDetails.cancel();\n reset();\n return;\n }\n handleClose();\n },\n [escIntent, handleClose, isClosing, reset],\n );\n\n const gestures = useViewerGestures({\n dragBy,\n dragEnd,\n handleDoubleClick,\n handleWheel,\n isClean,\n isZoomed,\n onClose: handleClose,\n reset,\n zoomIn,\n zoomOut,\n });\n\n const { hasNext, hasPrev, next, prev } = useGalleryNav({\n cancelPendingClose: gestures.cancelPendingClose,\n currentIndex,\n entries,\n flipX,\n flipY,\n getInitialScale,\n resetCloseTracking,\n rotate,\n scale,\n setCurrentIndex,\n setNatural,\n setSource,\n switchTo,\n syncZoomNatural,\n x,\n y,\n });\n\n const popupNodeRef = useRef<HTMLDivElement | null>(null);\n const popupRef = useMergeRefs<HTMLDivElement>([layerRef, gestures.popupRef, popupNodeRef]);\n const imageRef = useMergeRefs<HTMLImageElement>([flipImageRef, gestures.imageRef]);\n\n const handleLoad = useCallback(\n (event: SyntheticEvent<HTMLImageElement>) => {\n const loaded = event.currentTarget;\n if (!loaded.naturalWidth || !loaded.naturalHeight) return;\n const next = { height: loaded.naturalHeight, width: loaded.naturalWidth };\n setNatural((prev) =>\n prev.width === next.width && prev.height === next.height ? prev : next,\n );\n syncZoomNatural(next);\n },\n [syncZoomNatural],\n );\n\n useEffect(() => {\n const handleResize = () => {\n const next = readViewport();\n setViewport(next);\n syncZoomViewport(next);\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [syncZoomViewport]);\n\n useEffect(() => {\n const { previewSrc, src } = currentEntry;\n if (!previewSrc || previewSrc === src) return;\n let cancelled = false;\n const loader = new window.Image();\n const handleLoaded = () => {\n if (!cancelled) setSource(previewSrc);\n };\n loader.addEventListener('load', handleLoaded);\n loader.src = previewSrc;\n return () => {\n cancelled = true;\n loader.removeEventListener('load', handleLoaded);\n };\n }, [currentEntry]);\n\n useEffect(\n () => () => {\n const thumbnailWasRemoved = !openerEntry.element.isConnected;\n if (thumbnailWasRemoved) endClosePreview(token);\n },\n [openerEntry, token],\n );\n\n return (\n <Dialog.Root modal open onOpenChange={handleDialogOpenChange}>\n <Dialog.Portal container={appElement ?? undefined}>\n {/* Base UI drops a nested dialog's backdrop so overlays don't stack. The viewer is\n not an overlay on the surface it opened from — it replaces the screen — so it\n keeps its own scrim, or a viewer opened from a maskless drawer would show the\n page straight through the letterbox around the image. */}\n <Dialog.Backdrop\n forceRender\n {...styleProps(styles.viewerBackdrop, 'lobe-image-viewer-backdrop')}\n ref={backdropRef}\n style={zIndex === undefined ? undefined : { zIndex }}\n onClick={gestures.onSurfaceClick}\n />\n <Dialog.Popup\n aria-label={currentEntry.element.alt || undefined}\n {...styleProps(styles.viewerPopup, 'lobe-image-viewer-popup')}\n finalFocus={finalFocus}\n // Focus the popup itself instead of Base UI's default (the first\n // tabbable — the close button): a focus-visible close button pins\n // the idle auto-hide open forever and pops its tooltip on open.\n initialFocus={popupNodeRef}\n ref={popupRef}\n style={zIndex === undefined ? undefined : { zIndex: zIndex + 1 }}\n tabIndex={-1}\n onClick={gestures.onSurfaceClick}\n onPointerCancel={gestures.onPointerFinish}\n onPointerDown={gestures.onPointerDown}\n onPointerMove={gestures.onPointerMove}\n onPointerUp={gestures.onPointerFinish}\n >\n <img\n alt={currentEntry.element.alt}\n {...styleProps(styles.viewerImage, 'lobe-image-viewer-image')}\n ref={imageRef}\n src={source}\n style={{\n cursor: gestures.cursor,\n height: imageRect.height,\n left: imageRect.x,\n top: imageRect.y,\n width: imageRect.width,\n }}\n onClick={gestures.onImageClick}\n onDoubleClick={gestures.onImageDoubleClick}\n onLoad={handleLoad}\n />\n <ViewerChrome\n canZoomIn={canZoomIn}\n canZoomOut={canZoomOut}\n chromeRef={chromeRef}\n current={currentIndex}\n fitRect={fitRect}\n flipHorizontal={flipHorizontal}\n flipVertical={flipVertical}\n hasNext={hasNext}\n hasPrev={hasPrev}\n natural={natural}\n next={next}\n prev={prev}\n rotateLeft={rotateLeft}\n rotateRight={rotateRight}\n rotation={rotation}\n scale={scale}\n source={source}\n toggleActualSize={toggleActualSize}\n toolbarAddon={currentEntry.options.toolbarAddon}\n total={entries.length}\n zoomIn={zoomIn}\n zoomOut={zoomOut}\n onClose={handleClose}\n onDownload={currentEntry.options.onDownload}\n />\n </Dialog.Popup>\n <ToastHost root={appElement ?? undefined} />\n </Dialog.Portal>\n </Dialog.Root>\n );\n});\n\nImageViewer.displayName = 'ImageViewer';\n\nexport default ImageViewer;\n"],"mappings":"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"}
1
+ {"version":3,"file":"ImageViewer.mjs","names":[],"sources":["../../../src/Image/viewer/ImageViewer.tsx"],"sourcesContent":["'use client';\n\nimport '../style.css';\n\nimport { Dialog, type DialogRootProps } from '@base-ui/react/dialog';\nimport {\n memo,\n type SyntheticEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useAppElement } from '@/ConfigProvider/AppElementContext';\nimport { useLayerZIndex } from '@/internal/zIndex';\nimport { styleProps } from '@/styles/stylex/props';\nimport { ToastHost } from '@/Toast';\nimport { useMergeRefs } from '@/utils/mergeRefs';\n\nimport { styles } from '../style';\nimport { computeFit, type Size, unrotatedRect } from './geometry';\nimport { beginClosePreview, endClosePreview, type PreviewEntry } from './registry';\nimport { useFinalFocus } from './useFinalFocus';\nimport { useFlipTransition } from './useFlipTransition';\nimport { readNatural, useGalleryNav } from './useGalleryNav';\nimport { useRefitTransition } from './useRefitTransition';\nimport { useViewerGestures } from './useViewerGestures';\nimport { useZoomPan } from './useZoomPan';\nimport ViewerChrome from './ViewerChrome';\n\nexport interface ImageViewerProps {\n entries: PreviewEntry[];\n index: number;\n openerFocusElement: HTMLElement | null;\n token: number;\n}\n\nconst readViewport = (): Size => ({ height: window.innerHeight, width: window.innerWidth });\n\nconst prefersReducedMotion = () =>\n window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n\nconst ImageViewer = memo<ImageViewerProps>(({ entries, index, openerFocusElement, token }) => {\n const openerEntry = entries[index];\n const appElement = useAppElement();\n const [animated] = useState(() => !prefersReducedMotion());\n\n const { ref: layerRef, zIndex } = useLayerZIndex<HTMLDivElement>('modal');\n\n const [currentIndex, setCurrentIndex] = useState(index);\n const currentEntry = entries[currentIndex];\n const currentEntryRef = useRef(currentEntry);\n currentEntryRef.current = currentEntry;\n\n const [source, setSource] = useState(openerEntry.src);\n const [natural, setNatural] = useState<Size>(() => readNatural(openerEntry.element));\n const [viewport, setViewport] = useState<Size>(readViewport);\n\n const closeRef = useRef<(() => void) | null>(null);\n const requestClose = useCallback(() => closeRef.current?.(), []);\n\n // useZoomPan needs to gate its wheel/toolbar entry points on close-in-flight,\n // but that state only exists once useFlipTransition (below) has run — and it\n // needs useZoomPan's own transform values first. Break the cycle with a\n // stable indirection: the ref is repointed at the real isClosing once it's\n // available, written directly during render like the other *Ref fields here.\n const isClosingIndirectRef = useRef<() => boolean>(() => false);\n const isClosingIndirect = useCallback(() => isClosingIndirectRef.current(), []);\n\n const isTransitioningIndirectRef = useRef<() => boolean>(() => true);\n const isTransitioningIndirect = useCallback(() => isTransitioningIndirectRef.current(), []);\n\n const fillViewport = Boolean(\n currentEntry.previewSrc && currentEntry.previewSrc !== currentEntry.src,\n );\n\n const {\n canZoomIn,\n canZoomOut,\n dragBy,\n dragEnd,\n escIntent,\n flipHorizontal,\n flipVertical,\n flipX,\n flipY,\n getInitialScale,\n handleDoubleClick,\n handleWheel,\n isClean,\n isZoomed,\n reset,\n resetCloseTracking,\n rotate,\n rotateLeft,\n rotateRight,\n rotation,\n scale,\n setNatural: syncZoomNatural,\n setViewport: syncZoomViewport,\n toggleActualSize,\n x,\n y,\n zoomIn,\n zoomOut,\n } = useZoomPan({\n autoZoomThreshold: currentEntry.options.autoZoomThreshold,\n defaultZoom: currentEntry.options.defaultZoom,\n fillViewport,\n isClosing: isClosingIndirect,\n isTransitioning: isTransitioningIndirect,\n maxScale: currentEntry.options.maxScale,\n natural,\n onCloseRequest: requestClose,\n viewport,\n });\n\n const fitRect = useMemo(\n () => computeFit(natural, viewport, rotation, fillViewport),\n [natural, viewport, rotation, fillViewport],\n );\n const fitRectRef = useRef(fitRect);\n fitRectRef.current = fitRect;\n const getFitRect = useCallback(() => fitRectRef.current, []);\n\n const imageRect = useMemo(() => unrotatedRect(fitRect, rotation), [fitRect, rotation]);\n\n const viewportRef = useRef(viewport);\n viewportRef.current = viewport;\n const getViewportWidth = useCallback(() => viewportRef.current.width, []);\n\n const getCloseSource = useCallback(() => currentEntryRef.current.element, []);\n\n const transform = useMemo(\n () => ({ flipX, flipY, rotate, scale, x, y }),\n [flipX, flipY, rotate, scale, x, y],\n );\n const handleClosed = useCallback(() => endClosePreview(token), [token]);\n\n const {\n backdropRef,\n chromeRef,\n close,\n imageRef: flipImageRef,\n isClosing,\n isTransitioning,\n switchTo,\n } = useFlipTransition({\n animated,\n getCloseSource,\n getFitRect,\n getInitialScale,\n getViewportWidth,\n onClosed: handleClosed,\n source: openerEntry.element,\n transform,\n });\n isClosingIndirectRef.current = isClosing;\n isTransitioningIndirectRef.current = isTransitioning;\n\n useRefitTransition({\n animated,\n fillViewport,\n isTransitioning,\n natural,\n rotation,\n scale,\n viewport,\n x,\n y,\n });\n const finalFocus = useFinalFocus(openerFocusElement);\n\n const isCleanRef = useRef(isClean);\n isCleanRef.current = isClean;\n\n const handleClose = useCallback(() => {\n beginClosePreview(token);\n close({ fade: !isCleanRef.current });\n }, [close, token]);\n closeRef.current = handleClose;\n\n const handleDialogOpenChange = useCallback<NonNullable<DialogRootProps['onOpenChange']>>(\n (open, eventDetails) => {\n if (open) return;\n // Base UI fires this on every Esc regardless of whether we're already\n // mid-close (our Dialog.Root's open prop never actually flips to\n // false), so without this guard a second Esc during the close window\n // reads escIntent() off the close spring's mid-flight scale, decides\n // 'reset', and cancels the close animation out from under itself.\n if (isClosing()) return;\n if (eventDetails.reason === 'escape-key' && escIntent() === 'reset') {\n eventDetails.cancel();\n reset();\n return;\n }\n handleClose();\n },\n [escIntent, handleClose, isClosing, reset],\n );\n\n const gestures = useViewerGestures({\n dragBy,\n dragEnd,\n handleDoubleClick,\n handleWheel,\n isClean,\n isZoomed,\n onClose: handleClose,\n reset,\n zoomIn,\n zoomOut,\n });\n\n const { hasNext, hasPrev, next, prev } = useGalleryNav({\n cancelPendingClose: gestures.cancelPendingClose,\n currentIndex,\n entries,\n flipX,\n flipY,\n getInitialScale,\n resetCloseTracking,\n rotate,\n scale,\n setCurrentIndex,\n setNatural,\n setSource,\n switchTo,\n syncZoomNatural,\n x,\n y,\n });\n\n const popupNodeRef = useRef<HTMLDivElement | null>(null);\n const popupRef = useMergeRefs<HTMLDivElement>([layerRef, gestures.popupRef, popupNodeRef]);\n const imageRef = useMergeRefs<HTMLImageElement>([flipImageRef, gestures.imageRef]);\n\n const handleLoad = useCallback(\n (event: SyntheticEvent<HTMLImageElement>) => {\n const loaded = event.currentTarget;\n if (!loaded.naturalWidth || !loaded.naturalHeight) return;\n const next = { height: loaded.naturalHeight, width: loaded.naturalWidth };\n setNatural((prev) =>\n prev.width === next.width && prev.height === next.height ? prev : next,\n );\n syncZoomNatural(next);\n },\n [syncZoomNatural],\n );\n\n useEffect(() => {\n const handleResize = () => {\n const next = readViewport();\n setViewport(next);\n syncZoomViewport(next);\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [syncZoomViewport]);\n\n useEffect(() => {\n const { previewSrc, src } = currentEntry;\n if (!previewSrc || previewSrc === src) return;\n let cancelled = false;\n const loader = new window.Image();\n const handleLoaded = () => {\n if (!cancelled) setSource(previewSrc);\n };\n loader.addEventListener('load', handleLoaded);\n loader.src = previewSrc;\n return () => {\n cancelled = true;\n loader.removeEventListener('load', handleLoaded);\n };\n }, [currentEntry]);\n\n useEffect(\n () => () => {\n const thumbnailWasRemoved = !openerEntry.element.isConnected;\n if (thumbnailWasRemoved) endClosePreview(token);\n },\n [openerEntry, token],\n );\n\n return (\n <Dialog.Root modal open onOpenChange={handleDialogOpenChange}>\n <Dialog.Portal container={appElement ?? undefined}>\n {/* Base UI drops a nested dialog's backdrop so overlays don't stack. The viewer is\n not an overlay on the surface it opened from — it replaces the screen — so it\n keeps its own scrim, or a viewer opened from a maskless drawer would show the\n page straight through the letterbox around the image. */}\n <Dialog.Backdrop\n forceRender\n {...styleProps(styles.viewerBackdrop, 'lobe-image-viewer-backdrop')}\n ref={backdropRef}\n style={zIndex === undefined ? undefined : { zIndex }}\n onClick={gestures.onSurfaceClick}\n />\n <Dialog.Popup\n aria-label={currentEntry.element.alt || undefined}\n {...styleProps(styles.viewerPopup, 'lobe-image-viewer-popup')}\n finalFocus={finalFocus}\n // Focus the popup itself instead of Base UI's default (the first\n // tabbable — the close button): a focus-visible close button pins\n // the idle auto-hide open forever and pops its tooltip on open.\n initialFocus={popupNodeRef}\n ref={popupRef}\n style={zIndex === undefined ? undefined : { zIndex: zIndex + 1 }}\n tabIndex={-1}\n onClick={gestures.onSurfaceClick}\n onPointerCancel={gestures.onPointerFinish}\n onPointerDown={gestures.onPointerDown}\n onPointerMove={gestures.onPointerMove}\n onPointerUp={gestures.onPointerFinish}\n >\n <img\n alt={currentEntry.element.alt}\n {...styleProps(styles.viewerImage, 'lobe-image-viewer-image')}\n ref={imageRef}\n src={source}\n style={{\n cursor: gestures.cursor,\n height: imageRect.height,\n left: imageRect.x,\n top: imageRect.y,\n width: imageRect.width,\n }}\n onClick={gestures.onImageClick}\n onDoubleClick={gestures.onImageDoubleClick}\n onLoad={handleLoad}\n />\n <ViewerChrome\n canZoomIn={canZoomIn}\n canZoomOut={canZoomOut}\n chromeRef={chromeRef}\n current={currentIndex}\n fetchImage={currentEntry.options.fetchImage}\n fitRect={fitRect}\n flipHorizontal={flipHorizontal}\n flipVertical={flipVertical}\n hasNext={hasNext}\n hasPrev={hasPrev}\n natural={natural}\n next={next}\n prev={prev}\n rotateLeft={rotateLeft}\n rotateRight={rotateRight}\n rotation={rotation}\n scale={scale}\n source={source}\n toggleActualSize={toggleActualSize}\n toolbarAddon={currentEntry.options.toolbarAddon}\n total={entries.length}\n zoomIn={zoomIn}\n zoomOut={zoomOut}\n onClose={handleClose}\n onDownload={currentEntry.options.onDownload}\n />\n </Dialog.Popup>\n <ToastHost root={appElement ?? undefined} />\n </Dialog.Portal>\n </Dialog.Root>\n );\n});\n\nImageViewer.displayName = 'ImageViewer';\n\nexport default ImageViewer;\n"],"mappings":"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"}
@@ -42,7 +42,10 @@ const usePercentage = (scale, matchScale) => {
42
42
  const getSnapshot = useCallback(() => Math.round(scale.get() / matchScale * 100), [matchScale, scale]);
43
43
  return useSyncExternalStore(subscribe, getSnapshot);
44
44
  };
45
- const Toolbar = memo(({ canZoomIn, canZoomOut, fitRect, flipHorizontal, flipVertical, natural, onDownload, onMoreOpenChange, rotateLeft, rotateRight, rotation, scale, source, toggleActualSize, toolbarAddon, zoomIn, zoomOut }) => {
45
+ const defaultFetchImage = async (src) => {
46
+ return (await fetch(src, { mode: "cors" })).blob();
47
+ };
48
+ const Toolbar = memo(({ canZoomIn, canZoomOut, fetchImage, fitRect, flipHorizontal, flipVertical, natural, onDownload, onMoreOpenChange, rotateLeft, rotateRight, rotation, scale, source, toggleActualSize, toolbarAddon, zoomIn, zoomOut }) => {
46
49
  const { t } = useTranslation(image_default);
47
50
  const [containerEl, setContainerEl] = useState(null);
48
51
  const [copyLoading, setCopyLoading] = useState(false);
@@ -51,9 +54,10 @@ const Toolbar = memo(({ canZoomIn, canZoomOut, fitRect, flipHorizontal, flipVert
51
54
  const percentage = usePercentage(scale, matchScale);
52
55
  const canToggleActualSize = Math.abs(matchScale - 1) > .01;
53
56
  const showFitAffordance = canToggleActualSize && percentage === 100;
57
+ const loadImage = fetchImage ?? defaultFetchImage;
54
58
  const defaultDownload = useCallback(async (src) => {
55
59
  try {
56
- const blob = await (await fetch(src, { mode: "cors" })).blob();
60
+ const blob = await loadImage(src);
57
61
  const blobUrl = URL.createObjectURL(blob);
58
62
  let fileName = getFileNameFromUrl(src);
59
63
  const ext = getExtensionFromMimeType(blob.type);
@@ -62,10 +66,15 @@ const Toolbar = memo(({ canZoomIn, canZoomOut, fitRect, flipHorizontal, flipVert
62
66
  await downloadBlob(blobUrl, fileName);
63
67
  URL.revokeObjectURL(blobUrl);
64
68
  toast.success(t("image.downloadSuccess"));
65
- } catch {
69
+ } catch (error) {
70
+ if (fetchImage) throw error;
66
71
  window.open(src, "_blank", "noopener,noreferrer");
67
72
  }
68
- }, [t]);
73
+ }, [
74
+ fetchImage,
75
+ loadImage,
76
+ t
77
+ ]);
69
78
  const handleDownload = useCallback(async () => {
70
79
  setDownloadLoading(true);
71
80
  try {
@@ -84,7 +93,7 @@ const Toolbar = memo(({ canZoomIn, canZoomOut, fitRect, flipHorizontal, flipVert
84
93
  const handleCopy = useCallback(async () => {
85
94
  setCopyLoading(true);
86
95
  try {
87
- const blob = await (await fetch(source, { mode: "cors" })).blob();
96
+ const blob = await loadImage(source);
88
97
  const clipboardBlob = await getClipboardBlob(blob);
89
98
  await navigator.clipboard.write([new ClipboardItem(clipboardBlob)]);
90
99
  toast.success(t("image.copySuccess"));
@@ -93,7 +102,11 @@ const Toolbar = memo(({ canZoomIn, canZoomOut, fitRect, flipHorizontal, flipVert
93
102
  } finally {
94
103
  setCopyLoading(false);
95
104
  }
96
- }, [source, t]);
105
+ }, [
106
+ loadImage,
107
+ source,
108
+ t
109
+ ]);
97
110
  const moreItems = useMemo(() => [
98
111
  {
99
112
  icon: FlipHorizontal,
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.mjs","names":["imageMessages","Flexbox","ActionIcon"],"sources":["../../../src/Image/viewer/Toolbar.tsx"],"sourcesContent":["'use client';\n\nimport * as stylex from '@stylexjs/stylex';\nimport {\n Copy,\n Download,\n Ellipsis,\n FlipHorizontal,\n FlipVertical,\n RotateCcw,\n RotateCw,\n ZoomIn,\n ZoomOut,\n} from 'lucide-react';\nimport type { MotionValue } from 'motion/react';\nimport { memo, type ReactNode, useCallback, useMemo, useState, useSyncExternalStore } from 'react';\n\nimport { ActionIconImpl as ActionIcon } from '@/ActionIcon/ActionIcon';\nimport DropdownMenu, { type DropdownItem } from '@/DropdownMenu';\nimport { Center, Flexbox } from '@/Flex';\nimport imageMessages from '@/i18n/resources/en/image';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport { styleProps } from '@/styles/stylex/props';\nimport { toast } from '@/Toast';\nimport { TooltipGroup } from '@/Tooltip';\nimport { getClipboardBlob } from '@/utils/blobToPng';\nimport { downloadBlob } from '@/utils/downloadBlob';\n\nimport { actualSizeMarker } from '../marker.stylex';\nimport { styles } from '../style';\nimport ActualSizeIcon from './ActualSizeIcon';\nimport { naturalScale, type Rect, type Rotation, type Size } from './geometry';\n\nconst getFileNameFromUrl = (url: string): string => {\n try {\n const pathname = new URL(url).pathname;\n const match = pathname.match(/\\/([^/]+)$/);\n return match ? decodeURIComponent(match[1]) : 'image';\n } catch {\n return 'image';\n }\n};\n\nconst getExtensionFromMimeType = (mimeType: string): string => {\n const map: Record<string, string> = {\n 'image/svg+xml': 'svg',\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n };\n return map[mimeType?.toLowerCase()] || mimeType?.split('/')[1]?.split('+')[0] || 'png';\n};\n\nconst usePercentage = (scale: MotionValue<number>, matchScale: number): number => {\n const subscribe = useCallback((onChange: () => void) => scale.on('change', onChange), [scale]);\n const getSnapshot = useCallback(\n () => Math.round((scale.get() / matchScale) * 100),\n [matchScale, scale],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n\nexport interface ToolbarProps {\n canZoomIn: boolean;\n canZoomOut: boolean;\n fitRect: Rect;\n flipHorizontal: () => void;\n flipVertical: () => void;\n natural: Size;\n onDownload?: (source: string) => void | Promise<void>;\n onMoreOpenChange?: (open: boolean) => void;\n rotateLeft: () => void;\n rotateRight: () => void;\n rotation: Rotation;\n scale: MotionValue<number>;\n source: string;\n toggleActualSize: () => void;\n toolbarAddon?: ReactNode;\n zoomIn: () => void;\n zoomOut: () => void;\n}\n\nconst Toolbar = memo<ToolbarProps>(\n ({\n canZoomIn,\n canZoomOut,\n fitRect,\n flipHorizontal,\n flipVertical,\n natural,\n onDownload,\n onMoreOpenChange,\n rotateLeft,\n rotateRight,\n rotation,\n scale,\n source,\n toggleActualSize,\n toolbarAddon,\n zoomIn,\n zoomOut,\n }) => {\n const { t } = useTranslation(imageMessages);\n const [containerEl, setContainerEl] = useState<HTMLElement | null>(null);\n const [copyLoading, setCopyLoading] = useState(false);\n const [downloadLoading, setDownloadLoading] = useState(false);\n\n const matchScale = naturalScale(natural, fitRect, rotation);\n const percentage = usePercentage(scale, matchScale);\n // An image small enough that computeFit never scaled it down is already at\n // 100% when fitted, so the two states the toggle switches between are the\n // same state and the control has nothing to do.\n const canToggleActualSize = Math.abs(matchScale - 1) > 0.01;\n // The control reads as \"go to actual size\" by default, and only flips to\n // the fit affordance when it can actually take you back — an image that\n // fits at 100% is at both states at once and would otherwise offer to\n // \"fit to screen\" while already fitted.\n const showFitAffordance = canToggleActualSize && percentage === 100;\n\n const defaultDownload = useCallback(\n async (src: string) => {\n try {\n const response = await fetch(src, { mode: 'cors' });\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n let fileName = getFileNameFromUrl(src);\n const ext = getExtensionFromMimeType(blob.type);\n if (!fileName.includes('.')) {\n fileName = `${fileName}.${ext}`;\n } else if (fileName.endsWith('.svg+xml')) {\n fileName = fileName.replace(/\\.svg\\+xml$/i, '.svg');\n }\n await downloadBlob(blobUrl, fileName);\n URL.revokeObjectURL(blobUrl);\n toast.success(t('image.downloadSuccess'));\n } catch {\n // A cross-origin source without CORS headers (e.g. a presigned URL reached\n // through a redirect, which sends `Origin: null`) can never be read as a\n // blob; opening it lets the browser handle the save instead of failing.\n window.open(src, '_blank', 'noopener,noreferrer');\n }\n },\n [t],\n );\n\n const handleDownload = useCallback(async () => {\n setDownloadLoading(true);\n try {\n await (onDownload ?? defaultDownload)(source);\n } catch {\n toast.error(t('image.downloadFailed'));\n } finally {\n setDownloadLoading(false);\n }\n }, [defaultDownload, onDownload, source, t]);\n\n const handleCopy = useCallback(async () => {\n setCopyLoading(true);\n try {\n const response = await fetch(source, { mode: 'cors' });\n const blob = await response.blob();\n const clipboardBlob = await getClipboardBlob(blob);\n await navigator.clipboard.write([new ClipboardItem(clipboardBlob)]);\n toast.success(t('image.copySuccess'));\n } catch {\n toast.error(t('image.copyFailed'));\n } finally {\n setCopyLoading(false);\n }\n }, [source, t]);\n\n const moreItems = useMemo<DropdownItem[]>(\n () => [\n {\n icon: FlipHorizontal,\n key: 'flip-horizontal',\n label: t('image.flipHorizontal'),\n onClick: flipHorizontal,\n },\n {\n icon: FlipVertical,\n key: 'flip-vertical',\n label: t('image.flipVertical'),\n onClick: flipVertical,\n },\n { icon: RotateCcw, key: 'rotate-left', label: t('image.rotateLeft'), onClick: rotateLeft },\n {\n icon: RotateCw,\n key: 'rotate-right',\n label: t('image.rotateRight'),\n onClick: rotateRight,\n },\n { icon: Copy, key: 'copy', label: t('image.copy'), onClick: handleCopy },\n ],\n [flipHorizontal, flipVertical, handleCopy, rotateLeft, rotateRight, t],\n );\n\n return (\n <TooltipGroup popupContainer={containerEl ?? undefined}>\n <div {...styleProps(styles.toolbar, 'lobe-image-viewer-toolbar')} ref={setContainerEl}>\n <Flexbox horizontal align=\"center\" gap={8} {...stylex.props(styles.toolbarRow)}>\n <ActionIcon\n disabled={!canZoomOut}\n icon={ZoomOut}\n style={{ borderRadius: 999 }}\n title={t('image.zoomOut')}\n onClick={zoomOut}\n />\n <Center horizontal {...stylex.props(styles.toolbarPercentage)}>\n {percentage}%\n </Center>\n <ActionIcon\n disabled={!canZoomIn}\n icon={ZoomIn}\n style={{ borderRadius: 999 }}\n title={t('image.zoomIn')}\n onClick={zoomIn}\n />\n <ActionIcon\n data-actual-size={showFitAffordance ? 'fit' : 'actual'}\n disabled={!canToggleActualSize}\n icon={ActualSizeIcon}\n style={{ borderRadius: 999 }}\n title={showFitAffordance ? t('image.fitToScreen') : t('image.actualSize')}\n xstyle={actualSizeMarker}\n onClick={toggleActualSize}\n />\n <ActionIcon\n icon={Download}\n loading={downloadLoading}\n style={{ borderRadius: 999 }}\n title={t('image.download')}\n onClick={handleDownload}\n />\n <DropdownMenu\n items={moreItems}\n placement=\"top\"\n portalProps={{ container: containerEl ?? undefined }}\n onOpenChange={(open) => onMoreOpenChange?.(open)}\n >\n <ActionIcon\n icon={Ellipsis}\n loading={copyLoading}\n style={{ borderRadius: 999 }}\n title={t('image.more')}\n />\n </DropdownMenu>\n {toolbarAddon}\n </Flexbox>\n </div>\n </TooltipGroup>\n );\n },\n);\n\nToolbar.displayName = 'Toolbar';\n\nexport default Toolbar;\n"],"mappings":"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"}
1
+ {"version":3,"file":"Toolbar.mjs","names":["imageMessages","Flexbox","ActionIcon"],"sources":["../../../src/Image/viewer/Toolbar.tsx"],"sourcesContent":["'use client';\n\nimport * as stylex from '@stylexjs/stylex';\nimport {\n Copy,\n Download,\n Ellipsis,\n FlipHorizontal,\n FlipVertical,\n RotateCcw,\n RotateCw,\n ZoomIn,\n ZoomOut,\n} from 'lucide-react';\nimport type { MotionValue } from 'motion/react';\nimport { memo, type ReactNode, useCallback, useMemo, useState, useSyncExternalStore } from 'react';\n\nimport { ActionIconImpl as ActionIcon } from '@/ActionIcon/ActionIcon';\nimport DropdownMenu, { type DropdownItem } from '@/DropdownMenu';\nimport { Center, Flexbox } from '@/Flex';\nimport imageMessages from '@/i18n/resources/en/image';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport { styleProps } from '@/styles/stylex/props';\nimport { toast } from '@/Toast';\nimport { TooltipGroup } from '@/Tooltip';\nimport { getClipboardBlob } from '@/utils/blobToPng';\nimport { downloadBlob } from '@/utils/downloadBlob';\n\nimport { actualSizeMarker } from '../marker.stylex';\nimport { styles } from '../style';\nimport ActualSizeIcon from './ActualSizeIcon';\nimport { naturalScale, type Rect, type Rotation, type Size } from './geometry';\n\nconst getFileNameFromUrl = (url: string): string => {\n try {\n const pathname = new URL(url).pathname;\n const match = pathname.match(/\\/([^/]+)$/);\n return match ? decodeURIComponent(match[1]) : 'image';\n } catch {\n return 'image';\n }\n};\n\nconst getExtensionFromMimeType = (mimeType: string): string => {\n const map: Record<string, string> = {\n 'image/svg+xml': 'svg',\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n };\n return map[mimeType?.toLowerCase()] || mimeType?.split('/')[1]?.split('+')[0] || 'png';\n};\n\nconst usePercentage = (scale: MotionValue<number>, matchScale: number): number => {\n const subscribe = useCallback((onChange: () => void) => scale.on('change', onChange), [scale]);\n const getSnapshot = useCallback(\n () => Math.round((scale.get() / matchScale) * 100),\n [matchScale, scale],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot);\n};\n\nconst defaultFetchImage = async (src: string): Promise<Blob> => {\n const response = await fetch(src, { mode: 'cors' });\n return response.blob();\n};\n\nexport interface ToolbarProps {\n canZoomIn: boolean;\n canZoomOut: boolean;\n fetchImage?: (source: string) => Promise<Blob>;\n fitRect: Rect;\n flipHorizontal: () => void;\n flipVertical: () => void;\n natural: Size;\n onDownload?: (source: string) => void | Promise<void>;\n onMoreOpenChange?: (open: boolean) => void;\n rotateLeft: () => void;\n rotateRight: () => void;\n rotation: Rotation;\n scale: MotionValue<number>;\n source: string;\n toggleActualSize: () => void;\n toolbarAddon?: ReactNode;\n zoomIn: () => void;\n zoomOut: () => void;\n}\n\nconst Toolbar = memo<ToolbarProps>(\n ({\n canZoomIn,\n canZoomOut,\n fetchImage,\n fitRect,\n flipHorizontal,\n flipVertical,\n natural,\n onDownload,\n onMoreOpenChange,\n rotateLeft,\n rotateRight,\n rotation,\n scale,\n source,\n toggleActualSize,\n toolbarAddon,\n zoomIn,\n zoomOut,\n }) => {\n const { t } = useTranslation(imageMessages);\n const [containerEl, setContainerEl] = useState<HTMLElement | null>(null);\n const [copyLoading, setCopyLoading] = useState(false);\n const [downloadLoading, setDownloadLoading] = useState(false);\n\n const matchScale = naturalScale(natural, fitRect, rotation);\n const percentage = usePercentage(scale, matchScale);\n // An image small enough that computeFit never scaled it down is already at\n // 100% when fitted, so the two states the toggle switches between are the\n // same state and the control has nothing to do.\n const canToggleActualSize = Math.abs(matchScale - 1) > 0.01;\n // The control reads as \"go to actual size\" by default, and only flips to\n // the fit affordance when it can actually take you back — an image that\n // fits at 100% is at both states at once and would otherwise offer to\n // \"fit to screen\" while already fitted.\n const showFitAffordance = canToggleActualSize && percentage === 100;\n\n const loadImage = fetchImage ?? defaultFetchImage;\n\n const defaultDownload = useCallback(\n async (src: string) => {\n try {\n const blob = await loadImage(src);\n const blobUrl = URL.createObjectURL(blob);\n let fileName = getFileNameFromUrl(src);\n const ext = getExtensionFromMimeType(blob.type);\n if (!fileName.includes('.')) {\n fileName = `${fileName}.${ext}`;\n } else if (fileName.endsWith('.svg+xml')) {\n fileName = fileName.replace(/\\.svg\\+xml$/i, '.svg');\n }\n await downloadBlob(blobUrl, fileName);\n URL.revokeObjectURL(blobUrl);\n toast.success(t('image.downloadSuccess'));\n } catch (error) {\n if (fetchImage) throw error;\n // A cross-origin source without CORS headers (e.g. a presigned URL reached\n // through a redirect, which sends `Origin: null`) can never be read as a\n // blob; opening it lets the browser handle the save instead of failing.\n window.open(src, '_blank', 'noopener,noreferrer');\n }\n },\n [fetchImage, loadImage, t],\n );\n\n const handleDownload = useCallback(async () => {\n setDownloadLoading(true);\n try {\n await (onDownload ?? defaultDownload)(source);\n } catch {\n toast.error(t('image.downloadFailed'));\n } finally {\n setDownloadLoading(false);\n }\n }, [defaultDownload, onDownload, source, t]);\n\n const handleCopy = useCallback(async () => {\n setCopyLoading(true);\n try {\n const blob = await loadImage(source);\n const clipboardBlob = await getClipboardBlob(blob);\n await navigator.clipboard.write([new ClipboardItem(clipboardBlob)]);\n toast.success(t('image.copySuccess'));\n } catch {\n toast.error(t('image.copyFailed'));\n } finally {\n setCopyLoading(false);\n }\n }, [loadImage, source, t]);\n\n const moreItems = useMemo<DropdownItem[]>(\n () => [\n {\n icon: FlipHorizontal,\n key: 'flip-horizontal',\n label: t('image.flipHorizontal'),\n onClick: flipHorizontal,\n },\n {\n icon: FlipVertical,\n key: 'flip-vertical',\n label: t('image.flipVertical'),\n onClick: flipVertical,\n },\n { icon: RotateCcw, key: 'rotate-left', label: t('image.rotateLeft'), onClick: rotateLeft },\n {\n icon: RotateCw,\n key: 'rotate-right',\n label: t('image.rotateRight'),\n onClick: rotateRight,\n },\n { icon: Copy, key: 'copy', label: t('image.copy'), onClick: handleCopy },\n ],\n [flipHorizontal, flipVertical, handleCopy, rotateLeft, rotateRight, t],\n );\n\n return (\n <TooltipGroup popupContainer={containerEl ?? undefined}>\n <div {...styleProps(styles.toolbar, 'lobe-image-viewer-toolbar')} ref={setContainerEl}>\n <Flexbox horizontal align=\"center\" gap={8} {...stylex.props(styles.toolbarRow)}>\n <ActionIcon\n disabled={!canZoomOut}\n icon={ZoomOut}\n style={{ borderRadius: 999 }}\n title={t('image.zoomOut')}\n onClick={zoomOut}\n />\n <Center horizontal {...stylex.props(styles.toolbarPercentage)}>\n {percentage}%\n </Center>\n <ActionIcon\n disabled={!canZoomIn}\n icon={ZoomIn}\n style={{ borderRadius: 999 }}\n title={t('image.zoomIn')}\n onClick={zoomIn}\n />\n <ActionIcon\n data-actual-size={showFitAffordance ? 'fit' : 'actual'}\n disabled={!canToggleActualSize}\n icon={ActualSizeIcon}\n style={{ borderRadius: 999 }}\n title={showFitAffordance ? t('image.fitToScreen') : t('image.actualSize')}\n xstyle={actualSizeMarker}\n onClick={toggleActualSize}\n />\n <ActionIcon\n icon={Download}\n loading={downloadLoading}\n style={{ borderRadius: 999 }}\n title={t('image.download')}\n onClick={handleDownload}\n />\n <DropdownMenu\n items={moreItems}\n placement=\"top\"\n portalProps={{ container: containerEl ?? undefined }}\n onOpenChange={(open) => onMoreOpenChange?.(open)}\n >\n <ActionIcon\n icon={Ellipsis}\n loading={copyLoading}\n style={{ borderRadius: 999 }}\n title={t('image.more')}\n />\n </DropdownMenu>\n {toolbarAddon}\n </Flexbox>\n </div>\n </TooltipGroup>\n );\n },\n);\n\nToolbar.displayName = 'Toolbar';\n\nexport default Toolbar;\n"],"mappings":"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"}