@superad/ui 0.1.1

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 (343) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/es/Button/Button.d.mts +7 -0
  4. package/es/Button/Button.mjs +29 -0
  5. package/es/Button/Button.mjs.map +1 -0
  6. package/es/Button/index.d.mts +3 -0
  7. package/es/Button/index.d.ts +1 -0
  8. package/es/Button/index.js +1 -0
  9. package/es/Button/index.mjs +2 -0
  10. package/es/Button/style.mjs +33 -0
  11. package/es/Button/style.mjs.map +1 -0
  12. package/es/Button/type.d.mts +21 -0
  13. package/es/ConfigProvider/index.d.mts +9 -0
  14. package/es/ConfigProvider/index.d.ts +1 -0
  15. package/es/ConfigProvider/index.js +1 -0
  16. package/es/ConfigProvider/index.mjs +82 -0
  17. package/es/ConfigProvider/index.mjs.map +1 -0
  18. package/es/ConfigProvider/type.d.mts +24 -0
  19. package/es/DraggablePanel/DraggablePanel.d.mts +6 -0
  20. package/es/DraggablePanel/DraggablePanel.mjs +104 -0
  21. package/es/DraggablePanel/DraggablePanel.mjs.map +1 -0
  22. package/es/DraggablePanel/index.d.mts +3 -0
  23. package/es/DraggablePanel/index.d.ts +1 -0
  24. package/es/DraggablePanel/index.js +1 -0
  25. package/es/DraggablePanel/index.mjs +3 -0
  26. package/es/DraggablePanel/style.mjs +86 -0
  27. package/es/DraggablePanel/style.mjs.map +1 -0
  28. package/es/DraggablePanel/type.d.mts +27 -0
  29. package/es/Flex/Center.d.mts +7 -0
  30. package/es/Flex/Center.mjs +14 -0
  31. package/es/Flex/Center.mjs.map +1 -0
  32. package/es/Flex/FlexBasic.d.mts +6 -0
  33. package/es/Flex/FlexBasic.mjs +46 -0
  34. package/es/Flex/FlexBasic.mjs.map +1 -0
  35. package/es/Flex/Flexbox.d.mts +2 -0
  36. package/es/Flex/index.d.mts +5 -0
  37. package/es/Flex/index.d.ts +1 -0
  38. package/es/Flex/index.js +1 -0
  39. package/es/Flex/index.mjs +3 -0
  40. package/es/Flex/type.d.mts +35 -0
  41. package/es/Flex/utils.mjs +24 -0
  42. package/es/Flex/utils.mjs.map +1 -0
  43. package/es/FontLoader/index.d.mts +9 -0
  44. package/es/FontLoader/index.d.ts +1 -0
  45. package/es/FontLoader/index.js +1 -0
  46. package/es/FontLoader/index.mjs +23 -0
  47. package/es/FontLoader/index.mjs.map +1 -0
  48. package/es/GlobalFocusRing/index.d.mts +6 -0
  49. package/es/GlobalFocusRing/index.d.ts +1 -0
  50. package/es/GlobalFocusRing/index.js +1 -0
  51. package/es/GlobalFocusRing/index.mjs +204 -0
  52. package/es/GlobalFocusRing/index.mjs.map +1 -0
  53. package/es/GlobalFocusRing/style.mjs +35 -0
  54. package/es/GlobalFocusRing/style.mjs.map +1 -0
  55. package/es/Provider.d.mts +5 -0
  56. package/es/Table/canvas/canvasPalette.mjs +11 -0
  57. package/es/Table/canvas/canvasPalette.mjs.map +1 -0
  58. package/es/Table/canvas/chipPainters.mjs +408 -0
  59. package/es/Table/canvas/chipPainters.mjs.map +1 -0
  60. package/es/Table/canvas/controlPainters.mjs +193 -0
  61. package/es/Table/canvas/controlPainters.mjs.map +1 -0
  62. package/es/Table/canvas/groupTitle.mjs +181 -0
  63. package/es/Table/canvas/groupTitle.mjs.map +1 -0
  64. package/es/Table/canvas/paintUtils.mjs +70 -0
  65. package/es/Table/canvas/paintUtils.mjs.map +1 -0
  66. package/es/Table/canvas/rowSeries.mjs +130 -0
  67. package/es/Table/canvas/rowSeries.mjs.map +1 -0
  68. package/es/Table/canvas/textPainters.mjs +97 -0
  69. package/es/Table/canvas/textPainters.mjs.map +1 -0
  70. package/es/Table/canvas/theme.d.mts +14 -0
  71. package/es/Table/canvas/theme.mjs +276 -0
  72. package/es/Table/canvas/theme.mjs.map +1 -0
  73. package/es/Table/core/cellValue.d.mts +1 -0
  74. package/es/Table/core/cellValue.mjs +240 -0
  75. package/es/Table/core/cellValue.mjs.map +1 -0
  76. package/es/Table/core/rowSelection.mjs +138 -0
  77. package/es/Table/core/rowSelection.mjs.map +1 -0
  78. package/es/Table/core/size.mjs +60 -0
  79. package/es/Table/core/size.mjs.map +1 -0
  80. package/es/Table/core/tableI18n.d.mts +11 -0
  81. package/es/Table/core/tableI18n.mjs +123 -0
  82. package/es/Table/core/tableI18n.mjs.map +1 -0
  83. package/es/Table/core/tagTone.mjs +245 -0
  84. package/es/Table/core/tagTone.mjs.map +1 -0
  85. package/es/Table/core/zIndex.mjs +7 -0
  86. package/es/Table/core/zIndex.mjs.map +1 -0
  87. package/es/Table/index.d.mts +6 -0
  88. package/es/Table/index.d.ts +1 -0
  89. package/es/Table/index.js +1 -0
  90. package/es/Table/index.mjs +6 -0
  91. package/es/Table/react/Table.d.mts +9 -0
  92. package/es/Table/react/Table.mjs +407 -0
  93. package/es/Table/react/Table.mjs.map +1 -0
  94. package/es/Table/react/TableColumnSettings.mjs +65 -0
  95. package/es/Table/react/TableColumnSettings.mjs.map +1 -0
  96. package/es/Table/react/a11y.mjs +72 -0
  97. package/es/Table/react/a11y.mjs.map +1 -0
  98. package/es/Table/react/editors/base.mjs +427 -0
  99. package/es/Table/react/editors/base.mjs.map +1 -0
  100. package/es/Table/react/editors/inline.mjs +225 -0
  101. package/es/Table/react/editors/inline.mjs.map +1 -0
  102. package/es/Table/react/editors/picker.mjs +368 -0
  103. package/es/Table/react/editors/picker.mjs.map +1 -0
  104. package/es/Table/react/overlayChips.mjs +50 -0
  105. package/es/Table/react/overlayChips.mjs.map +1 -0
  106. package/es/Table/react/sameRenderInputs.mjs +50 -0
  107. package/es/Table/react/sameRenderInputs.mjs.map +1 -0
  108. package/es/Table/react/style.mjs +650 -0
  109. package/es/Table/react/style.mjs.map +1 -0
  110. package/es/Table/react/useTableCellEvents.mjs +32 -0
  111. package/es/Table/react/useTableCellEvents.mjs.map +1 -0
  112. package/es/Table/react/useTableColumnSettings.mjs +107 -0
  113. package/es/Table/react/useTableColumnSettings.mjs.map +1 -0
  114. package/es/Table/react/useTableEditorSync.mjs +41 -0
  115. package/es/Table/react/useTableEditorSync.mjs.map +1 -0
  116. package/es/Table/react/useTableHoverHint.mjs +62 -0
  117. package/es/Table/react/useTableHoverHint.mjs.map +1 -0
  118. package/es/Table/react/useTableKeyboard.mjs +41 -0
  119. package/es/Table/react/useTableKeyboard.mjs.map +1 -0
  120. package/es/Table/react/useTableRenderState.mjs +194 -0
  121. package/es/Table/react/useTableRenderState.mjs.map +1 -0
  122. package/es/Table/react/useTableScroll.mjs +33 -0
  123. package/es/Table/react/useTableScroll.mjs.map +1 -0
  124. package/es/Table/react/useTableSelectionInfo.mjs +48 -0
  125. package/es/Table/react/useTableSelectionInfo.mjs.map +1 -0
  126. package/es/Table/react/useTableVTableEvents.mjs +132 -0
  127. package/es/Table/react/useTableVTableEvents.mjs.map +1 -0
  128. package/es/Table/type.d.mts +279 -0
  129. package/es/Table/type.mjs +34 -0
  130. package/es/Table/type.mjs.map +1 -0
  131. package/es/Table/vtable/columns.d.mts +17 -0
  132. package/es/Table/vtable/columns.mjs +381 -0
  133. package/es/Table/vtable/columns.mjs.map +1 -0
  134. package/es/Table/vtable/frozenShadow.mjs +27 -0
  135. package/es/Table/vtable/frozenShadow.mjs.map +1 -0
  136. package/es/Table/vtable/interaction.mjs +175 -0
  137. package/es/Table/vtable/interaction.mjs.map +1 -0
  138. package/es/Table/vtable/option.d.mts +39 -0
  139. package/es/Table/vtable/option.mjs +232 -0
  140. package/es/Table/vtable/option.mjs.map +1 -0
  141. package/es/Table/vtable/seriesSelection.mjs +374 -0
  142. package/es/Table/vtable/seriesSelection.mjs.map +1 -0
  143. package/es/Table/vtable/vtableAdapter.mjs +96 -0
  144. package/es/Table/vtable/vtableAdapter.mjs.map +1 -0
  145. package/es/ThemeProvider/AppElementContext.d.mts +5 -0
  146. package/es/ThemeProvider/AppElementContext.mjs +10 -0
  147. package/es/ThemeProvider/AppElementContext.mjs.map +1 -0
  148. package/es/ThemeProvider/ConfigProvider.mjs +29 -0
  149. package/es/ThemeProvider/ConfigProvider.mjs.map +1 -0
  150. package/es/ThemeProvider/GlobalStyle/antdOverride.mjs +73 -0
  151. package/es/ThemeProvider/GlobalStyle/antdOverride.mjs.map +1 -0
  152. package/es/ThemeProvider/GlobalStyle/essential.mjs +50 -0
  153. package/es/ThemeProvider/GlobalStyle/essential.mjs.map +1 -0
  154. package/es/ThemeProvider/GlobalStyle/global.mjs +90 -0
  155. package/es/ThemeProvider/GlobalStyle/global.mjs.map +1 -0
  156. package/es/ThemeProvider/GlobalStyle/index.mjs +14 -0
  157. package/es/ThemeProvider/GlobalStyle/index.mjs.map +1 -0
  158. package/es/ThemeProvider/Meta.d.mts +7 -0
  159. package/es/ThemeProvider/Meta.mjs +48 -0
  160. package/es/ThemeProvider/Meta.mjs.map +1 -0
  161. package/es/ThemeProvider/ThemeProvider.d.mts +6 -0
  162. package/es/ThemeProvider/ThemeProvider.mjs +89 -0
  163. package/es/ThemeProvider/ThemeProvider.mjs.map +1 -0
  164. package/es/ThemeProvider/constants.d.mts +5 -0
  165. package/es/ThemeProvider/constants.mjs +6 -0
  166. package/es/ThemeProvider/constants.mjs.map +1 -0
  167. package/es/ThemeProvider/index.d.mts +6 -0
  168. package/es/ThemeProvider/index.d.ts +1 -0
  169. package/es/ThemeProvider/index.js +1 -0
  170. package/es/ThemeProvider/index.mjs +5 -0
  171. package/es/ThemeProvider/type.d.mts +48 -0
  172. package/es/_virtual/_rolldown/runtime.mjs +13 -0
  173. package/es/color/colors/blue.d.mts +6 -0
  174. package/es/color/colors/blue.mjs +67 -0
  175. package/es/color/colors/blue.mjs.map +1 -0
  176. package/es/color/colors/cyan.d.mts +6 -0
  177. package/es/color/colors/cyan.mjs +67 -0
  178. package/es/color/colors/cyan.mjs.map +1 -0
  179. package/es/color/colors/geekblue.d.mts +6 -0
  180. package/es/color/colors/geekblue.mjs +67 -0
  181. package/es/color/colors/geekblue.mjs.map +1 -0
  182. package/es/color/colors/gold.d.mts +6 -0
  183. package/es/color/colors/gold.mjs +67 -0
  184. package/es/color/colors/gold.mjs.map +1 -0
  185. package/es/color/colors/gray.d.mts +6 -0
  186. package/es/color/colors/gray.mjs +67 -0
  187. package/es/color/colors/gray.mjs.map +1 -0
  188. package/es/color/colors/green.d.mts +6 -0
  189. package/es/color/colors/green.mjs +67 -0
  190. package/es/color/colors/green.mjs.map +1 -0
  191. package/es/color/colors/index.d.mts +22 -0
  192. package/es/color/colors/index.mjs +35 -0
  193. package/es/color/colors/index.mjs.map +1 -0
  194. package/es/color/colors/lime.d.mts +6 -0
  195. package/es/color/colors/lime.mjs +67 -0
  196. package/es/color/colors/lime.mjs.map +1 -0
  197. package/es/color/colors/magenta.d.mts +6 -0
  198. package/es/color/colors/magenta.mjs +67 -0
  199. package/es/color/colors/magenta.mjs.map +1 -0
  200. package/es/color/colors/orange.d.mts +6 -0
  201. package/es/color/colors/orange.mjs +67 -0
  202. package/es/color/colors/orange.mjs.map +1 -0
  203. package/es/color/colors/primary.d.mts +6 -0
  204. package/es/color/colors/primary.mjs +67 -0
  205. package/es/color/colors/primary.mjs.map +1 -0
  206. package/es/color/colors/purple.d.mts +6 -0
  207. package/es/color/colors/purple.mjs +67 -0
  208. package/es/color/colors/purple.mjs.map +1 -0
  209. package/es/color/colors/red.d.mts +6 -0
  210. package/es/color/colors/red.mjs +67 -0
  211. package/es/color/colors/red.mjs.map +1 -0
  212. package/es/color/colors/volcano.d.mts +6 -0
  213. package/es/color/colors/volcano.mjs +67 -0
  214. package/es/color/colors/volcano.mjs.map +1 -0
  215. package/es/color/colors/yellow.d.mts +6 -0
  216. package/es/color/colors/yellow.mjs +67 -0
  217. package/es/color/colors/yellow.mjs.map +1 -0
  218. package/es/color/index.d.mts +23 -0
  219. package/es/color/index.d.ts +1 -0
  220. package/es/color/index.js +1 -0
  221. package/es/color/index.mjs +22 -0
  222. package/es/color/neutrals/index.d.mts +13 -0
  223. package/es/color/neutrals/index.mjs +17 -0
  224. package/es/color/neutrals/index.mjs.map +1 -0
  225. package/es/color/neutrals/mauve.d.mts +6 -0
  226. package/es/color/neutrals/mauve.mjs +67 -0
  227. package/es/color/neutrals/mauve.mjs.map +1 -0
  228. package/es/color/neutrals/olive.d.mts +6 -0
  229. package/es/color/neutrals/olive.mjs +67 -0
  230. package/es/color/neutrals/olive.mjs.map +1 -0
  231. package/es/color/neutrals/sage.d.mts +6 -0
  232. package/es/color/neutrals/sage.mjs +67 -0
  233. package/es/color/neutrals/sage.mjs.map +1 -0
  234. package/es/color/neutrals/sand.d.mts +6 -0
  235. package/es/color/neutrals/sand.mjs +67 -0
  236. package/es/color/neutrals/sand.mjs.map +1 -0
  237. package/es/color/neutrals/slate.d.mts +6 -0
  238. package/es/color/neutrals/slate.mjs +67 -0
  239. package/es/color/neutrals/slate.mjs.map +1 -0
  240. package/es/color/types.d.mts +16 -0
  241. package/es/eslint/index.d.mts +26 -0
  242. package/es/eslint/index.d.ts +1 -0
  243. package/es/eslint/index.js +1 -0
  244. package/es/eslint/index.mjs +11 -0
  245. package/es/eslint/index.mjs.map +1 -0
  246. package/es/hooks/useIsClient.d.mts +8 -0
  247. package/es/hooks/useIsClient.mjs +13 -0
  248. package/es/hooks/useIsClient.mjs.map +1 -0
  249. package/es/i18n/context.d.mts +7 -0
  250. package/es/i18n/context.mjs +16 -0
  251. package/es/i18n/context.mjs.map +1 -0
  252. package/es/i18n/index.d.mts +6 -0
  253. package/es/i18n/index.d.ts +1 -0
  254. package/es/i18n/index.js +1 -0
  255. package/es/i18n/index.mjs +5 -0
  256. package/es/i18n/resources/en/common.d.mts +35 -0
  257. package/es/i18n/resources/en/common.mjs +36 -0
  258. package/es/i18n/resources/en/common.mjs.map +1 -0
  259. package/es/i18n/resources/en/index.d.mts +7 -0
  260. package/es/i18n/resources/en/index.mjs +8 -0
  261. package/es/i18n/resources/en/index.mjs.map +1 -0
  262. package/es/i18n/resources/zhCn/common.d.mts +35 -0
  263. package/es/i18n/resources/zhCn/common.mjs +36 -0
  264. package/es/i18n/resources/zhCn/common.mjs.map +1 -0
  265. package/es/i18n/resources/zhCn/index.d.mts +7 -0
  266. package/es/i18n/resources/zhCn/index.mjs +8 -0
  267. package/es/i18n/resources/zhCn/index.mjs.map +1 -0
  268. package/es/i18n/types.d.mts +22 -0
  269. package/es/i18n/useTranslation.d.mts +12 -0
  270. package/es/i18n/useTranslation.mjs +22 -0
  271. package/es/i18n/useTranslation.mjs.map +1 -0
  272. package/es/index.d.mts +43 -0
  273. package/es/index.mjs +26 -0
  274. package/es/storybook/StoryBook/StoryBook.d.mts +7 -0
  275. package/es/storybook/StoryBook/StoryBook.mjs +52 -0
  276. package/es/storybook/StoryBook/StoryBook.mjs.map +1 -0
  277. package/es/storybook/StoryBook/index.d.mts +3 -0
  278. package/es/storybook/StoryBook/style.mjs +97 -0
  279. package/es/storybook/StoryBook/style.mjs.map +1 -0
  280. package/es/storybook/StoryBook/type.d.mts +13 -0
  281. package/es/storybook/index.d.mts +4 -0
  282. package/es/storybook/index.d.ts +1 -0
  283. package/es/storybook/index.js +1 -0
  284. package/es/storybook/index.mjs +2 -0
  285. package/es/styles/classNames.d.mts +11 -0
  286. package/es/styles/classNames.mjs +12 -0
  287. package/es/styles/classNames.mjs.map +1 -0
  288. package/es/styles/customTheme.d.mts +36 -0
  289. package/es/styles/customTheme.mjs +71 -0
  290. package/es/styles/customTheme.mjs.map +1 -0
  291. package/es/styles/index.d.mts +6 -0
  292. package/es/styles/index.d.ts +1 -0
  293. package/es/styles/index.js +1 -0
  294. package/es/styles/index.mjs +6 -0
  295. package/es/styles/theme/algorithms/darkAlgorithm.mjs +18 -0
  296. package/es/styles/theme/algorithms/darkAlgorithm.mjs.map +1 -0
  297. package/es/styles/theme/algorithms/lightAlgorithm.mjs +18 -0
  298. package/es/styles/theme/algorithms/lightAlgorithm.mjs.map +1 -0
  299. package/es/styles/theme/algorithms/resolveScales.mjs +25 -0
  300. package/es/styles/theme/algorithms/resolveScales.mjs.map +1 -0
  301. package/es/styles/theme/antdTheme.mjs +31 -0
  302. package/es/styles/theme/antdTheme.mjs.map +1 -0
  303. package/es/styles/theme/customStylish.d.mts +7 -0
  304. package/es/styles/theme/customStylish.mjs +158 -0
  305. package/es/styles/theme/customStylish.mjs.map +1 -0
  306. package/es/styles/theme/customStylishStatic.d.mts +6 -0
  307. package/es/styles/theme/customStylishStatic.mjs +156 -0
  308. package/es/styles/theme/customStylishStatic.mjs.map +1 -0
  309. package/es/styles/theme/customToken.d.mts +7 -0
  310. package/es/styles/theme/customToken.mjs +127 -0
  311. package/es/styles/theme/customToken.mjs.map +1 -0
  312. package/es/styles/theme/generateColorPalette.d.mts +16 -0
  313. package/es/styles/theme/generateColorPalette.mjs +170 -0
  314. package/es/styles/theme/generateColorPalette.mjs.map +1 -0
  315. package/es/styles/theme/token/base.mjs +64 -0
  316. package/es/styles/theme/token/base.mjs.map +1 -0
  317. package/es/styles/theme/token/dark.mjs +37 -0
  318. package/es/styles/theme/token/dark.mjs.map +1 -0
  319. package/es/styles/theme/token/light.mjs +37 -0
  320. package/es/styles/theme/token/light.mjs.map +1 -0
  321. package/es/types/customStylish.d.mts +28 -0
  322. package/es/types/customToken.d.mts +34 -0
  323. package/es/types/customToken.mjs +32 -0
  324. package/es/types/customToken.mjs.map +1 -0
  325. package/es/types/index.d.mts +15 -0
  326. package/es/types/index.d.ts +1 -0
  327. package/es/types/index.js +1 -0
  328. package/es/types/index.mjs +2 -0
  329. package/es/utils/composeEventHandlers.d.mts +5 -0
  330. package/es/utils/composeEventHandlers.mjs +10 -0
  331. package/es/utils/composeEventHandlers.mjs.map +1 -0
  332. package/es/utils/copyToClipboard.d.mts +5 -0
  333. package/es/utils/copyToClipboard.mjs +28 -0
  334. package/es/utils/copyToClipboard.mjs.map +1 -0
  335. package/es/utils/dom.d.mts +15 -0
  336. package/es/utils/dom.mjs +16 -0
  337. package/es/utils/dom.mjs.map +1 -0
  338. package/es/utils/genCdnUrl.d.mts +12 -0
  339. package/es/utils/genCdnUrl.mjs +17 -0
  340. package/es/utils/genCdnUrl.mjs.map +1 -0
  341. package/es/utils/safeReadableColor.mjs +6 -0
  342. package/es/utils/safeReadableColor.mjs.map +1 -0
  343. package/package.json +96 -0
@@ -0,0 +1,276 @@
1
+ import { contrastRatio, parseHex } from "../core/tagTone.mjs";
2
+ import { parseToRgb, rgba } from "polished";
3
+ //#region src/Table/canvas/theme.ts
4
+ /**
5
+ * SuperAd Table visual language, aligned with the Figma Community kit.
6
+ * White header, light bottom hairlines, 44px rows, neutral group rows and a
7
+ * restrained brand-blue accent. Values are fallbacks; createTableTheme still
8
+ * prefers Ant Design / antd-style tokens where they exist.
9
+ */
10
+ const TABLE_CHROME = {
11
+ accent: "#1F5EFF",
12
+ fontFamily: "PingFang SC,Helvetica Neue,Microsoft Yahei,system-ui,'-apple-system',segoe ui,Roboto,Helvetica,Arial,sans-serif",
13
+ border: "#F4F4F5",
14
+ group: "#FAFAFA",
15
+ groupDark: "#161616",
16
+ hover: "#FAFAFA",
17
+ rowHover: "#F5F5F5",
18
+ overlayBorder: "#E4E4E7",
19
+ paddingX: 8,
20
+ pillBg: "#F4F4F5",
21
+ starOff: "#F4F4F5",
22
+ starOn: "#71717A",
23
+ success: "#16A34A",
24
+ text: "#18181B",
25
+ textAux: "#71717A",
26
+ textMuted: "#A1A1AA"
27
+ };
28
+ /**
29
+ * True when a CSS colour paints fully opaque. parseToRgb reports opaque colours without an
30
+ * alpha at all rather than as 1, and throws on anything it cannot parse (which we treat as
31
+ * not opaque, so callers fall back).
32
+ */
33
+ const isOpaqueColor = (color) => {
34
+ try {
35
+ const parsed = parseToRgb(color);
36
+ const alpha = "alpha" in parsed ? parsed.alpha : void 0;
37
+ return alpha == null || alpha >= 1;
38
+ } catch {
39
+ return false;
40
+ }
41
+ };
42
+ /**
43
+ * Group title rows sit above the rows that scroll beneath them, so their fill has to be
44
+ * opaque — a translucent token would let content show through the sticky title. Keep the
45
+ * token when it is opaque and fall back to the built-in neutral when it is not, so a custom
46
+ * opaque colour still themes the group rows.
47
+ */
48
+ const resolveGroupFill = (token) => {
49
+ const fallback = token?.isDarkMode ? TABLE_CHROME.groupDark : TABLE_CHROME.group;
50
+ const fill = token?.colorFillQuaternary;
51
+ if (fill == null) return fallback;
52
+ return isOpaqueColor(fill) ? fill : fallback;
53
+ };
54
+ const resolveTableCanvasPalette = (token) => ({
55
+ accent: token?.colorPrimary ?? TABLE_CHROME.accent,
56
+ fontFamily: token?.fontFamily ?? TABLE_CHROME.fontFamily,
57
+ isDarkMode: Boolean(token?.isDarkMode),
58
+ checkboxBg: token?.colorBgContainer ?? "#FFFFFF",
59
+ overlayBorder: token?.colorBorderSecondary ?? TABLE_CHROME.overlayBorder,
60
+ pillBg: token?.colorFillTertiary ?? TABLE_CHROME.pillBg,
61
+ rowHover: token?.colorFillTertiary ?? TABLE_CHROME.rowHover,
62
+ starOff: token?.colorFillTertiary ?? TABLE_CHROME.starOff,
63
+ starOn: token?.colorTextTertiary ?? TABLE_CHROME.starOn,
64
+ success: token?.colorSuccess ?? TABLE_CHROME.success,
65
+ text: token?.colorText ?? TABLE_CHROME.text,
66
+ textAux: token?.colorTextSecondary ?? TABLE_CHROME.textAux,
67
+ textMuted: token?.colorTextTertiary ?? TABLE_CHROME.textMuted
68
+ });
69
+ /** 画在 `accent` 之上的前景色候选:深、浅各一个,取对比度更高的那个。 */
70
+ const ON_ACCENT_DARK = "#18181B";
71
+ const ON_ACCENT_LIGHT = "#FFFFFF";
72
+ const ON_ACCENT_DARK_RGB = {
73
+ b: 27,
74
+ g: 24,
75
+ r: 24
76
+ };
77
+ const ON_ACCENT_LIGHT_RGB = {
78
+ b: 255,
79
+ g: 255,
80
+ r: 255
81
+ };
82
+ /**
83
+ * 画在 `accent` 之上的前景色(对勾、半选短横)。
84
+ *
85
+ * **不能写死白色。** `accent` 来自 `token.colorPrimary`,而主色不一定深 —— 暗色下实测是
86
+ * `#eeeeee`,白对勾画上去对比度只有 **1.1:1**,等于看不见(浅色下 `accent` 是 `#222222`,
87
+ * 白对勾才是对的)。按对比度二选一,两种主题都对。
88
+ */
89
+ const resolveOnAccent = (accent) => {
90
+ const rgb = parseHex(accent);
91
+ if (!rgb) return ON_ACCENT_LIGHT;
92
+ return contrastRatio(rgb, ON_ACCENT_DARK_RGB) >= contrastRatio(rgb, ON_ACCENT_LIGHT_RGB) ? ON_ACCENT_DARK : ON_ACCENT_LIGHT;
93
+ };
94
+ /** SuperAd rows use a top hairline so separators stay visible; vertical borders are opt-in. */
95
+ const hairline = [
96
+ 1,
97
+ 1,
98
+ 0,
99
+ 0
100
+ ];
101
+ /** Default SuperAd table: horizontal top row separator only. */
102
+ const hairlineNoVertical = [
103
+ 1,
104
+ 0,
105
+ 0,
106
+ 0
107
+ ];
108
+ const createTableTheme = (token, { showVerticalBorder = false } = {}) => {
109
+ const borderLineWidth = showVerticalBorder ? hairline : hairlineNoVertical;
110
+ const headerBorderLineWidth = showVerticalBorder ? [
111
+ 0,
112
+ 1,
113
+ 0,
114
+ 0
115
+ ] : [
116
+ 0,
117
+ 0,
118
+ 0,
119
+ 0
120
+ ];
121
+ const accent = token.colorPrimary ?? TABLE_CHROME.accent;
122
+ const shadowInk = token.isDarkMode ? "255, 255, 255" : "24, 24, 27";
123
+ const shadowAlpha = token.isDarkMode ? .14 : .06;
124
+ const border = token.colorBorderSecondary ?? TABLE_CHROME.border;
125
+ const headerBorderColor = showVerticalBorder ? border : "transparent";
126
+ const groupFill = resolveGroupFill(token);
127
+ const text = token.colorText ?? TABLE_CHROME.text;
128
+ const textSecondary = token.colorTextSecondary ?? TABLE_CHROME.textAux;
129
+ const textMuted = token.colorTextTertiary ?? TABLE_CHROME.textMuted;
130
+ const rowHover = token.colorFillTertiary ?? TABLE_CHROME.rowHover;
131
+ return {
132
+ bodyStyle: {
133
+ bgColor: token.colorBgContainer,
134
+ borderColor: border,
135
+ borderLineWidth,
136
+ color: text,
137
+ fontFamily: token.fontFamily,
138
+ fontSize: 14,
139
+ hover: {
140
+ cellBgColor: rowHover,
141
+ inlineRowBgColor: rowHover,
142
+ inlineColumnBgColor: rowHover
143
+ },
144
+ padding: [
145
+ 0,
146
+ TABLE_CHROME.paddingX,
147
+ 0,
148
+ TABLE_CHROME.paddingX
149
+ ],
150
+ textAlign: "left"
151
+ },
152
+ cellInnerBorder: true,
153
+ columnResize: {
154
+ bgColor: "transparent",
155
+ labelVisible: false,
156
+ lineColor: "transparent",
157
+ lineWidth: 0,
158
+ visibleOnHover: false,
159
+ width: 0
160
+ },
161
+ dragHeaderSplitLine: {
162
+ lineColor: accent,
163
+ lineWidth: 2,
164
+ shadowBlockColor: rgba(accent, .08)
165
+ },
166
+ defaultStyle: {
167
+ borderColor: "transparent",
168
+ borderLineWidth: headerBorderLineWidth,
169
+ fontFamily: token.fontFamily,
170
+ fontSize: 14
171
+ },
172
+ frozenColumnLine: { shadow: {
173
+ width: 8,
174
+ startColor: `rgba(${shadowInk}, ${shadowAlpha})`,
175
+ endColor: `rgba(${shadowInk}, 0)`,
176
+ visible: "scrolling"
177
+ } },
178
+ frameStyle: {
179
+ borderColor: border,
180
+ borderLineWidth: [
181
+ 0,
182
+ 0,
183
+ 1,
184
+ 0
185
+ ],
186
+ cornerRadius: 0
187
+ },
188
+ functionalIconsStyle: {
189
+ collapse_color: textMuted,
190
+ dragReorder_color: textMuted,
191
+ dragReorder_color_opacity: "1",
192
+ dragReorder_size: 16,
193
+ expand_color: textMuted
194
+ },
195
+ groupTitleStyle: {
196
+ bgColor: groupFill,
197
+ borderColor: border,
198
+ borderLineWidth,
199
+ color: textSecondary,
200
+ cursor: "pointer",
201
+ fontFamily: token.fontFamily,
202
+ fontSize: 14,
203
+ fontWeight: 500,
204
+ hover: { cellBgColor: rowHover },
205
+ padding: [
206
+ 0,
207
+ TABLE_CHROME.paddingX,
208
+ 0,
209
+ 8
210
+ ],
211
+ select: {
212
+ cellBgColor: groupFill,
213
+ inlineRowBgColor: groupFill
214
+ },
215
+ textAlign: "left"
216
+ },
217
+ rowHeaderStyle: { hover: {
218
+ cellBgColor: rowHover,
219
+ inlineRowBgColor: rowHover,
220
+ inlineColumnBgColor: rowHover
221
+ } },
222
+ headerStyle: {
223
+ bgColor: token.colorBgContainer,
224
+ borderColor: headerBorderColor,
225
+ borderLineWidth: headerBorderLineWidth,
226
+ color: textSecondary,
227
+ fontFamily: token.fontFamily,
228
+ fontSize: 13,
229
+ fontWeight: 500,
230
+ hover: {
231
+ cellBgColor: rowHover,
232
+ inlineRowBgColor: rowHover,
233
+ inlineColumnBgColor: rowHover
234
+ },
235
+ padding: [
236
+ 0,
237
+ TABLE_CHROME.paddingX,
238
+ 0,
239
+ TABLE_CHROME.paddingX
240
+ ],
241
+ textAlign: "left"
242
+ },
243
+ scrollStyle: {
244
+ barToSide: true,
245
+ ignoreFrozenCols: true,
246
+ hoverOn: false,
247
+ horizontalPadding: [
248
+ 2,
249
+ 0,
250
+ 2,
251
+ 0
252
+ ],
253
+ verticalPadding: [
254
+ 0,
255
+ 2,
256
+ 0,
257
+ 2
258
+ ],
259
+ scrollSliderColor: token.colorTextQuaternary || TABLE_CHROME.starOff,
260
+ scrollSliderCornerRadius: 999,
261
+ visible: "focus",
262
+ width: 8
263
+ },
264
+ selectionStyle: {
265
+ cellBgColor: "transparent",
266
+ cellBorderColor: "transparent",
267
+ cellBorderLineWidth: 0,
268
+ selectionFillMode: "overlay"
269
+ },
270
+ underlayBackgroundColor: token.colorBgContainer
271
+ };
272
+ };
273
+ //#endregion
274
+ export { TABLE_CHROME, createTableTheme, isOpaqueColor, resolveGroupFill, resolveOnAccent, resolveTableCanvasPalette };
275
+
276
+ //# sourceMappingURL=theme.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.mjs","names":[],"sources":["../../../src/Table/canvas/theme.ts"],"sourcesContent":["import type { Theme } from 'antd-style';\nimport { parseToRgb, rgba } from 'polished';\n\nimport { contrastRatio, parseHex, type Rgb } from '../core/tagTone';\nimport type { TableTheme } from '../type';\n\nexport type TableToken = Pick<\n Theme,\n | 'colorBgContainer'\n | 'colorBorderSecondary'\n | 'colorFillQuaternary'\n | 'colorFillSecondary'\n | 'colorFillTertiary'\n | 'colorInfo'\n | 'colorPrimary'\n | 'colorSplit'\n | 'colorSuccess'\n | 'colorText'\n | 'colorTextQuaternary'\n | 'colorTextSecondary'\n | 'colorTextTertiary'\n | 'fontFamily'\n> & { isDarkMode?: boolean };\n/**\n * SuperAd Table visual language, aligned with the Figma Community kit.\n * White header, light bottom hairlines, 44px rows, neutral group rows and a\n * restrained brand-blue accent. Values are fallbacks; createTableTheme still\n * prefers Ant Design / antd-style tokens where they exist.\n */\nexport const TABLE_CHROME = {\n accent: '#1F5EFF',\n // 与 vrender 内置的默认一致:拿不到 antd token 时行为不变(见 DEFAULT_TEXT_FONT_FAMILY)。\n fontFamily:\n \"PingFang SC,Helvetica Neue,Microsoft Yahei,system-ui,'-apple-system',segoe ui,Roboto,Helvetica,Arial,sans-serif\",\n\n border: '#F4F4F5',\n group: '#FAFAFA',\n groupDark: '#161616',\n hover: '#FAFAFA',\n rowHover: '#F5F5F5',\n overlayBorder: '#E4E4E7',\n paddingX: 8,\n pillBg: '#F4F4F5',\n starOff: '#F4F4F5',\n starOn: '#71717A',\n success: '#16A34A',\n text: '#18181B',\n textAux: '#71717A',\n textMuted: '#A1A1AA',\n} as const;\n\nexport interface TableCanvasPalette {\n accent: string;\n checkboxBg: string;\n fontFamily: string;\n isDarkMode: boolean;\n overlayBorder: string;\n pillBg: string;\n rowHover: string;\n starOff: string;\n starOn: string;\n success: string;\n text: string;\n textAux: string;\n textMuted: string;\n}\n\n/**\n * True when a CSS colour paints fully opaque. parseToRgb reports opaque colours without an\n * alpha at all rather than as 1, and throws on anything it cannot parse (which we treat as\n * not opaque, so callers fall back).\n */\nexport const isOpaqueColor = (color: string): boolean => {\n try {\n const parsed = parseToRgb(color);\n const alpha = 'alpha' in parsed ? parsed.alpha : undefined;\n\n return alpha == null || alpha >= 1;\n } catch {\n return false;\n }\n};\n\n/**\n * Group title rows sit above the rows that scroll beneath them, so their fill has to be\n * opaque — a translucent token would let content show through the sticky title. Keep the\n * token when it is opaque and fall back to the built-in neutral when it is not, so a custom\n * opaque colour still themes the group rows.\n */\nexport const resolveGroupFill = (token?: TableToken): string => {\n const fallback = token?.isDarkMode ? TABLE_CHROME.groupDark : TABLE_CHROME.group;\n const fill = token?.colorFillQuaternary;\n\n if (fill == null) return fallback;\n\n return isOpaqueColor(fill) ? fill : fallback;\n};\n\nexport const resolveTableCanvasPalette = (token?: TableToken): TableCanvasPalette => ({\n accent: token?.colorPrimary ?? TABLE_CHROME.accent,\n fontFamily: token?.fontFamily ?? TABLE_CHROME.fontFamily,\n isDarkMode: Boolean(token?.isDarkMode),\n checkboxBg: token?.colorBgContainer ?? '#FFFFFF',\n overlayBorder: token?.colorBorderSecondary ?? TABLE_CHROME.overlayBorder,\n pillBg: token?.colorFillTertiary ?? TABLE_CHROME.pillBg,\n rowHover: token?.colorFillTertiary ?? TABLE_CHROME.rowHover,\n starOff: token?.colorFillTertiary ?? TABLE_CHROME.starOff,\n starOn: token?.colorTextTertiary ?? TABLE_CHROME.starOn,\n success: token?.colorSuccess ?? TABLE_CHROME.success,\n text: token?.colorText ?? TABLE_CHROME.text,\n textAux: token?.colorTextSecondary ?? TABLE_CHROME.textAux,\n textMuted: token?.colorTextTertiary ?? TABLE_CHROME.textMuted,\n});\n\n/** 画在 `accent` 之上的前景色候选:深、浅各一个,取对比度更高的那个。 */\nconst ON_ACCENT_DARK = '#18181B';\nconst ON_ACCENT_LIGHT = '#FFFFFF';\nconst ON_ACCENT_DARK_RGB: Rgb = { b: 27, g: 24, r: 24 };\nconst ON_ACCENT_LIGHT_RGB: Rgb = { b: 255, g: 255, r: 255 };\n\n/**\n * 画在 `accent` 之上的前景色(对勾、半选短横)。\n *\n * **不能写死白色。** `accent` 来自 `token.colorPrimary`,而主色不一定深 —— 暗色下实测是\n * `#eeeeee`,白对勾画上去对比度只有 **1.1:1**,等于看不见(浅色下 `accent` 是 `#222222`,\n * 白对勾才是对的)。按对比度二选一,两种主题都对。\n */\nexport const resolveOnAccent = (accent: string): string => {\n const rgb = parseHex(accent);\n if (!rgb) return ON_ACCENT_LIGHT;\n\n return contrastRatio(rgb, ON_ACCENT_DARK_RGB) >= contrastRatio(rgb, ON_ACCENT_LIGHT_RGB)\n ? ON_ACCENT_DARK\n : ON_ACCENT_LIGHT;\n};\n\n/** SuperAd rows use a top hairline so separators stay visible; vertical borders are opt-in. */\nconst hairline = [1, 1, 0, 0] as [number, number, number, number];\n/** Default SuperAd table: horizontal top row separator only. */\nconst hairlineNoVertical = [1, 0, 0, 0] as [number, number, number, number];\n\nexport interface CreateTableThemeOptions {\n /** Show vertical borders between columns. The row index column never gets one. */\n showVerticalBorder?: boolean;\n}\n\nexport const createTableTheme = (\n token: TableToken,\n { showVerticalBorder = false }: CreateTableThemeOptions = {},\n): TableTheme => {\n const borderLineWidth = showVerticalBorder ? hairline : hairlineNoVertical;\n const headerBorderLineWidth = showVerticalBorder\n ? ([0, 1, 0, 0] as [number, number, number, number])\n : ([0, 0, 0, 0] as [number, number, number, number]);\n const accent = token.colorPrimary ?? TABLE_CHROME.accent;\n // 冻结列阴影的「墨色」。**不能两套主题共用一个近黑** —— 深色压在同为深色的底上等于没有阴影:\n // 实测暗色下已滚动与未滚动的边界像素完全一样(13,13,13),冻结列与内容之间看不出分界。\n // 浅色这一侧的 RGB 与 alpha 保持原值不动,基线才不会被这次修复带偏。\n const shadowInk = token.isDarkMode ? '255, 255, 255' : '24, 24, 27';\n const shadowAlpha = token.isDarkMode ? 0.14 : 0.06;\n const border = token.colorBorderSecondary ?? TABLE_CHROME.border;\n const headerBorderColor = showVerticalBorder ? border : 'transparent';\n const groupFill = resolveGroupFill(token);\n const text = token.colorText ?? TABLE_CHROME.text;\n const textSecondary = token.colorTextSecondary ?? TABLE_CHROME.textAux;\n const textMuted = token.colorTextTertiary ?? TABLE_CHROME.textMuted;\n const rowHover = token.colorFillTertiary ?? TABLE_CHROME.rowHover;\n\n return {\n bodyStyle: {\n bgColor: token.colorBgContainer,\n borderColor: border,\n borderLineWidth,\n color: text,\n fontFamily: token.fontFamily,\n fontSize: 14,\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, TABLE_CHROME.paddingX],\n textAlign: 'left',\n },\n cellInnerBorder: true,\n columnResize: {\n bgColor: 'transparent',\n labelVisible: false,\n lineColor: 'transparent',\n lineWidth: 0,\n visibleOnHover: false,\n width: 0,\n },\n dragHeaderSplitLine: {\n lineColor: accent,\n lineWidth: 2,\n shadowBlockColor: rgba(accent, 0.08),\n },\n defaultStyle: {\n borderColor: 'transparent',\n borderLineWidth: headerBorderLineWidth,\n fontFamily: token.fontFamily,\n fontSize: 14,\n },\n frozenColumnLine: {\n shadow: {\n width: 8,\n // 两端用同一组 RGB:混用会让渐变中途泛灰。\n startColor: `rgba(${shadowInk}, ${shadowAlpha})`,\n endColor: `rgba(${shadowInk}, 0)`,\n visible: 'scrolling',\n },\n },\n frameStyle: {\n borderColor: border,\n borderLineWidth: [0, 0, 1, 0] as [number, number, number, number],\n cornerRadius: 0,\n },\n functionalIconsStyle: {\n collapse_color: textMuted,\n dragReorder_color: textMuted,\n dragReorder_color_opacity: '1',\n dragReorder_size: 16,\n expand_color: textMuted,\n },\n groupTitleStyle: {\n bgColor: groupFill,\n borderColor: border,\n borderLineWidth,\n color: textSecondary,\n cursor: 'pointer',\n fontFamily: token.fontFamily,\n fontSize: 14,\n fontWeight: 500,\n hover: {\n cellBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, 8],\n select: {\n cellBgColor: groupFill,\n inlineRowBgColor: groupFill,\n },\n textAlign: 'left',\n },\n rowHeaderStyle: {\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n },\n headerStyle: {\n bgColor: token.colorBgContainer,\n borderColor: headerBorderColor,\n borderLineWidth: headerBorderLineWidth,\n color: textSecondary,\n fontFamily: token.fontFamily,\n fontSize: 13,\n fontWeight: 500,\n hover: {\n cellBgColor: rowHover,\n inlineRowBgColor: rowHover,\n inlineColumnBgColor: rowHover,\n },\n padding: [0, TABLE_CHROME.paddingX, 0, TABLE_CHROME.paddingX],\n textAlign: 'left',\n },\n scrollStyle: {\n barToSide: true,\n // 默认**从冻结列右边才开始**,不铺满整宽 —— 有冻结列的表(树表、单元格类型)因此和没有\n // 冻结列的表长得不一样,看起来像「缩在里面」。打开这个开关让横条始终从最左铺到最右。\n ignoreFrozenCols: true,\n // **`hoverOn` 不是「hover 才显示」,而是「滚动条浮在内容上、不占位」。** 开着它时\n // `getHorizontalScrollBarSize` 直接返回 0,引擎**不预留滚动条槽** —— 内容一直铺到画布\n // 底边,而横条固定在画布底边,于是它压在底边框上(内容纵向溢出的表都是这个观感:树形\n // 表格、Table 分组、百万数据)。关掉它引擎才会 `height = heightP - hScrollBarWidth` 预留\n // 那 4px,横条落到边框**下方**的槽里,与不滚动的表(autoHeight 自带 8px 槽)一致。\n //\n // 「什么时候出现」由 `visible: 'focus'` 管,与这一项无关。\n hoverOn: false,\n // 滑块与表格留出 2px 缝:`width` 是**滚动条的槽宽**(引擎按它预留空间),滑块的实际\n // 粗细由 padding 从槽里内缩出来 —— 槽 8、上下(横条)/ 左右(竖条)各内缩 2,滑块仍是\n // 4px,贴着表格那侧因此有 2px 空白。槽宽即内容要付出的宽度,比 4px 多让出 4px。\n horizontalPadding: [2, 0, 2, 0],\n verticalPadding: [0, 2, 0, 2],\n scrollSliderColor: token.colorTextQuaternary || TABLE_CHROME.starOff,\n scrollSliderCornerRadius: 999,\n visible: 'focus',\n // 槽宽(不是滑块粗细)。8 = 滑块 4 + 两侧各 2 的缝,见上面的 padding 注释。\n width: 8,\n },\n selectionStyle: {\n // 选中不画任何高亮:不铺色(铺色和 hover 的中性灰撞)、不描边(这套 token 的主色是\n // 近黑,描出来是一圈黑框)。选中只用来驱动编辑与复制,视觉反馈交给 hover。\n cellBgColor: 'transparent',\n cellBorderColor: 'transparent',\n cellBorderLineWidth: 0,\n selectionFillMode: 'overlay',\n },\n underlayBackgroundColor: token.colorBgContainer,\n };\n};\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ import "../type.mjs";
@@ -0,0 +1,240 @@
1
+ import dayjs from "dayjs";
2
+ //#region src/Table/core/cellValue.ts
3
+ /** `YYYY-MM-DD` 开头(含可选的 T/空格 + 时间部分)的输入要做往返校验。 */
4
+ const ISO_DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]|$)/;
5
+ /** 空白串按空值处理:`Number(' ')` 是 0,会让 CSV / 粘贴进来的空白列显示成 `0`。 */
6
+ const isBlank = (value) => value == null || typeof value === "string" && value.trim() === "";
7
+ /**
8
+ * 串自己带了时区标记(`Z` 或 `±HH:MM` / `±HHMM`)。
9
+ *
10
+ * 这类串由 `new Date(str)` 解析成一个**确定瞬间**,根本不存在「本地日历日」这回事 ——
11
+ * 拿本地日期去和串里写的日期做往返校验是范畴错误:同一个瞬间在 UTC+8 下就是第二天,
12
+ * 于是 `16:00:00Z` 之后的一整天全被判成非法日期、单元格显示空白。
13
+ * (dayjs 对 `Z` 结尾的串本来就走原生解析,对带偏移的串则因匹配不上它的正则而同样走原生。)
14
+ */
15
+ const HAS_TIMEZONE = /(?:[Zz]|[+-]\d{2}:?\d{2})$/;
16
+ const toDate = (value) => {
17
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? void 0 : value;
18
+ if (value == null || value === "") return void 0;
19
+ const parsed = dayjs(value);
20
+ if (parsed.isValid() === false) return void 0;
21
+ const trimmed = typeof value === "string" ? value.trim() : void 0;
22
+ if (trimmed != null && ISO_DATE_PREFIX.test(trimmed) && HAS_TIMEZONE.test(trimmed) === false && parsed.format("YYYY-MM-DD") !== trimmed.slice(0, 10)) return;
23
+ return parsed.toDate();
24
+ };
25
+ const formatDateTime = (value, format = "YYYY-MM-DD HH:mm") => {
26
+ const date = toDate(value);
27
+ return date ? dayjs(date).format(format) : "";
28
+ };
29
+ /**
30
+ * 日期范围的显示文案(`dateRange` 列)。值是一条 `[起, 止]` 数组,默认显示**起止时间**。
31
+ * (参考图里甘特条上的「N天 / N个月」是时间条自己的标注,不是表格列的显示。)
32
+ * 历史数据只存了一端时显示那一端的日期。
33
+ */
34
+ const formatDateRange = (value, format = "YYYY-MM-DD") => {
35
+ const [start, end] = toValueList(value);
36
+ const startText = formatDateTime(start, format);
37
+ const endText = end == null ? "" : formatDateTime(end, format);
38
+ if (startText === "") return endText;
39
+ if (endText === "") return startText;
40
+ return `${startText} ~ ${endText}`;
41
+ };
42
+ /**
43
+ * 一段 `[起, 止]` 的时长文案 —— 状态标题后跟的「5天 / 1个月」这类。不足一月按天、
44
+ * 满一月按月(月按 30 天)。算不出(缺任一端)返回空串。
45
+ */
46
+ const formatDuration = (value) => {
47
+ const [start, end] = toValueList(value);
48
+ const startDate = start == null ? void 0 : toDate(start);
49
+ const endDate = end == null ? void 0 : toDate(end);
50
+ if (!startDate || !endDate) return "";
51
+ const days = dayjs(endDate).diff(dayjs(startDate), "day");
52
+ return days >= 30 ? `${Math.floor(days / 30)}个月` : `${days}天`;
53
+ };
54
+ /**
55
+ * 从记录里取日期范围,供「状态标题后跟时长」用。`duration` 是字段名(值本身就是
56
+ * `[起, 止]` 数组),或 `{ from, to }` 两个字段。
57
+ */
58
+ const resolveDurationRange = (record, duration) => {
59
+ if (duration == null || record == null) return void 0;
60
+ const source = record;
61
+ if (typeof duration === "string") return source[duration];
62
+ return [source[duration.from], source[duration.to]];
63
+ };
64
+ const toValueList = (value) => {
65
+ if (value == null || value === "") return [];
66
+ return Array.isArray(value) ? value : [value];
67
+ };
68
+ const isEmptyCellValue = (value) => {
69
+ if (value == null || value === "") return true;
70
+ if (typeof value === "number" || typeof value === "boolean") return false;
71
+ if (value instanceof Date) return Number.isNaN(value.getTime());
72
+ if (Array.isArray(value)) return value.every(isEmptyCellValue);
73
+ if (typeof value === "object") return Object.values(value).every(isEmptyCellValue);
74
+ return false;
75
+ };
76
+ const emptyCellLabel = (title) => title ? `设置 ${title}` : "设置";
77
+ const matchesQuery = (query, ...parts) => {
78
+ const needle = query.trim().toLowerCase();
79
+ if (!needle) return true;
80
+ return parts.some((part) => String(part ?? "").toLowerCase().includes(needle));
81
+ };
82
+ const optionMap = (options = []) => new Map(options.map((item) => [item.value, item]));
83
+ const resolveOption = (value, options = []) => {
84
+ if (value == null || value === "") return {
85
+ label: "",
86
+ value: ""
87
+ };
88
+ const raw = typeof value === "object" && value && "value" in value ? String(value.value) : String(value);
89
+ return optionMap(options).get(raw) ?? {
90
+ label: raw,
91
+ value: raw
92
+ };
93
+ };
94
+ /**
95
+ * 把任意取值收敛成一个**可以画**的 user。
96
+ *
97
+ * `name` 必须是字符串:painter 要按字符逐个量宽(`measureLabelWidth` 用 `for...of`),
98
+ * 数字 / `null` / `undefined` 透传过去就是 `TypeError: label is not iterable` —— 异常从
99
+ * layout 回调抛进引擎渲染循环,**整张表都画不出来**(实时代码复现:canvas 直接没渲染)。
100
+ * 没有 `name` 键的对象同样不能走 `String(value)`:那会在 chip 上画出 "[object Object]"。
101
+ * 这两种形状都收敛成空名字,painter 见到空名字会退回落位提示。
102
+ */
103
+ const resolveUser = (value) => {
104
+ if (typeof value === "string") return { name: value };
105
+ if (value && typeof value === "object") {
106
+ const user = value;
107
+ return {
108
+ avatar: user.avatar,
109
+ name: typeof user.name === "string" ? user.name : ""
110
+ };
111
+ }
112
+ return { name: value == null ? "" : String(value) };
113
+ };
114
+ const isGroupTitleRecord = (record) => Boolean(record && typeof record === "object" && record.vtableMerge);
115
+ const toBoolean = (value) => {
116
+ if (isBlank(value)) return null;
117
+ if (typeof value === "boolean") return value;
118
+ if (Array.isArray(value)) return null;
119
+ if (value != null && typeof value === "object") {
120
+ const checked = value.checked;
121
+ return typeof checked === "boolean" ? checked : null;
122
+ }
123
+ const text = String(value).trim().toLowerCase();
124
+ if (text === "1" || text === "true" || text === "y" || text === "yes" || text === "是") return true;
125
+ if (text === "0" || text === "false" || text === "f" || text === "n" || text === "no" || text === "off" || text === "否") return false;
126
+ if (typeof value === "number") return Number.isFinite(value) ? value !== 0 : null;
127
+ if (text === "t") return true;
128
+ return Boolean(value);
129
+ };
130
+ const toRating = (value) => {
131
+ const numeric = Number(value);
132
+ if (!Number.isFinite(numeric)) return 0;
133
+ return Math.min(5, Math.max(0, Math.round(numeric)));
134
+ };
135
+ /**
136
+ * `-0` 会被 `toLocaleString` 渲染成 `'-0'`,但它既不小于 0 也不是正数 —— 金额于是显示成
137
+ * `¥-0`。`Math.round(-0.2)` 就是 -0,计算列很容易产出它。
138
+ */
139
+ const normalizeZero = (value) => value === 0 ? 0 : value;
140
+ /**
141
+ * 取到**显示精度**再消负零。
142
+ *
143
+ * 只挡字面 `-0` 是不够的:`toLocaleString('en-US')` 默认保留 3 位小数,于是它自己会把
144
+ * `-0.0001` 舍成字符串 `'-0'`、把 `-1e-9` 舍成 `'-0'` —— 计算列(占比、汇率换算)很容易
145
+ * 产出这种值,界面上就出现 `-0 人` / `-¥0`。先按同一精度取整,再判零。
146
+ */
147
+ const toDisplayPrecision = (value) => normalizeZero(Math.round(value * 1e3) / 1e3);
148
+ const formatNumber = (value, unit) => {
149
+ if (isBlank(value)) return "";
150
+ const numeric = Number(value);
151
+ const text = Number.isFinite(numeric) ? toDisplayPrecision(numeric).toLocaleString("en-US") : String(value);
152
+ if (!unit) return text;
153
+ return unit === "%" ? `${text}%` : `${text} ${unit}`;
154
+ };
155
+ const formatCurrency = (value, currency = "¥") => {
156
+ if (isBlank(value)) return "";
157
+ const numeric = Number(value);
158
+ if (!Number.isFinite(numeric)) return String(value);
159
+ const display = toDisplayPrecision(numeric);
160
+ if (display < 0) return `-${currency}${Math.abs(display).toLocaleString("en-US")}`;
161
+ return `${currency}${display.toLocaleString("en-US")}`;
162
+ };
163
+ const formatJson = (value) => {
164
+ if (value == null || value === "") return "";
165
+ if (typeof value === "string") return value;
166
+ try {
167
+ return JSON.stringify(value);
168
+ } catch {
169
+ return String(value);
170
+ }
171
+ };
172
+ const formatLinkLabel = (value) => value.replace(/^https?:\/\/(?:www\.)?/i, "").replace(/^www\./i, "");
173
+ const hrefFor = (type, value) => {
174
+ const text = value == null ? "" : String(value);
175
+ if (type === "email") return `mailto:${text}`;
176
+ if (type === "phone") return `tel:${text}`;
177
+ return text;
178
+ };
179
+ const formatAddress = (value) => {
180
+ const address = parseAddress(value);
181
+ return [
182
+ "street",
183
+ "street2",
184
+ "city",
185
+ "state",
186
+ "country",
187
+ "postcode"
188
+ ].map((key) => address[key]).filter(Boolean).join(", ");
189
+ };
190
+ const parseAddress = (value) => {
191
+ if (value == null || value === "") return {};
192
+ if (typeof value === "string") return { street: value };
193
+ if (typeof value !== "object") return { street: String(value) };
194
+ const source = value;
195
+ const next = {};
196
+ [
197
+ "street",
198
+ "street2",
199
+ "city",
200
+ "state",
201
+ "country",
202
+ "postcode"
203
+ ].forEach((key) => {
204
+ const part = source[key];
205
+ if (part != null && part !== "") next[key] = String(part);
206
+ });
207
+ return next;
208
+ };
209
+ /**
210
+ * 单元格在**画面上**显示成什么 —— 供 a11y 隐藏层与剪贴板桥使用。
211
+ *
212
+ * 画布上的 select / tag / user / array 是 painter 把标签映射**闭包**进去画的,而
213
+ * `getCellValue()` 给的是记录里的原始值:屏幕阅读器因此朗读 `done` 而不是「已完成」,
214
+ * 对象值更是直接读成 `[object Object]`(画布上明明是姓名)。这里按同一套映射还原成
215
+ * 人能读的文本。
216
+ *
217
+ * `markers` 取编译后的列上的 `superadKind` / `superadOptions`(见 `vtable/columns.ts`)。
218
+ */
219
+ const describeCellValue = (value, markers, record) => {
220
+ if (value == null || value === "") return "";
221
+ const kind = markers?.kind;
222
+ if (kind === "select" || kind === "tag") {
223
+ const labels = toValueList(value).map((item) => resolveOption(item, markers?.options).label).filter((label) => label !== "");
224
+ const durationText = formatDuration(resolveDurationRange(record, markers?.duration));
225
+ const suffix = durationText === "" ? "" : ` ${durationText}`;
226
+ return `${labels.join("、")}${suffix}`;
227
+ }
228
+ if (kind === "user") return toValueList(value).map(resolveUser).map((user) => user.name).filter(Boolean).join("、");
229
+ if (kind === "array") return toValueList(value).map(String).join("、");
230
+ if (kind === "boolean" || kind === "checkbox") {
231
+ const boolean = toBoolean(value);
232
+ return boolean == null ? "" : boolean ? "是" : "否";
233
+ }
234
+ if (typeof value === "object") return formatJson(value);
235
+ return String(value);
236
+ };
237
+ //#endregion
238
+ export { describeCellValue, emptyCellLabel, formatAddress, formatCurrency, formatDateRange, formatDateTime, formatDuration, formatJson, formatLinkLabel, formatNumber, hrefFor, isEmptyCellValue, isGroupTitleRecord, matchesQuery, optionMap, parseAddress, resolveDurationRange, resolveOption, resolveUser, toBoolean, toDate, toRating, toValueList };
239
+
240
+ //# sourceMappingURL=cellValue.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cellValue.mjs","names":[],"sources":["../../../src/Table/core/cellValue.ts"],"sourcesContent":["import dayjs from 'dayjs';\n\nimport type { TableAddress, TableSelectOption, TableUser } from '../type';\n\n/** `YYYY-MM-DD` 开头(含可选的 T/空格 + 时间部分)的输入要做往返校验。 */\nconst ISO_DATE_PREFIX = /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ]|$)/;\n\n/** 空白串按空值处理:`Number(' ')` 是 0,会让 CSV / 粘贴进来的空白列显示成 `0`。 */\nconst isBlank = (value: unknown): boolean =>\n value == null || (typeof value === 'string' && value.trim() === '');\n\n/**\n * 串自己带了时区标记(`Z` 或 `±HH:MM` / `±HHMM`)。\n *\n * 这类串由 `new Date(str)` 解析成一个**确定瞬间**,根本不存在「本地日历日」这回事 ——\n * 拿本地日期去和串里写的日期做往返校验是范畴错误:同一个瞬间在 UTC+8 下就是第二天,\n * 于是 `16:00:00Z` 之后的一整天全被判成非法日期、单元格显示空白。\n * (dayjs 对 `Z` 结尾的串本来就走原生解析,对带偏移的串则因匹配不上它的正则而同样走原生。)\n */\nconst HAS_TIMEZONE = /(?:[Zz]|[+-]\\d{2}:?\\d{2})$/;\n\nexport const toDate = (value: unknown): Date | undefined => {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? undefined : value;\n if (value == null || value === '') return undefined;\n const parsed = dayjs(value as any);\n if (parsed.isValid() === false) return undefined;\n\n // dayjs 是宽松解析:'2026-13-45' 会被「滚动」成 2027-02-13,`isValid()` 仍是 true ——\n // 界面上于是显示一个看起来合理、其实完全错误的日子。只对 ISO 形状、且**不带时区标记**的\n // 字符串做往返校验,其它写法(时间戳、Date、'2026/3/4'、带 Z/偏移的串)维持原样。\n const trimmed = typeof value === 'string' ? value.trim() : undefined;\n if (\n trimmed != null &&\n ISO_DATE_PREFIX.test(trimmed) &&\n HAS_TIMEZONE.test(trimmed) === false &&\n parsed.format('YYYY-MM-DD') !== trimmed.slice(0, 10)\n ) {\n return undefined;\n }\n\n return parsed.toDate();\n};\n\nexport const formatDateTime = (value: unknown, format = 'YYYY-MM-DD HH:mm'): string => {\n const date = toDate(value);\n return date ? dayjs(date).format(format) : '';\n};\n\n/**\n * 日期范围的显示文案(`dateRange` 列)。值是一条 `[起, 止]` 数组,默认显示**起止时间**。\n * (参考图里甘特条上的「N天 / N个月」是时间条自己的标注,不是表格列的显示。)\n * 历史数据只存了一端时显示那一端的日期。\n */\nexport const formatDateRange = (value: unknown, format = 'YYYY-MM-DD'): string => {\n const [start, end] = toValueList(value);\n const startText = formatDateTime(start, format);\n const endText = end == null ? '' : formatDateTime(end, format);\n\n // 只显示有值的那端,别把空端格式化成 `~ ` 悬着。\n if (startText === '') return endText;\n if (endText === '') return startText;\n return `${startText} ~ ${endText}`;\n};\n\n/**\n * 一段 `[起, 止]` 的时长文案 —— 状态标题后跟的「5天 / 1个月」这类。不足一月按天、\n * 满一月按月(月按 30 天)。算不出(缺任一端)返回空串。\n */\nexport const formatDuration = (value: unknown): string => {\n const [start, end] = toValueList(value);\n const startDate = start == null ? undefined : toDate(start);\n const endDate = end == null ? undefined : toDate(end);\n if (!startDate || !endDate) return '';\n\n const days = dayjs(endDate).diff(dayjs(startDate), 'day');\n return days >= 30 ? `${Math.floor(days / 30)}个月` : `${days}天`;\n};\n\n/**\n * 从记录里取日期范围,供「状态标题后跟时长」用。`duration` 是字段名(值本身就是\n * `[起, 止]` 数组),或 `{ from, to }` 两个字段。\n */\nexport const resolveDurationRange = (\n record: unknown,\n duration?: string | { from: string; to: string },\n): unknown => {\n if (duration == null || record == null) return undefined;\n const source = record as Record<string, unknown>;\n\n if (typeof duration === 'string') return source[duration];\n return [source[duration.from], source[duration.to]];\n};\n\nexport const toValueList = (value: unknown): unknown[] => {\n if (value == null || value === '') return [];\n return Array.isArray(value) ? value : [value];\n};\n\nexport const isEmptyCellValue = (value: unknown): boolean => {\n if (value == null || value === '') return true;\n if (typeof value === 'number' || typeof value === 'boolean') return false;\n if (value instanceof Date) return Number.isNaN(value.getTime());\n if (Array.isArray(value)) return value.every(isEmptyCellValue);\n if (typeof value === 'object') {\n const values = Object.values(value as Record<string, unknown>);\n return values.every(isEmptyCellValue);\n }\n return false;\n};\n\nexport const emptyCellLabel = (title?: string): string => (title ? `设置 ${title}` : '设置');\n\nexport const matchesQuery = (\n query: string,\n ...parts: Array<string | null | undefined>\n): boolean => {\n const needle = query.trim().toLowerCase();\n if (!needle) return true;\n return parts.some((part) =>\n String(part ?? '')\n .toLowerCase()\n .includes(needle),\n );\n};\n\nexport const optionMap = (options: TableSelectOption[] = []): Map<string, TableSelectOption> =>\n new Map(options.map((item) => [item.value, item]));\n\nexport const resolveOption = (\n value: unknown,\n options: TableSelectOption[] = [],\n): TableSelectOption => {\n if (value == null || value === '') return { label: '', value: '' };\n const raw =\n typeof value === 'object' && value && 'value' in value\n ? String((value as TableSelectOption).value)\n : String(value);\n return optionMap(options).get(raw) ?? { label: raw, value: raw };\n};\n\n/**\n * 把任意取值收敛成一个**可以画**的 user。\n *\n * `name` 必须是字符串:painter 要按字符逐个量宽(`measureLabelWidth` 用 `for...of`),\n * 数字 / `null` / `undefined` 透传过去就是 `TypeError: label is not iterable` —— 异常从\n * layout 回调抛进引擎渲染循环,**整张表都画不出来**(实时代码复现:canvas 直接没渲染)。\n * 没有 `name` 键的对象同样不能走 `String(value)`:那会在 chip 上画出 \"[object Object]\"。\n * 这两种形状都收敛成空名字,painter 见到空名字会退回落位提示。\n */\nexport const resolveUser = (value: unknown): TableUser => {\n if (typeof value === 'string') return { name: value };\n if (value && typeof value === 'object') {\n const user = value as Partial<TableUser>;\n return {\n avatar: user.avatar,\n name: typeof user.name === 'string' ? user.name : '',\n };\n }\n return { name: value == null ? '' : String(value) };\n};\n\nexport const isGroupTitleRecord = (record: unknown): record is { vtableMerge: true } =>\n Boolean(\n record && typeof record === 'object' && (record as { vtableMerge?: boolean }).vtableMerge,\n );\n\nexport const toBoolean = (value: unknown): boolean | null => {\n // 空白串按空值处理,与 `isBlank`(number / currency 那一侧)同口径 —— 少了 trim,\n // `' '` 会落到兜底 `Boolean(value)` 变成「是」,复选框直接画成已勾选。\n if (isBlank(value)) return null;\n if (typeof value === 'boolean') return value;\n // 数组没有布尔语义。原样走兜底的话空数组 `[]` 也是真。\n if (Array.isArray(value)) return null;\n if (value != null && typeof value === 'object') {\n // VTable 的 checkbox 单元格把状态存在记录的 `{checked}` 上;其余对象没有布尔语义。\n const checked = (value as { checked?: unknown }).checked;\n return typeof checked === 'boolean' ? checked : null;\n }\n\n // 比较必须忽略大小写与首尾空格:'FALSE' / 'False' 落到最后的 `Boolean(value)` 会得到\n // true —— 后端把布尔写成大写时整个含义反过来,而且没有任何报错。否定写法要列全('n' /\n // 'no' / 'off' 同样会落到那个兜底,把「否」画成绿色的对勾)。\n const text = String(value).trim().toLowerCase();\n if (text === '1' || text === 'true' || text === 'y' || text === 'yes' || text === '是')\n return true;\n if (\n text === '0' ||\n text === 'false' ||\n // `'f'` 是 Postgres / MySQL 的布尔输出写法(`'t'` / `'f'`),漏在集合外就整个反过来。\n text === 'f' ||\n text === 'n' ||\n text === 'no' ||\n text === 'off' ||\n text === '否'\n ) {\n return false;\n }\n\n // 数字保持原语义:0 是假,其余是真;`NaN` / `Infinity` 不是布尔。\n if (typeof value === 'number') return Number.isFinite(value) ? value !== 0 : null;\n if (text === 't') return true;\n return Boolean(value);\n};\n\nexport const toRating = (value: unknown): number => {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return 0;\n return Math.min(5, Math.max(0, Math.round(numeric)));\n};\n\n/**\n * `-0` 会被 `toLocaleString` 渲染成 `'-0'`,但它既不小于 0 也不是正数 —— 金额于是显示成\n * `¥-0`。`Math.round(-0.2)` 就是 -0,计算列很容易产出它。\n */\nconst normalizeZero = (value: number): number => (value === 0 ? 0 : value);\n\n/**\n * 取到**显示精度**再消负零。\n *\n * 只挡字面 `-0` 是不够的:`toLocaleString('en-US')` 默认保留 3 位小数,于是它自己会把\n * `-0.0001` 舍成字符串 `'-0'`、把 `-1e-9` 舍成 `'-0'` —— 计算列(占比、汇率换算)很容易\n * 产出这种值,界面上就出现 `-0 人` / `-¥0`。先按同一精度取整,再判零。\n */\nconst toDisplayPrecision = (value: number): number =>\n normalizeZero(Math.round(value * 1000) / 1000);\n\nexport const formatNumber = (value: unknown, unit?: string): string => {\n if (isBlank(value)) return '';\n const numeric = Number(value);\n const text = Number.isFinite(numeric)\n ? toDisplayPrecision(numeric).toLocaleString('en-US')\n : String(value);\n if (!unit) return text;\n return unit === '%' ? `${text}%` : `${text} ${unit}`;\n};\n\nexport const formatCurrency = (value: unknown, currency = '¥'): string => {\n if (isBlank(value)) return '';\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n const display = toDisplayPrecision(numeric);\n // 负号要在货币符之前:`¥-5` 不是中文里写金额的方式。符号取自**显示值** —— 舍入后是 0 的\n // 小负数不该带负号(否则就是 `-¥0`)。\n if (display < 0) return `-${currency}${Math.abs(display).toLocaleString('en-US')}`;\n return `${currency}${display.toLocaleString('en-US')}`;\n};\n\nexport const formatJson = (value: unknown): string => {\n if (value == null || value === '') return '';\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n};\n\nexport const formatLinkLabel = (value: string): string =>\n value.replace(/^https?:\\/\\/(?:www\\.)?/i, '').replace(/^www\\./i, '');\n\n/** Link flavours a cell can render as. Owned here because it is a value concept, not an engine one. */\nexport type LinkKind = 'email' | 'link' | 'phone';\n\nexport const hrefFor = (type: LinkKind, value: unknown): string => {\n const text = value == null ? '' : String(value);\n if (type === 'email') return `mailto:${text}`;\n if (type === 'phone') return `tel:${text}`;\n return text;\n};\n\nexport const formatAddress = (value: unknown): string => {\n const address = parseAddress(value);\n return (['street', 'street2', 'city', 'state', 'country', 'postcode'] as const)\n .map((key) => address[key])\n .filter(Boolean)\n .join(', ');\n};\n\nexport const parseAddress = (value: unknown): TableAddress => {\n if (value == null || value === '') return {};\n if (typeof value === 'string') return { street: value };\n if (typeof value !== 'object') return { street: String(value) };\n const source = value as Record<string, unknown>;\n const next: TableAddress = {};\n (['street', 'street2', 'city', 'state', 'country', 'postcode'] as const).forEach((key) => {\n const part = source[key];\n if (part != null && part !== '') next[key] = String(part);\n });\n return next;\n};\n\n/**\n * 单元格在**画面上**显示成什么 —— 供 a11y 隐藏层与剪贴板桥使用。\n *\n * 画布上的 select / tag / user / array 是 painter 把标签映射**闭包**进去画的,而\n * `getCellValue()` 给的是记录里的原始值:屏幕阅读器因此朗读 `done` 而不是「已完成」,\n * 对象值更是直接读成 `[object Object]`(画布上明明是姓名)。这里按同一套映射还原成\n * 人能读的文本。\n *\n * `markers` 取编译后的列上的 `superadKind` / `superadOptions`(见 `vtable/columns.ts`)。\n */\nexport const describeCellValue = (\n value: unknown,\n markers?: {\n duration?: string | { from: string; to: string };\n kind?: string;\n options?: TableSelectOption[];\n },\n record?: unknown,\n): string => {\n if (value == null || value === '') return '';\n const kind = markers?.kind;\n\n if (kind === 'select' || kind === 'tag') {\n const labels = toValueList(value)\n .map((item) => resolveOption(item, markers?.options).label)\n .filter((label) => label !== '');\n // 画布上时长标注画在最后一枚 chip 后面 —— 读屏与剪贴板也带上,两边口径一致。\n const durationText = formatDuration(resolveDurationRange(record, markers?.duration));\n const suffix = durationText === '' ? '' : ` ${durationText}`;\n\n return `${labels.join('、')}${suffix}`;\n }\n if (kind === 'user') {\n return toValueList(value)\n .map(resolveUser)\n .map((user) => user.name)\n .filter(Boolean)\n .join('、');\n }\n if (kind === 'array') return toValueList(value).map(String).join('、');\n if (kind === 'boolean' || kind === 'checkbox') {\n const boolean = toBoolean(value);\n return boolean == null ? '' : boolean ? '是' : '否';\n }\n // 兜底:对象值别读成 \"[object Object]\"。\n if (typeof value === 'object') return formatJson(value);\n return String(value);\n};\n"],"mappings":"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"}