torch-glare 2.4.0 → 2.4.2

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 (318) hide show
  1. package/README.md +8 -0
  2. package/apps/lib/components/ActionButton.tsx +7 -12
  3. package/apps/lib/components/ActionsGroup.tsx +4 -15
  4. package/apps/lib/components/AlertDialog.tsx +173 -191
  5. package/apps/lib/components/Avatar.tsx +13 -16
  6. package/apps/lib/components/Badge.tsx +1 -3
  7. package/apps/lib/components/BadgeField.tsx +76 -71
  8. package/apps/lib/components/Breadcrumb.tsx +49 -85
  9. package/apps/lib/components/Button.tsx +21 -20
  10. package/apps/lib/components/ButtonGroup.tsx +31 -41
  11. package/apps/lib/components/Calendar.tsx +51 -29
  12. package/apps/lib/components/Card.tsx +40 -24
  13. package/apps/lib/components/Checkbox.tsx +9 -16
  14. package/apps/lib/components/ColorPicker.tsx +441 -0
  15. package/apps/lib/components/ConclusionHeader.tsx +148 -0
  16. package/apps/lib/components/ContextMenu.tsx +97 -96
  17. package/apps/lib/components/CountBadge.tsx +4 -6
  18. package/apps/lib/components/DataTable.tsx +196 -216
  19. package/apps/lib/components/DataViews/DataViewRadio.tsx +1 -6
  20. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +13 -47
  21. package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
  22. package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
  23. package/apps/lib/components/DataViews/FilterPanel.tsx +155 -149
  24. package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
  25. package/apps/lib/components/DataViews/InboxView.tsx +22 -68
  26. package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
  27. package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
  28. package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
  29. package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
  30. package/apps/lib/components/DataViews/TableView.tsx +8 -28
  31. package/apps/lib/components/DataViews/TreeView.tsx +12 -40
  32. package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
  33. package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
  34. package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +30 -23
  35. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
  36. package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
  37. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
  38. package/apps/lib/components/DataViews/index.ts +26 -26
  39. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
  40. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
  41. package/apps/lib/components/DataViews/types.ts +104 -115
  42. package/apps/lib/components/DatePicker.tsx +200 -196
  43. package/apps/lib/components/Dialog.tsx +92 -105
  44. package/apps/lib/components/Divider.tsx +21 -24
  45. package/apps/lib/components/Drawer.tsx +124 -142
  46. package/apps/lib/components/DropdownMenu.tsx +131 -171
  47. package/apps/lib/components/FieldHint.tsx +4 -11
  48. package/apps/lib/components/Form.tsx +146 -155
  49. package/apps/lib/components/FormBuilder/DisplayField.tsx +40 -0
  50. package/apps/lib/components/FormBuilder/context.ts +71 -0
  51. package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +65 -0
  52. package/apps/lib/components/FormBuilder/fields/ColorField.tsx +76 -0
  53. package/apps/lib/components/FormBuilder/fields/CustomField.tsx +16 -0
  54. package/apps/lib/components/FormBuilder/fields/DateField.tsx +34 -0
  55. package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +79 -0
  56. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +174 -0
  57. package/apps/lib/components/FormBuilder/fields/FileField.tsx +40 -0
  58. package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +139 -0
  59. package/apps/lib/components/FormBuilder/fields/OtpField.tsx +32 -0
  60. package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +82 -0
  61. package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +37 -0
  62. package/apps/lib/components/FormBuilder/fields/SelectField.tsx +101 -0
  63. package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +175 -0
  64. package/apps/lib/components/FormBuilder/fields/SliderField.tsx +72 -0
  65. package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +42 -0
  66. package/apps/lib/components/FormBuilder/fields/TableField.tsx +312 -0
  67. package/apps/lib/components/FormBuilder/fields/TextField.tsx +222 -0
  68. package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +49 -0
  69. package/apps/lib/components/FormBuilder/fields/countries.ts +303 -0
  70. package/apps/lib/components/FormBuilder/fields/index.ts +25 -0
  71. package/apps/lib/components/FormBuilder/form-builder.tsx +292 -0
  72. package/apps/lib/components/FormBuilder/header.tsx +105 -0
  73. package/apps/lib/components/FormBuilder/index.ts +37 -0
  74. package/apps/lib/components/FormBuilder/numberFormat.ts +16 -0
  75. package/apps/lib/components/FormBuilder/stepper.tsx +217 -0
  76. package/apps/lib/components/FormBuilder/submit.tsx +41 -0
  77. package/apps/lib/components/FormBuilder/types.ts +264 -0
  78. package/apps/lib/components/FormBuilder/viewFormat.tsx +137 -0
  79. package/apps/lib/components/FormRenderer/FormDrawer.tsx +121 -0
  80. package/apps/lib/components/FormRenderer/form-renderer.tsx +113 -0
  81. package/apps/lib/components/FormRenderer/index.ts +9 -0
  82. package/apps/lib/components/FormRenderer/types.ts +79 -0
  83. package/apps/lib/components/FormStepper.tsx +9 -39
  84. package/apps/lib/components/FormSummary.tsx +282 -0
  85. package/apps/lib/components/HeaderBar.tsx +1 -9
  86. package/apps/lib/components/ImageAttachment.tsx +106 -93
  87. package/apps/lib/components/InnerLabelField.tsx +20 -16
  88. package/apps/lib/components/Input.tsx +171 -172
  89. package/apps/lib/components/InputField.tsx +44 -45
  90. package/apps/lib/components/InputOTP.tsx +68 -69
  91. package/apps/lib/components/Label.tsx +61 -58
  92. package/apps/lib/components/LabelField.tsx +4 -5
  93. package/apps/lib/components/LabeledCheckBox.tsx +8 -12
  94. package/apps/lib/components/LabeledRadio.tsx +7 -5
  95. package/apps/lib/components/LinkButton.tsx +2 -9
  96. package/apps/lib/components/LoginButton.tsx +4 -9
  97. package/apps/lib/components/PasswordLevel.tsx +5 -5
  98. package/apps/lib/components/Popover.tsx +15 -33
  99. package/apps/lib/components/ProfileMenu.tsx +67 -72
  100. package/apps/lib/components/Radio.tsx +1 -5
  101. package/apps/lib/components/RadioCard.tsx +11 -28
  102. package/apps/lib/components/ScrollArea.tsx +45 -43
  103. package/apps/lib/components/SearchField.tsx +49 -39
  104. package/apps/lib/components/SearchableSelect.tsx +16 -16
  105. package/apps/lib/components/SearchableTable.tsx +108 -118
  106. package/apps/lib/components/SearchableTree.tsx +469 -0
  107. package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
  108. package/apps/lib/components/SectionBlock.tsx +19 -23
  109. package/apps/lib/components/Select.tsx +80 -186
  110. package/apps/lib/components/SimpleSelect.tsx +196 -187
  111. package/apps/lib/components/Skeleton.tsx +9 -12
  112. package/apps/lib/components/SlideDatePicker.tsx +215 -166
  113. package/apps/lib/components/SpinLoading.tsx +33 -49
  114. package/apps/lib/components/Stepper.tsx +13 -56
  115. package/apps/lib/components/Switch.tsx +8 -10
  116. package/apps/lib/components/TabFormItem.tsx +3 -12
  117. package/apps/lib/components/TabSwitch.tsx +7 -34
  118. package/apps/lib/components/Table.tsx +60 -75
  119. package/apps/lib/components/{ChartBlockTool.ts → TextEditor/ChartBlockTool.ts} +17 -52
  120. package/apps/lib/components/TextEditor/RichTextField.tsx +46 -0
  121. package/apps/lib/components/{TableDnDWrapper.ts → TextEditor/TableDnDWrapper.ts} +15 -48
  122. package/apps/lib/components/{TextEditor.tsx → TextEditor/TextEditor.tsx} +156 -113
  123. package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +429 -0
  124. package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +70 -0
  125. package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +50 -0
  126. package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +48 -0
  127. package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +98 -0
  128. package/apps/lib/{types → components/TextEditor}/editorjs.d.ts +19 -0
  129. package/apps/lib/components/TextEditor/index.ts +7 -0
  130. package/apps/lib/{utils → components/TextEditor}/markdownParser.ts +2 -7
  131. package/apps/lib/components/Textarea.tsx +9 -13
  132. package/apps/lib/components/Timeline.tsx +21 -41
  133. package/apps/lib/components/Toast.tsx +83 -83
  134. package/apps/lib/components/Toggle.tsx +112 -114
  135. package/apps/lib/components/ToggleButton.tsx +1 -1
  136. package/apps/lib/components/Tooltip.tsx +24 -19
  137. package/apps/lib/components/TransparentLabel.tsx +61 -62
  138. package/apps/lib/components/TreeDropDown.tsx +86 -52
  139. package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
  140. package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
  141. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
  142. package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
  143. package/apps/lib/components/TreeFolder/icons.tsx +14 -17
  144. package/apps/lib/components/TreeFolder/index.ts +15 -8
  145. package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
  146. package/apps/lib/components/TreeFolder/types.ts +32 -32
  147. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
  148. package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
  149. package/apps/lib/hooks/useClickOutside.ts +21 -20
  150. package/apps/lib/hooks/useDataViewsState.ts +86 -80
  151. package/apps/lib/hooks/useIsMobile.ts +12 -12
  152. package/apps/lib/hooks/useResize.ts +54 -65
  153. package/apps/lib/hooks/useTagSelection.ts +201 -201
  154. package/apps/lib/layouts/CNLayout.tsx +181 -140
  155. package/apps/lib/layouts/DataViewCard.tsx +4 -18
  156. package/apps/lib/layouts/FieldSection.tsx +2 -6
  157. package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
  158. package/apps/lib/providers/ThemeProvider.tsx +8 -23
  159. package/apps/lib/registry.json +997 -0
  160. package/apps/lib/tsconfig.json +9 -0
  161. package/apps/lib/tsconfig.tsbuildinfo +1 -0
  162. package/apps/lib/utils/cn.ts +1 -1
  163. package/apps/lib/utils/color.ts +175 -0
  164. package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
  165. package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
  166. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
  167. package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
  168. package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
  169. package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
  170. package/apps/lib/utils/dateFormat.ts +64 -54
  171. package/apps/lib/utils/resize.ts +21 -25
  172. package/apps/lib/utils/types.ts +13 -13
  173. package/dist/bin/index.js +8 -2
  174. package/dist/bin/index.js.map +1 -1
  175. package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
  176. package/dist/src/shared/getAvailableFiles.js +6 -1
  177. package/dist/src/shared/getAvailableFiles.js.map +1 -1
  178. package/dist/src/shared/loadRegistry.d.ts +7 -0
  179. package/dist/src/shared/loadRegistry.d.ts.map +1 -0
  180. package/dist/src/shared/loadRegistry.js +31 -0
  181. package/dist/src/shared/loadRegistry.js.map +1 -0
  182. package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
  183. package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
  184. package/dist/src/shared/resolveInstallPlan.js +39 -0
  185. package/dist/src/shared/resolveInstallPlan.js.map +1 -0
  186. package/dist/src/types/main.d.ts +24 -0
  187. package/dist/src/types/main.d.ts.map +1 -1
  188. package/docs/README.md +10 -2
  189. package/docs/components/action-button.md +30 -24
  190. package/docs/components/actions-group.md +26 -20
  191. package/docs/components/alert-dialog.md +45 -45
  192. package/docs/components/avatar.md +6 -2
  193. package/docs/components/badge-field.md +6 -2
  194. package/docs/components/badge.md +7 -1
  195. package/docs/components/breadcrumb.md +15 -40
  196. package/docs/components/button-group.md +13 -10
  197. package/docs/components/button.md +58 -33
  198. package/docs/components/calendar.md +26 -19
  199. package/docs/components/card.md +23 -18
  200. package/docs/components/chart-block-tool.md +129 -0
  201. package/docs/components/checkbox.md +16 -8
  202. package/docs/components/cn-layout.md +7 -1
  203. package/docs/components/color-picker.md +101 -0
  204. package/docs/components/conclusion-header.md +80 -0
  205. package/docs/components/context-menu.md +18 -25
  206. package/docs/components/count-badge.md +7 -1
  207. package/docs/components/data-table.md +45 -33
  208. package/docs/components/data-views-config-panel.md +15 -11
  209. package/docs/components/data-views-layout.md +13 -16
  210. package/docs/components/date-picker.md +30 -23
  211. package/docs/components/dialog.md +43 -42
  212. package/docs/components/divider.md +10 -4
  213. package/docs/components/drawer.md +161 -135
  214. package/docs/components/dropdown-menu.md +25 -34
  215. package/docs/components/field-hint.md +37 -25
  216. package/docs/components/field-section.md +9 -3
  217. package/docs/components/form-builder.md +270 -0
  218. package/docs/components/form-renderer.md +228 -0
  219. package/docs/components/form-stepper.md +6 -0
  220. package/docs/components/form-summary.md +123 -0
  221. package/docs/components/form.md +17 -20
  222. package/docs/components/header-bar.md +6 -0
  223. package/docs/components/image-attachment.md +43 -45
  224. package/docs/components/inbox-view.md +16 -5
  225. package/docs/components/inner-label-field.md +34 -24
  226. package/docs/components/input-field.md +18 -10
  227. package/docs/components/input-otp.md +14 -11
  228. package/docs/components/input.md +22 -16
  229. package/docs/components/kanban-view.md +15 -5
  230. package/docs/components/label-field.md +32 -22
  231. package/docs/components/label.md +43 -32
  232. package/docs/components/labeled-check-box.md +16 -8
  233. package/docs/components/labeled-radio.md +18 -11
  234. package/docs/components/link-button.md +7 -1
  235. package/docs/components/login-button.md +8 -2
  236. package/docs/components/password-level.md +37 -24
  237. package/docs/components/popover.md +26 -23
  238. package/docs/components/profile-menu.md +38 -27
  239. package/docs/components/radio-card.md +16 -9
  240. package/docs/components/radio.md +16 -8
  241. package/docs/components/scroll-area.md +6 -2
  242. package/docs/components/search-field.md +14 -6
  243. package/docs/components/searchable-select.md +19 -14
  244. package/docs/components/searchable-table.md +18 -13
  245. package/docs/components/searchable-tree-dialog.md +190 -0
  246. package/docs/components/searchable-tree.md +200 -0
  247. package/docs/components/section-block.md +12 -4
  248. package/docs/components/select.md +23 -18
  249. package/docs/components/simple-select.md +14 -10
  250. package/docs/components/skeleton.md +43 -33
  251. package/docs/components/slide-date-picker.md +37 -29
  252. package/docs/components/spin-loading.md +32 -23
  253. package/docs/components/stepper.md +6 -0
  254. package/docs/components/switch.md +14 -6
  255. package/docs/components/tab-form-item.md +7 -1
  256. package/docs/components/tab-switch.md +13 -6
  257. package/docs/components/table-dnd-wrapper.md +107 -0
  258. package/docs/components/table-view.md +15 -5
  259. package/docs/components/table.md +28 -28
  260. package/docs/components/text-editor.md +37 -13
  261. package/docs/components/textarea.md +15 -7
  262. package/docs/components/timeline.md +6 -0
  263. package/docs/components/toast.md +35 -26
  264. package/docs/components/toggle-button.md +24 -21
  265. package/docs/components/toggle.md +23 -15
  266. package/docs/components/tooltip.md +42 -30
  267. package/docs/components/transparent-label.md +29 -20
  268. package/docs/components/tree-drop-down.md +31 -23
  269. package/docs/components/tree-sub-layout.md +6 -2
  270. package/docs/components/tree-view.md +16 -5
  271. package/docs/explanation/architecture.md +54 -0
  272. package/docs/explanation/design-system.md +52 -0
  273. package/docs/how-to/data-views-from-backend-response.md +6 -4
  274. package/docs/how-to/form-and-list-recipes.md +26 -19
  275. package/docs/how-to/forms-with-form-builder.md +408 -0
  276. package/docs/how-to/guides.md +177 -197
  277. package/docs/migration/changelog.md +38 -0
  278. package/docs/reference/cli.md +87 -0
  279. package/docs/reference/components.md +11 -12
  280. package/docs/reference/hooks.md +8 -8
  281. package/docs/reference/providers.md +22 -22
  282. package/docs/reference/tailwind-plugins.md +2 -2
  283. package/docs/reference/theme.md +85 -0
  284. package/docs/reference/types.md +4 -4
  285. package/docs/reference/utilities.md +19 -26
  286. package/docs/tutorials/building-first-form.md +170 -172
  287. package/docs/tutorials/component-composition.md +34 -50
  288. package/docs/tutorials/getting-started.md +2 -2
  289. package/docs/tutorials/theming-basics.md +13 -10
  290. package/package.json +31 -3
  291. package/dist/src/commands/block.d.ts +0 -5
  292. package/dist/src/commands/block.d.ts.map +0 -1
  293. package/dist/src/commands/block.js +0 -255
  294. package/dist/src/commands/block.js.map +0 -1
  295. package/dist/src/shared/configureFonts.d.ts +0 -6
  296. package/dist/src/shared/configureFonts.d.ts.map +0 -1
  297. package/dist/src/shared/configureFonts.js +0 -106
  298. package/dist/src/shared/configureFonts.js.map +0 -1
  299. package/dist/src/shared/configureGlobalCss.d.ts +0 -6
  300. package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
  301. package/dist/src/shared/configureGlobalCss.js +0 -154
  302. package/dist/src/shared/configureGlobalCss.js.map +0 -1
  303. package/dist/src/shared/configureTailwind.d.ts +0 -7
  304. package/dist/src/shared/configureTailwind.d.ts.map +0 -1
  305. package/dist/src/shared/configureTailwind.js +0 -163
  306. package/dist/src/shared/configureTailwind.js.map +0 -1
  307. package/dist/src/shared/detectFramework.d.ts +0 -23
  308. package/dist/src/shared/detectFramework.d.ts.map +0 -1
  309. package/dist/src/shared/detectFramework.js +0 -119
  310. package/dist/src/shared/detectFramework.js.map +0 -1
  311. package/dist/src/shared/installBaseUtils.d.ts +0 -5
  312. package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
  313. package/dist/src/shared/installBaseUtils.js +0 -79
  314. package/dist/src/shared/installBaseUtils.js.map +0 -1
  315. package/dist/src/shared/resolveAliases.d.ts +0 -24
  316. package/dist/src/shared/resolveAliases.d.ts.map +0 -1
  317. package/dist/src/shared/resolveAliases.js +0 -98
  318. package/dist/src/shared/resolveAliases.js.map +0 -1
@@ -2,5 +2,5 @@ import { clsx, type ClassValue } from "clsx";
2
2
  import { twMerge } from "tailwind-merge";
3
3
 
4
4
  export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
5
+ return twMerge(clsx(inputs));
6
6
  }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Pure color-math helpers for the `ColorPicker` component (and anything else that
3
+ * needs to move between hex, RGB, HSV and HSL). No dependencies, no DOM — safe to
4
+ * unit-test and to import on the server.
5
+ *
6
+ * Conventions:
7
+ * - `RGBA` channels are 0–255 (`r`,`g`,`b`) and 0–1 (`a`).
8
+ * - `HSV`/`HSL` use `h` 0–360, everything else 0–100.
9
+ * - Hex output is uppercase, `#rrggbb` when fully opaque and `#rrggbbaa` otherwise.
10
+ */
11
+
12
+ export interface RGBA {
13
+ r: number;
14
+ g: number;
15
+ b: number;
16
+ a: number;
17
+ }
18
+
19
+ export interface HSV {
20
+ h: number;
21
+ s: number;
22
+ v: number;
23
+ }
24
+
25
+ export interface HSL {
26
+ h: number;
27
+ s: number;
28
+ l: number;
29
+ }
30
+
31
+ /** Clamp `n` into the inclusive `[min, max]` range. */
32
+ export function clamp(n: number, min: number, max: number): number {
33
+ return n < min ? min : n > max ? max : n;
34
+ }
35
+
36
+ /** Round to `digits` decimal places (default 0). */
37
+ export function round(n: number, digits = 0): number {
38
+ const f = 10 ** digits;
39
+ return Math.round(n * f) / f;
40
+ }
41
+
42
+ const toHex2 = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0");
43
+
44
+ /**
45
+ * Parse a hex color into `RGBA`. Accepts `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`
46
+ * with or without the leading `#`. Returns `null` for anything unparseable.
47
+ */
48
+ export function parseHex(input: string): RGBA | null {
49
+ if (typeof input !== "string") return null;
50
+ let hex = input.trim().replace(/^#/, "");
51
+
52
+ // Expand shorthand (#rgb / #rgba → #rrggbb(aa)).
53
+ if (hex.length === 3 || hex.length === 4) {
54
+ hex = hex
55
+ .split("")
56
+ .map((c) => c + c)
57
+ .join("");
58
+ }
59
+
60
+ if (hex.length !== 6 && hex.length !== 8) return null;
61
+ if (!/^[0-9a-fA-F]+$/.test(hex)) return null;
62
+
63
+ const r = parseInt(hex.slice(0, 2), 16);
64
+ const g = parseInt(hex.slice(2, 4), 16);
65
+ const b = parseInt(hex.slice(4, 6), 16);
66
+ const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1;
67
+ return { r, g, b, a: round(a, 2) };
68
+ }
69
+
70
+ /** Format `RGBA` as an uppercase hex string — `#rrggbb` if opaque, else `#rrggbbaa`. */
71
+ export function formatHex({ r, g, b, a }: RGBA): string {
72
+ const base = `#${toHex2(r)}${toHex2(g)}${toHex2(b)}`.toUpperCase();
73
+ if (a >= 1) return base;
74
+ return `${base}${toHex2(clamp(a, 0, 1) * 255).toUpperCase()}`;
75
+ }
76
+
77
+ /** RGB (0–255) → HSV (h 0–360, s/v 0–100). Alpha is carried through unchanged elsewhere. */
78
+ export function rgbToHsv({ r, g, b }: { r: number; g: number; b: number }): HSV {
79
+ const rn = r / 255;
80
+ const gn = g / 255;
81
+ const bn = b / 255;
82
+ const max = Math.max(rn, gn, bn);
83
+ const min = Math.min(rn, gn, bn);
84
+ const d = max - min;
85
+
86
+ let h = 0;
87
+ if (d !== 0) {
88
+ if (max === rn) h = ((gn - bn) / d) % 6;
89
+ else if (max === gn) h = (bn - rn) / d + 2;
90
+ else h = (rn - gn) / d + 4;
91
+ h *= 60;
92
+ if (h < 0) h += 360;
93
+ }
94
+ const s = max === 0 ? 0 : d / max;
95
+ // Keep 2 decimals: HSV is the picker's internal state, so rounding to whole degrees/percents
96
+ // here would visibly shift a color that was only ever passed in (e.g. #10B981 → #11BA82).
97
+ return { h: round(h, 2), s: round(s * 100, 2), v: round(max * 100, 2) };
98
+ }
99
+
100
+ /** HSV (h 0–360, s/v 0–100) → RGB (0–255). */
101
+ export function hsvToRgb({ h, s, v }: HSV): { r: number; g: number; b: number } {
102
+ const hn = ((h % 360) + 360) % 360;
103
+ const sn = clamp(s, 0, 100) / 100;
104
+ const vn = clamp(v, 0, 100) / 100;
105
+
106
+ const c = vn * sn;
107
+ const x = c * (1 - Math.abs(((hn / 60) % 2) - 1));
108
+ const m = vn - c;
109
+
110
+ let rp = 0;
111
+ let gp = 0;
112
+ let bp = 0;
113
+ if (hn < 60) [rp, gp, bp] = [c, x, 0];
114
+ else if (hn < 120) [rp, gp, bp] = [x, c, 0];
115
+ else if (hn < 180) [rp, gp, bp] = [0, c, x];
116
+ else if (hn < 240) [rp, gp, bp] = [0, x, c];
117
+ else if (hn < 300) [rp, gp, bp] = [x, 0, c];
118
+ else [rp, gp, bp] = [c, 0, x];
119
+
120
+ return {
121
+ r: round((rp + m) * 255),
122
+ g: round((gp + m) * 255),
123
+ b: round((bp + m) * 255),
124
+ };
125
+ }
126
+
127
+ /** RGB (0–255) → HSL (h 0–360, s/l 0–100). */
128
+ export function rgbToHsl({ r, g, b }: { r: number; g: number; b: number }): HSL {
129
+ const rn = r / 255;
130
+ const gn = g / 255;
131
+ const bn = b / 255;
132
+ const max = Math.max(rn, gn, bn);
133
+ const min = Math.min(rn, gn, bn);
134
+ const d = max - min;
135
+ const l = (max + min) / 2;
136
+
137
+ let h = 0;
138
+ let s = 0;
139
+ if (d !== 0) {
140
+ s = d / (1 - Math.abs(2 * l - 1));
141
+ if (max === rn) h = ((gn - bn) / d) % 6;
142
+ else if (max === gn) h = (bn - rn) / d + 2;
143
+ else h = (rn - gn) / d + 4;
144
+ h *= 60;
145
+ if (h < 0) h += 360;
146
+ }
147
+ return { h: round(h), s: round(s * 100), l: round(l * 100) };
148
+ }
149
+
150
+ /** HSL (h 0–360, s/l 0–100) → RGB (0–255). */
151
+ export function hslToRgb({ h, s, l }: HSL): { r: number; g: number; b: number } {
152
+ const hn = ((h % 360) + 360) % 360;
153
+ const sn = clamp(s, 0, 100) / 100;
154
+ const ln = clamp(l, 0, 100) / 100;
155
+
156
+ const c = (1 - Math.abs(2 * ln - 1)) * sn;
157
+ const x = c * (1 - Math.abs(((hn / 60) % 2) - 1));
158
+ const m = ln - c / 2;
159
+
160
+ let rp = 0;
161
+ let gp = 0;
162
+ let bp = 0;
163
+ if (hn < 60) [rp, gp, bp] = [c, x, 0];
164
+ else if (hn < 120) [rp, gp, bp] = [x, c, 0];
165
+ else if (hn < 180) [rp, gp, bp] = [0, c, x];
166
+ else if (hn < 240) [rp, gp, bp] = [0, x, c];
167
+ else if (hn < 300) [rp, gp, bp] = [x, 0, c];
168
+ else [rp, gp, bp] = [c, 0, x];
169
+
170
+ return {
171
+ r: round((rp + m) * 255),
172
+ g: round((gp + m) * 255),
173
+ b: round((bp + m) * 255),
174
+ };
175
+ }
@@ -1,28 +1,28 @@
1
- import type { DynamicColumnConfig, DynamicRecord } from "../../components/DataViews/types"
2
- import { isPlainObject } from "./nestedDataUtils"
1
+ import type { DynamicColumnConfig, DynamicRecord } from "../../components/DataViews/types";
2
+ import { isPlainObject } from "./nestedDataUtils";
3
3
 
4
4
  export function detectColumns(data: DynamicRecord[]): DynamicColumnConfig[] {
5
5
  if (!data || data.length === 0) {
6
- return []
6
+ return [];
7
7
  }
8
8
 
9
- const allKeys = new Set<string>()
9
+ const allKeys = new Set<string>();
10
10
  data.forEach((item) => {
11
- Object.keys(item).forEach((key) => allKeys.add(key))
12
- })
11
+ Object.keys(item).forEach((key) => allKeys.add(key));
12
+ });
13
13
 
14
- const columns: DynamicColumnConfig[] = []
15
- let order = 0
14
+ const columns: DynamicColumnConfig[] = [];
15
+ let order = 0;
16
16
 
17
17
  allKeys.forEach((key) => {
18
- if (key.startsWith("_")) return
18
+ if (key.startsWith("_")) return;
19
19
 
20
- const firstValue = data.find((item) => item[key] != null)?.[key]
20
+ const firstValue = data.find((item) => item[key] != null)?.[key];
21
21
 
22
- if (isPlainObject(firstValue)) return
23
- if (Array.isArray(firstValue) && firstValue.length > 0 && isPlainObject(firstValue[0])) return
22
+ if (isPlainObject(firstValue)) return;
23
+ if (Array.isArray(firstValue) && firstValue.length > 0 && isPlainObject(firstValue[0])) return;
24
24
 
25
- const type = inferColumnType(key, firstValue)
25
+ const type = inferColumnType(key, firstValue);
26
26
 
27
27
  columns.push({
28
28
  id: key,
@@ -30,54 +30,56 @@ export function detectColumns(data: DynamicRecord[]): DynamicColumnConfig[] {
30
30
  visible: true,
31
31
  order: order++,
32
32
  type,
33
+ });
34
+ });
35
+
36
+ return columns
37
+ .sort((a, b) => {
38
+ if (a.id === "id") return -1;
39
+ if (b.id === "id") return 1;
40
+ return a.label.localeCompare(b.label);
33
41
  })
34
- })
35
-
36
- return columns.sort((a, b) => {
37
- if (a.id === "id") return -1
38
- if (b.id === "id") return 1
39
- return a.label.localeCompare(b.label)
40
- }).map((col, idx) => ({ ...col, order: idx }))
42
+ .map((col, idx) => ({ ...col, order: idx }));
41
43
  }
42
44
 
43
45
  function inferColumnType(
44
46
  key: string,
45
- value: any
47
+ value: unknown,
46
48
  ): "text" | "number" | "date" | "badge" | "array" | "boolean" {
47
49
  if (key.toLowerCase().includes("date") || key.toLowerCase().includes("time")) {
48
- return "date"
50
+ return "date";
49
51
  }
50
52
  if (key.toLowerCase().includes("status") || key.toLowerCase().includes("priority")) {
51
- return "badge"
53
+ return "badge";
52
54
  }
53
55
  if (key.toLowerCase().includes("tag") || key.toLowerCase().includes("label")) {
54
- return "array"
56
+ return "array";
55
57
  }
56
58
 
57
59
  if (value === null || value === undefined) {
58
- return "text"
60
+ return "text";
59
61
  }
60
62
 
61
63
  if (typeof value === "boolean") {
62
- return "boolean"
64
+ return "boolean";
63
65
  }
64
66
 
65
67
  if (typeof value === "number") {
66
- return "number"
68
+ return "number";
67
69
  }
68
70
 
69
71
  if (Array.isArray(value)) {
70
- return "array"
72
+ return "array";
71
73
  }
72
74
 
73
75
  if (typeof value === "string") {
74
76
  if (!isNaN(Date.parse(value)) && /^\d{4}-\d{2}-\d{2}/.test(value)) {
75
- return "date"
77
+ return "date";
76
78
  }
77
- return "text"
79
+ return "text";
78
80
  }
79
81
 
80
- return "text"
82
+ return "text";
81
83
  }
82
84
 
83
85
  function formatLabel(key: string): string {
@@ -85,33 +87,33 @@ function formatLabel(key: string): string {
85
87
  return key
86
88
  .split("_")
87
89
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
88
- .join(" ")
90
+ .join(" ");
89
91
  }
90
92
 
91
93
  return key
92
94
  .replace(/([A-Z])/g, " $1")
93
95
  .replace(/^./, (str) => str.toUpperCase())
94
- .trim()
96
+ .trim();
95
97
  }
96
98
 
97
99
  export function mergeColumns(
98
100
  detected: DynamicColumnConfig[],
99
- custom?: Partial<DynamicColumnConfig>[]
101
+ custom?: Partial<DynamicColumnConfig>[],
100
102
  ): DynamicColumnConfig[] {
101
103
  if (!custom || custom.length === 0) {
102
- return detected
104
+ return detected;
103
105
  }
104
106
 
105
- const merged = [...detected]
106
- const customMap = new Map(custom.map((col) => [col.id, col]))
107
+ const merged = [...detected];
108
+ const customMap = new Map(custom.map((col) => [col.id, col]));
107
109
 
108
110
  merged.forEach((col, idx) => {
109
- const customCol = customMap.get(col.id)
111
+ const customCol = customMap.get(col.id);
110
112
  if (customCol) {
111
- merged[idx] = { ...col, ...customCol }
112
- customMap.delete(col.id)
113
+ merged[idx] = { ...col, ...customCol };
114
+ customMap.delete(col.id);
113
115
  }
114
- })
116
+ });
115
117
 
116
118
  customMap.forEach((customCol) => {
117
119
  if (customCol.id) {
@@ -122,9 +124,9 @@ export function mergeColumns(
122
124
  order: customCol.order ?? merged.length,
123
125
  type: customCol.type || "text",
124
126
  render: customCol.render,
125
- })
127
+ });
126
128
  }
127
- })
129
+ });
128
130
 
129
- return merged.sort((a, b) => a.order - b.order)
131
+ return merged.sort((a, b) => a.order - b.order);
130
132
  }
@@ -4,11 +4,11 @@ import type {
4
4
  FieldType,
5
5
  DynamicColumnConfig,
6
6
  InboxConfig,
7
- } from "../../components/DataViews/types"
8
- import { findFirstDefined, formatPathLabel, getByPath } from "./pathUtils"
9
- import { isPlainObject, isCurrencyField, isRatingField } from "./nestedDataUtils"
7
+ } from "../../components/DataViews/types";
8
+ import { findFirstDefined, formatPathLabel, getByPath } from "./pathUtils";
9
+ import { isPlainObject, isCurrencyField, isRatingField } from "./nestedDataUtils";
10
10
 
11
- const ISO_DATE = /^\d{4}-\d{2}-\d{2}/
11
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}/;
12
12
 
13
13
  const KEY_HINTS: Array<[RegExp, FieldType]> = [
14
14
  [/(^|[._])status$/i, "enum-badge"],
@@ -20,46 +20,46 @@ const KEY_HINTS: Array<[RegExp, FieldType]> = [
20
20
  [/(^|[._])(image|thumbnail|photo|picture)$/i, "image"],
21
21
  [/(^|[._])tags?$|labels?$/i, "badge-array"],
22
22
  [/(date|time)$/i, "date-format"],
23
- ]
23
+ ];
24
24
 
25
- export function inferFieldType(path: string, value: any): FieldType {
25
+ export function inferFieldType(path: string, value: unknown): FieldType {
26
26
  for (const [re, type] of KEY_HINTS) {
27
- if (re.test(path)) return type
27
+ if (re.test(path)) return type;
28
28
  }
29
29
 
30
- if (isCurrencyField(path)) return "currency"
31
- if (isRatingField(path) && typeof value === "number" && value <= 5) return "star-rating"
30
+ if (isCurrencyField(path)) return "currency";
31
+ if (isRatingField(path) && typeof value === "number" && value <= 5) return "star-rating";
32
32
 
33
- if (value === null || value === undefined) return "text"
34
- if (typeof value === "boolean") return "boolean"
35
- if (typeof value === "number") return "number"
36
- if (Array.isArray(value)) return "badge-array"
33
+ if (value === null || value === undefined) return "text";
34
+ if (typeof value === "boolean") return "boolean";
35
+ if (typeof value === "number") return "number";
36
+ if (Array.isArray(value)) return "badge-array";
37
37
  if (typeof value === "string") {
38
- if (ISO_DATE.test(value) && !isNaN(Date.parse(value))) return "date-format"
39
- return "text"
38
+ if (ISO_DATE.test(value) && !isNaN(Date.parse(value))) return "date-format";
39
+ return "text";
40
40
  }
41
- return "text"
41
+ return "text";
42
42
  }
43
43
 
44
44
  export function detectFields(data: DynamicRecord[]): FieldConfig[] {
45
- if (!data || data.length === 0) return []
45
+ if (!data || data.length === 0) return [];
46
46
 
47
- const allKeys = new Set<string>()
47
+ const allKeys = new Set<string>();
48
48
  for (const item of data) {
49
49
  if (item && typeof item === "object") {
50
- for (const k of Object.keys(item)) allKeys.add(k)
50
+ for (const k of Object.keys(item)) allKeys.add(k);
51
51
  }
52
52
  }
53
53
 
54
- const fields: FieldConfig[] = []
55
- let order = 0
54
+ const fields: FieldConfig[] = [];
55
+ let order = 0;
56
56
 
57
57
  for (const key of allKeys) {
58
- if (key.startsWith("_")) continue
58
+ if (key.startsWith("_")) continue;
59
59
 
60
- const sample = findFirstDefined(data, key)
61
- if (isPlainObject(sample)) continue
62
- if (Array.isArray(sample) && sample.length > 0 && isPlainObject(sample[0])) continue
60
+ const sample = findFirstDefined(data, key);
61
+ if (isPlainObject(sample)) continue;
62
+ if (Array.isArray(sample) && sample.length > 0 && isPlainObject(sample[0])) continue;
63
63
 
64
64
  fields.push({
65
65
  path: key,
@@ -67,32 +67,29 @@ export function detectFields(data: DynamicRecord[]): FieldConfig[] {
67
67
  type: inferFieldType(key, sample),
68
68
  visible: true,
69
69
  order: order++,
70
- })
70
+ });
71
71
  }
72
72
 
73
73
  return fields
74
74
  .sort((a, b) => {
75
- if (a.path === "id") return -1
76
- if (b.path === "id") return 1
77
- return (a.label || "").localeCompare(b.label || "")
75
+ if (a.path === "id") return -1;
76
+ if (b.path === "id") return 1;
77
+ return (a.label || "").localeCompare(b.label || "");
78
78
  })
79
- .map((f, i) => ({ ...f, order: i }))
79
+ .map((f, i) => ({ ...f, order: i }));
80
80
  }
81
81
 
82
- export function mergeFields(
83
- detected: FieldConfig[],
84
- custom?: FieldConfig[],
85
- ): FieldConfig[] {
86
- if (!custom || custom.length === 0) return detected
82
+ export function mergeFields(detected: FieldConfig[], custom?: FieldConfig[]): FieldConfig[] {
83
+ if (!custom || custom.length === 0) return detected;
87
84
 
88
- const out = [...detected]
89
- const byPath = new Map(out.map((f, i) => [f.path, i]))
85
+ const out = [...detected];
86
+ const byPath = new Map(out.map((f, i) => [f.path, i]));
90
87
 
91
88
  for (const c of custom) {
92
- if (!c.path) continue
93
- const existing = byPath.get(c.path)
89
+ if (!c.path) continue;
90
+ const existing = byPath.get(c.path);
94
91
  if (existing != null) {
95
- out[existing] = { ...out[existing], ...c }
92
+ out[existing] = { ...out[existing], ...c };
96
93
  } else {
97
94
  out.push({
98
95
  ...c,
@@ -100,24 +97,27 @@ export function mergeFields(
100
97
  label: c.label ?? formatPathLabel(c.path),
101
98
  visible: c.visible ?? true,
102
99
  order: c.order ?? out.length,
103
- })
104
- byPath.set(c.path, out.length - 1)
100
+ });
101
+ byPath.set(c.path, out.length - 1);
105
102
  }
106
103
  }
107
104
 
108
105
  return out
109
106
  .map((f, i) => ({ ...f, order: f.order ?? i }))
110
- .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
107
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
111
108
  }
112
109
 
113
110
  export function visibleFields(fields: FieldConfig[]): FieldConfig[] {
114
- return fields.filter((f) => f.type !== "hidden" && f.visible !== false)
111
+ return fields.filter((f) => f.type !== "hidden" && f.visible !== false);
115
112
  }
116
113
 
117
- export function fieldToColumn(field: FieldConfig, idx = 0): DynamicColumnConfig & {
118
- __field: FieldConfig
114
+ export function fieldToColumn(
115
+ field: FieldConfig,
116
+ idx = 0,
117
+ ): DynamicColumnConfig & {
118
+ __field: FieldConfig;
119
119
  } {
120
- const legacyType = mapFieldTypeToColumnType(field.type)
120
+ const legacyType = mapFieldTypeToColumnType(field.type);
121
121
  return {
122
122
  id: field.path,
123
123
  label: field.label ?? formatPathLabel(field.path),
@@ -126,7 +126,7 @@ export function fieldToColumn(field: FieldConfig, idx = 0): DynamicColumnConfig
126
126
  type: legacyType,
127
127
  render: field.render,
128
128
  __field: field,
129
- }
129
+ };
130
130
  }
131
131
 
132
132
  function mapFieldTypeToColumnType(t?: FieldType): DynamicColumnConfig["type"] {
@@ -136,63 +136,62 @@ function mapFieldTypeToColumnType(t?: FieldType): DynamicColumnConfig["type"] {
136
136
  case "number-format":
137
137
  case "progress-bar":
138
138
  case "star-rating":
139
- return "number"
139
+ return "number";
140
140
  case "date":
141
141
  case "date-format":
142
- return "date"
142
+ return "date";
143
143
  case "enum-badge":
144
144
  case "icon-text":
145
145
  case "two-line":
146
146
  case "link":
147
- return "badge"
147
+ return "badge";
148
148
  case "badge-array":
149
- return "array"
149
+ return "array";
150
150
  case "boolean":
151
- return "boolean"
151
+ return "boolean";
152
152
  default:
153
- return "text"
153
+ return "text";
154
154
  }
155
155
  }
156
156
 
157
- const STARRED_PATTERNS = ["isStarred", "starred", "favorite", "isFavorite", "pinned"]
158
- const READ_PATTERNS = ["isRead", "read", "seen", "viewed"]
159
- const ATTACHMENT_PATTERNS = ["hasAttachment", "hasAttachments", "attachments", "files"]
160
- const PRIORITY_PATTERNS = ["priority", "urgency", "level", "importance"]
157
+ const STARRED_PATTERNS = ["isStarred", "starred", "favorite", "isFavorite", "pinned"];
158
+ const READ_PATTERNS = ["isRead", "read", "seen", "viewed"];
159
+ const ATTACHMENT_PATTERNS = ["hasAttachment", "hasAttachments", "attachments", "files"];
160
+ const PRIORITY_PATTERNS = ["priority", "urgency", "level", "importance"];
161
161
 
162
162
  function pickField(sample: DynamicRecord, patterns: string[]): string | null {
163
163
  for (const p of patterns) {
164
- if (p in sample) return p
164
+ if (p in sample) return p;
165
165
  }
166
- return null
166
+ return null;
167
167
  }
168
168
 
169
169
  export function resolveInboxConfig(
170
170
  data: DynamicRecord[],
171
171
  user?: InboxConfig,
172
- ): Required<Omit<InboxConfig, "titlePath" | "previewPath">> & Pick<InboxConfig, "titlePath" | "previewPath"> {
173
- const sample = data?.[0] && typeof data[0] === "object" ? data[0] : {}
172
+ ): Required<Omit<InboxConfig, "titlePath" | "previewPath">> &
173
+ Pick<InboxConfig, "titlePath" | "previewPath"> {
174
+ const sample = data?.[0] && typeof data[0] === "object" ? data[0] : {};
174
175
 
175
176
  const auto = {
176
177
  starredField: pickField(sample, STARRED_PATTERNS),
177
178
  readField: pickField(sample, READ_PATTERNS),
178
179
  attachmentField: pickField(sample, ATTACHMENT_PATTERNS),
179
180
  priorityField: pickField(sample, PRIORITY_PATTERNS),
180
- }
181
+ };
181
182
 
182
183
  return {
183
184
  starredField: user?.starredField !== undefined ? user.starredField : auto.starredField,
184
185
  readField: user?.readField !== undefined ? user.readField : auto.readField,
185
- attachmentField: user?.attachmentField !== undefined ? user.attachmentField : auto.attachmentField,
186
+ attachmentField:
187
+ user?.attachmentField !== undefined ? user.attachmentField : auto.attachmentField,
186
188
  priorityField: user?.priorityField !== undefined ? user.priorityField : auto.priorityField,
187
189
  titlePath: user?.titlePath,
188
190
  previewPath: user?.previewPath,
189
- }
191
+ };
190
192
  }
191
193
 
192
- export function readInboxField(
193
- item: DynamicRecord,
194
- field: string | null | undefined,
195
- ): any {
196
- if (!field) return undefined
197
- return getByPath(item, field)
194
+ export function readInboxField(item: DynamicRecord, field: string | null | undefined): unknown {
195
+ if (!field) return undefined;
196
+ return getByPath(item, field);
198
197
  }