@puzzle-ui/ui 0.3.0 → 0.5.0

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 (240) hide show
  1. package/README.md +70 -70
  2. package/dist/components/actions/button/button.css +5 -2
  3. package/dist/components/actions/button-group/button-group.css +16 -17
  4. package/dist/components/data-display/badge/badge.css +15 -2
  5. package/dist/components/data-display/badge/index.cjs +13 -2
  6. package/dist/components/data-display/badge/index.cjs.map +1 -1
  7. package/dist/components/data-display/badge/index.d.cts +7 -2
  8. package/dist/components/data-display/badge/index.d.ts +7 -2
  9. package/dist/components/data-display/badge/index.js +13 -2
  10. package/dist/components/data-display/badge/index.js.map +1 -1
  11. package/dist/components/data-display/empty-state/empty-state.css +128 -0
  12. package/dist/components/data-display/empty-state/empty-state.utils.cjs +6 -0
  13. package/dist/components/data-display/empty-state/empty-state.utils.cjs.map +1 -0
  14. package/dist/components/data-display/empty-state/empty-state.utils.js +6 -0
  15. package/dist/components/data-display/empty-state/empty-state.utils.js.map +1 -0
  16. package/dist/components/data-display/empty-state/index.cjs +49 -0
  17. package/dist/components/data-display/empty-state/index.cjs.map +1 -0
  18. package/dist/components/data-display/empty-state/index.d.cts +19 -0
  19. package/dist/components/data-display/empty-state/index.d.ts +19 -0
  20. package/dist/components/data-display/empty-state/index.js +44 -0
  21. package/dist/components/data-display/empty-state/index.js.map +1 -0
  22. package/dist/components/data-display/rating/index.cjs +84 -0
  23. package/dist/components/data-display/rating/index.cjs.map +1 -0
  24. package/dist/components/data-display/rating/index.d.cts +3 -0
  25. package/dist/components/data-display/rating/index.d.ts +3 -0
  26. package/dist/components/data-display/rating/index.js +84 -0
  27. package/dist/components/data-display/rating/index.js.map +1 -0
  28. package/dist/components/data-display/rating/rating.css +72 -0
  29. package/dist/components/data-display/rating/rating.types.d.cts +15 -0
  30. package/dist/components/data-display/rating/rating.types.d.ts +15 -0
  31. package/dist/components/data-display/rating/rating.utils.cjs +27 -0
  32. package/dist/components/data-display/rating/rating.utils.cjs.map +1 -0
  33. package/dist/components/data-display/rating/rating.utils.js +25 -0
  34. package/dist/components/data-display/rating/rating.utils.js.map +1 -0
  35. package/dist/components/editors/text-editor/editor.cjs.map +1 -1
  36. package/dist/components/editors/text-editor/editor.js.map +1 -1
  37. package/dist/components/editors/text-editor/text-document.cjs.map +1 -1
  38. package/dist/components/editors/text-editor/text-document.js.map +1 -1
  39. package/dist/components/editors/text-editor/text-parser.cjs.map +1 -1
  40. package/dist/components/editors/text-editor/text-parser.js.map +1 -1
  41. package/dist/components/editors/text-editor/text-renderer.cjs.map +1 -1
  42. package/dist/components/editors/text-editor/text-renderer.js.map +1 -1
  43. package/dist/components/forms/autocomplete/index.cjs.map +1 -1
  44. package/dist/components/forms/autocomplete/index.js.map +1 -1
  45. package/dist/components/forms/checkbox/checkbox.css +2 -10
  46. package/dist/components/forms/color-picker/color-picker.css +351 -0
  47. package/dist/components/forms/color-picker/color-picker.types.d.cts +25 -0
  48. package/dist/components/forms/color-picker/color-picker.types.d.ts +25 -0
  49. package/dist/components/forms/color-picker/color-picker.utils.cjs +116 -0
  50. package/dist/components/forms/color-picker/color-picker.utils.cjs.map +1 -0
  51. package/dist/components/forms/color-picker/color-picker.utils.js +109 -0
  52. package/dist/components/forms/color-picker/color-picker.utils.js.map +1 -0
  53. package/dist/components/forms/color-picker/index.cjs +340 -0
  54. package/dist/components/forms/color-picker/index.cjs.map +1 -0
  55. package/dist/components/forms/color-picker/index.d.cts +3 -0
  56. package/dist/components/forms/color-picker/index.d.ts +3 -0
  57. package/dist/components/forms/color-picker/index.js +340 -0
  58. package/dist/components/forms/color-picker/index.js.map +1 -0
  59. package/dist/components/forms/file-upload/file-upload.utils.cjs.map +1 -1
  60. package/dist/components/forms/file-upload/file-upload.utils.js.map +1 -1
  61. package/dist/components/forms/input/number-input.js +5 -5
  62. package/dist/components/forms/input/number-input.js.map +1 -1
  63. package/dist/components/forms/input-group/index.d.cts +3 -0
  64. package/dist/components/forms/input-group/index.d.ts +3 -0
  65. package/dist/components/forms/input-group/input-group.cjs +18 -0
  66. package/dist/components/forms/input-group/input-group.cjs.map +1 -0
  67. package/dist/components/forms/input-group/input-group.css +62 -0
  68. package/dist/components/forms/input-group/input-group.d.cts +7 -0
  69. package/dist/components/forms/input-group/input-group.d.ts +7 -0
  70. package/dist/components/forms/input-group/input-group.js +18 -0
  71. package/dist/components/forms/input-group/input-group.js.map +1 -0
  72. package/dist/components/forms/input-group/input-group.types.d.cts +7 -0
  73. package/dist/components/forms/input-group/input-group.types.d.ts +7 -0
  74. package/dist/components/forms/input-group/input-group.utils.cjs +5 -0
  75. package/dist/components/forms/input-group/input-group.utils.cjs.map +1 -0
  76. package/dist/components/forms/input-group/input-group.utils.js +5 -0
  77. package/dist/components/forms/input-group/input-group.utils.js.map +1 -0
  78. package/dist/components/forms/radio-group/index.d.cts +2 -1
  79. package/dist/components/forms/radio-group/index.d.ts +2 -1
  80. package/dist/components/forms/select/index.cjs.map +1 -1
  81. package/dist/components/forms/select/index.js.map +1 -1
  82. package/dist/components/forms/slider/index.cjs +1 -1
  83. package/dist/components/forms/slider/index.cjs.map +1 -1
  84. package/dist/components/forms/slider/index.js +1 -1
  85. package/dist/components/forms/slider/index.js.map +1 -1
  86. package/dist/components/forms/stepper/index.d.cts +4 -2
  87. package/dist/components/forms/stepper/index.d.ts +4 -2
  88. package/dist/components/forms/time-picker/index.cjs +108 -0
  89. package/dist/components/forms/time-picker/index.cjs.map +1 -0
  90. package/dist/components/forms/time-picker/index.d.cts +3 -0
  91. package/dist/components/forms/time-picker/index.d.ts +3 -0
  92. package/dist/components/forms/time-picker/index.js +108 -0
  93. package/dist/components/forms/time-picker/index.js.map +1 -0
  94. package/dist/components/forms/time-picker/time-picker.css +166 -0
  95. package/dist/components/forms/time-picker/time-picker.types.d.cts +34 -0
  96. package/dist/components/forms/time-picker/time-picker.types.d.ts +34 -0
  97. package/dist/components/forms/time-picker/time-picker.utils.cjs +25 -0
  98. package/dist/components/forms/time-picker/time-picker.utils.cjs.map +1 -0
  99. package/dist/components/forms/time-picker/time-picker.utils.js +24 -0
  100. package/dist/components/forms/time-picker/time-picker.utils.js.map +1 -0
  101. package/dist/components/forms/token-input/index.cjs +189 -0
  102. package/dist/components/forms/token-input/index.cjs.map +1 -0
  103. package/dist/components/forms/token-input/index.d.cts +3 -0
  104. package/dist/components/forms/token-input/index.d.ts +3 -0
  105. package/dist/components/forms/token-input/index.js +189 -0
  106. package/dist/components/forms/token-input/index.js.map +1 -0
  107. package/dist/components/forms/token-input/token-input.css +145 -0
  108. package/dist/components/forms/token-input/token-input.types.d.cts +48 -0
  109. package/dist/components/forms/token-input/token-input.types.d.ts +48 -0
  110. package/dist/components/forms/token-input/token-input.utils.cjs +30 -0
  111. package/dist/components/forms/token-input/token-input.utils.cjs.map +1 -0
  112. package/dist/components/forms/token-input/token-input.utils.js +24 -0
  113. package/dist/components/forms/token-input/token-input.utils.js.map +1 -0
  114. package/dist/components/layout/aspect-ratio/aspect-ratio.css +14 -0
  115. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.cjs +11 -0
  116. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.cjs.map +1 -0
  117. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.js +11 -0
  118. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.js.map +1 -0
  119. package/dist/components/layout/aspect-ratio/index.cjs +16 -0
  120. package/dist/components/layout/aspect-ratio/index.cjs.map +1 -0
  121. package/dist/components/layout/aspect-ratio/index.d.cts +11 -0
  122. package/dist/components/layout/aspect-ratio/index.d.ts +11 -0
  123. package/dist/components/layout/aspect-ratio/index.js +16 -0
  124. package/dist/components/layout/aspect-ratio/index.js.map +1 -0
  125. package/dist/components/layout/container/container.css +1 -0
  126. package/dist/components/layout/page-layout/page-layout.css +1 -0
  127. package/dist/components/layout/resizable-panels/index.cjs +1 -1
  128. package/dist/components/layout/resizable-panels/index.cjs.map +1 -1
  129. package/dist/components/layout/resizable-panels/index.js +1 -1
  130. package/dist/components/layout/resizable-panels/index.js.map +1 -1
  131. package/dist/components/layout/resizable-panels/resizable-panels.utils.cjs.map +1 -1
  132. package/dist/components/layout/resizable-panels/resizable-panels.utils.js.map +1 -1
  133. package/dist/components/layout/scroll-area/index.cjs +27 -0
  134. package/dist/components/layout/scroll-area/index.cjs.map +1 -0
  135. package/dist/components/layout/scroll-area/index.d.cts +15 -0
  136. package/dist/components/layout/scroll-area/index.d.ts +15 -0
  137. package/dist/components/layout/scroll-area/index.js +27 -0
  138. package/dist/components/layout/scroll-area/index.js.map +1 -0
  139. package/dist/components/layout/scroll-area/scroll-area.css +25 -0
  140. package/dist/components/layout/scroll-area/scroll-area.utils.cjs +8 -0
  141. package/dist/components/layout/scroll-area/scroll-area.utils.cjs.map +1 -0
  142. package/dist/components/layout/scroll-area/scroll-area.utils.d.cts +2 -0
  143. package/dist/components/layout/scroll-area/scroll-area.utils.d.ts +2 -0
  144. package/dist/components/layout/scroll-area/scroll-area.utils.js +8 -0
  145. package/dist/components/layout/scroll-area/scroll-area.utils.js.map +1 -0
  146. package/dist/components/layout/section/index.cjs +10 -4
  147. package/dist/components/layout/section/index.cjs.map +1 -1
  148. package/dist/components/layout/section/index.d.cts +1 -1
  149. package/dist/components/layout/section/index.d.ts +1 -1
  150. package/dist/components/layout/section/index.js +11 -5
  151. package/dist/components/layout/section/index.js.map +1 -1
  152. package/dist/components/layout/section/section.css +9 -3
  153. package/dist/components/layout/section/types.d.cts +5 -5
  154. package/dist/components/layout/section/types.d.ts +5 -5
  155. package/dist/components/layout/separator/index.cjs +18 -0
  156. package/dist/components/layout/separator/index.cjs.map +1 -0
  157. package/dist/components/layout/separator/index.d.cts +17 -0
  158. package/dist/components/layout/separator/index.d.ts +17 -0
  159. package/dist/components/layout/separator/index.js +18 -0
  160. package/dist/components/layout/separator/index.js.map +1 -0
  161. package/dist/components/layout/separator/separator.css +15 -0
  162. package/dist/components/layout/separator/separator.utils.cjs +11 -0
  163. package/dist/components/layout/separator/separator.utils.cjs.map +1 -0
  164. package/dist/components/layout/separator/separator.utils.d.cts +2 -0
  165. package/dist/components/layout/separator/separator.utils.d.ts +2 -0
  166. package/dist/components/layout/separator/separator.utils.js +11 -0
  167. package/dist/components/layout/separator/separator.utils.js.map +1 -0
  168. package/dist/components/layout/stack/index.cjs +18 -2
  169. package/dist/components/layout/stack/index.cjs.map +1 -1
  170. package/dist/components/layout/stack/index.d.cts +8 -6
  171. package/dist/components/layout/stack/index.d.ts +8 -6
  172. package/dist/components/layout/stack/index.js +18 -2
  173. package/dist/components/layout/stack/index.js.map +1 -1
  174. package/dist/components/layout/stack/stack.css +118 -138
  175. package/dist/components/navigation/accordion/index.cjs.map +1 -1
  176. package/dist/components/navigation/accordion/index.js.map +1 -1
  177. package/dist/components/navigation/carousel/index.cjs +5 -2
  178. package/dist/components/navigation/carousel/index.cjs.map +1 -1
  179. package/dist/components/navigation/carousel/index.js +5 -2
  180. package/dist/components/navigation/carousel/index.js.map +1 -1
  181. package/dist/components/navigation/pagination/index.cjs +146 -0
  182. package/dist/components/navigation/pagination/index.cjs.map +1 -0
  183. package/dist/components/navigation/pagination/index.d.cts +3 -0
  184. package/dist/components/navigation/pagination/index.d.ts +3 -0
  185. package/dist/components/navigation/pagination/index.js +146 -0
  186. package/dist/components/navigation/pagination/index.js.map +1 -0
  187. package/dist/components/navigation/pagination/pagination.css +134 -0
  188. package/dist/components/navigation/pagination/pagination.types.d.cts +38 -0
  189. package/dist/components/navigation/pagination/pagination.types.d.ts +38 -0
  190. package/dist/components/navigation/pagination/pagination.utils.cjs +54 -0
  191. package/dist/components/navigation/pagination/pagination.utils.cjs.map +1 -0
  192. package/dist/components/navigation/pagination/pagination.utils.js +53 -0
  193. package/dist/components/navigation/pagination/pagination.utils.js.map +1 -0
  194. package/dist/components/navigation/tabs/index.d.cts +3 -2
  195. package/dist/components/navigation/tabs/index.d.ts +3 -2
  196. package/dist/components/navigation/tree-view/index.cjs.map +1 -1
  197. package/dist/components/navigation/tree-view/index.js.map +1 -1
  198. package/dist/components/overlays/context-menu/context-menu.css +151 -0
  199. package/dist/components/overlays/context-menu/context-menu.types.d.cts +69 -0
  200. package/dist/components/overlays/context-menu/context-menu.types.d.ts +69 -0
  201. package/dist/components/overlays/context-menu/context-menu.utils.cjs +46 -0
  202. package/dist/components/overlays/context-menu/context-menu.utils.cjs.map +1 -0
  203. package/dist/components/overlays/context-menu/context-menu.utils.js +40 -0
  204. package/dist/components/overlays/context-menu/context-menu.utils.js.map +1 -0
  205. package/dist/components/overlays/context-menu/index.cjs +830 -0
  206. package/dist/components/overlays/context-menu/index.cjs.map +1 -0
  207. package/dist/components/overlays/context-menu/index.d.cts +42 -0
  208. package/dist/components/overlays/context-menu/index.d.ts +42 -0
  209. package/dist/components/overlays/context-menu/index.js +818 -0
  210. package/dist/components/overlays/context-menu/index.js.map +1 -0
  211. package/dist/components/overlays/dropdown-menu/dropdown-menu-content.cjs.map +1 -1
  212. package/dist/components/overlays/dropdown-menu/dropdown-menu-content.js.map +1 -1
  213. package/dist/components/overlays/dropdown-menu/dropdown-menu.utils.cjs.map +1 -1
  214. package/dist/components/overlays/dropdown-menu/dropdown-menu.utils.js.map +1 -1
  215. package/dist/components/typography/heading.cjs +6 -2
  216. package/dist/components/typography/heading.cjs.map +1 -1
  217. package/dist/components/typography/heading.d.cts +3 -2
  218. package/dist/components/typography/heading.d.ts +3 -2
  219. package/dist/components/typography/heading.js +6 -2
  220. package/dist/components/typography/heading.js.map +1 -1
  221. package/dist/components/typography/text.cjs +6 -2
  222. package/dist/components/typography/text.cjs.map +1 -1
  223. package/dist/components/typography/text.d.cts +3 -2
  224. package/dist/components/typography/text.d.ts +3 -2
  225. package/dist/components/typography/text.js +6 -2
  226. package/dist/components/typography/text.js.map +1 -1
  227. package/dist/components/utilities/preview/index.cjs +89 -21
  228. package/dist/components/utilities/preview/index.cjs.map +1 -1
  229. package/dist/components/utilities/preview/index.d.cts +6 -2
  230. package/dist/components/utilities/preview/index.d.ts +6 -2
  231. package/dist/components/utilities/preview/index.js +89 -21
  232. package/dist/components/utilities/preview/index.js.map +1 -1
  233. package/dist/components/utilities/preview/preview.css +26 -11
  234. package/dist/components/utilities/transition/index.d.cts +1 -1
  235. package/dist/components/utilities/transition/index.d.ts +1 -1
  236. package/dist/index.cjs +136 -97
  237. package/dist/index.d.cts +23 -2
  238. package/dist/index.d.ts +23 -2
  239. package/dist/index.js +12 -1
  240. package/package.json +7 -6
@@ -0,0 +1,340 @@
1
+ "use client";
2
+ import { Popout } from "../../overlays/popout/index.js";
3
+ import { DEFAULT_HSV, hsvToFormat, parseColorToHsvInBrowser, resolveCssColor } from "./color-picker.utils.js";
4
+ import "./color-picker.css";
5
+ import { clamp, cn, isColorDark } from "@puzzle-ui/core";
6
+ import { useCallback, useEffect, useId, useRef, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ // src/components/forms/color-picker/index.tsx
9
+ const getSwatchValue = (swatch) => typeof swatch === "string" ? swatch : swatch.value;
10
+ const getSwatchLabel = (swatch) => typeof swatch === "string" ? swatch : swatch.label ?? swatch.value;
11
+ const ColorPicker = (props) => {
12
+ const { value, defaultValue, onChange, label, description, placeholder = "Select color", colors = [], format = "hexa", clearable = true, clearLabel = "Clear color", open, defaultOpen = false, onOpenChange, position = "bottom", align = "start", disabled = false, className, style, rootClassName, popoverClassName, id, ref, ...triggerProps } = props;
13
+ const generatedId = useId();
14
+ const triggerId = id ?? generatedId;
15
+ const labelId = `${generatedId}-label`;
16
+ const descriptionId = `${generatedId}-description`;
17
+ const panelId = `${generatedId}-panel`;
18
+ const areaId = `${generatedId}-area`;
19
+ const [internalValue, setInternalValue] = useState(defaultValue);
20
+ const isControlled = Object.prototype.hasOwnProperty.call(props, "value");
21
+ const selectedColor = isControlled ? value : internalValue;
22
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
23
+ const isOpen = open ?? internalOpen;
24
+ const [hsv, setHsv] = useState(() => (selectedColor ? parseColorToHsvInBrowser(selectedColor) : null) ?? DEFAULT_HSV);
25
+ const [inputValue, setInputValue] = useState(selectedColor ?? "");
26
+ const [inputError, setInputError] = useState(false);
27
+ const areaRef = useRef(null);
28
+ const changeOpen = useCallback((nextOpen) => {
29
+ if (open === void 0) setInternalOpen(nextOpen);
30
+ onOpenChange?.(nextOpen);
31
+ }, [open, onOpenChange]);
32
+ const commitColor = useCallback((nextColor) => {
33
+ if (!isControlled) setInternalValue(nextColor);
34
+ onChange?.(nextColor);
35
+ setInputValue(nextColor ?? "");
36
+ setInputError(false);
37
+ }, [isControlled, onChange]);
38
+ const updateHsv = useCallback((next) => {
39
+ const normalized = {
40
+ h: (next.h % 360 + 360) % 360,
41
+ s: clamp(next.s, 0, 100),
42
+ v: clamp(next.v, 0, 100),
43
+ a: clamp(next.a, 0, 1)
44
+ };
45
+ setHsv(normalized);
46
+ commitColor(hsvToFormat(normalized, format));
47
+ }, [commitColor, format]);
48
+ useEffect(() => {
49
+ if (!selectedColor) {
50
+ setHsv(DEFAULT_HSV);
51
+ return;
52
+ }
53
+ const parsed = parseColorToHsvInBrowser(selectedColor);
54
+ if (parsed) setHsv(parsed);
55
+ }, [selectedColor]);
56
+ useEffect(() => {
57
+ if (!isOpen) {
58
+ setInputValue(selectedColor ?? "");
59
+ setInputError(false);
60
+ }
61
+ }, [isOpen, selectedColor]);
62
+ const updateArea = (event) => {
63
+ if (event.type === "pointermove" && event.buttons !== 1) return;
64
+ const rect = event.currentTarget.getBoundingClientRect();
65
+ if (rect.width <= 0 || rect.height <= 0) return;
66
+ const saturation = clamp((event.clientX - rect.left) / rect.width * 100, 0, 100);
67
+ const brightness = clamp((1 - (event.clientY - rect.top) / rect.height) * 100, 0, 100);
68
+ updateHsv({
69
+ ...hsv,
70
+ s: saturation,
71
+ v: brightness
72
+ });
73
+ };
74
+ const handleAreaKeyDown = (event) => {
75
+ const step = event.shiftKey ? 10 : 1;
76
+ let saturation = hsv.s;
77
+ let brightness = hsv.v;
78
+ switch (event.key) {
79
+ case "ArrowLeft":
80
+ saturation -= step;
81
+ break;
82
+ case "ArrowRight":
83
+ saturation += step;
84
+ break;
85
+ case "ArrowUp":
86
+ brightness += step;
87
+ break;
88
+ case "ArrowDown":
89
+ brightness -= step;
90
+ break;
91
+ case "Home":
92
+ saturation = 0;
93
+ break;
94
+ case "End":
95
+ saturation = 100;
96
+ break;
97
+ default: return;
98
+ }
99
+ event.preventDefault();
100
+ updateHsv({
101
+ ...hsv,
102
+ s: saturation,
103
+ v: brightness
104
+ });
105
+ };
106
+ const selectSwatch = (color) => {
107
+ commitColor(color);
108
+ const parsed = parseColorToHsvInBrowser(color);
109
+ if (parsed) setHsv(parsed);
110
+ };
111
+ const applyInput = () => {
112
+ const color = inputValue.trim();
113
+ const parsed = parseColorToHsvInBrowser(color);
114
+ if (!parsed) {
115
+ setInputError(true);
116
+ return;
117
+ }
118
+ commitColor(color);
119
+ setHsv(parsed);
120
+ };
121
+ const hueColor = `hsl(${hsv.h}, 100%, 50%)`;
122
+ const colorAreaStyle = { "--puzzle-color-picker-hue": hueColor };
123
+ const triggerSwatchStyle = { background: selectedColor ? resolveCssColor(selectedColor) ?? selectedColor : void 0 };
124
+ const clearColor = () => {
125
+ commitColor(void 0);
126
+ setHsv(DEFAULT_HSV);
127
+ };
128
+ return /* @__PURE__ */ jsxs("div", {
129
+ className: cn("puzzle-color-picker", rootClassName),
130
+ "data-disabled": disabled || void 0,
131
+ children: [
132
+ label && /* @__PURE__ */ jsx("label", {
133
+ id: labelId,
134
+ className: "puzzle-color-picker__label",
135
+ htmlFor: triggerId,
136
+ children: label
137
+ }),
138
+ /* @__PURE__ */ jsx(Popout, {
139
+ position,
140
+ align,
141
+ action: "click",
142
+ open: isOpen,
143
+ onOpenChange: changeOpen,
144
+ closeOnOutsideClick: true,
145
+ closeOnEscape: true,
146
+ contentClassName: cn("puzzle-color-picker__popover", popoverClassName),
147
+ trigger: /* @__PURE__ */ jsxs("button", {
148
+ ...triggerProps,
149
+ ref,
150
+ id: triggerId,
151
+ type: "button",
152
+ className: cn("puzzle-color-picker__trigger", className),
153
+ style,
154
+ disabled,
155
+ "aria-label": label ? void 0 : "Choose color",
156
+ "aria-labelledby": label ? labelId : void 0,
157
+ "aria-describedby": description ? descriptionId : void 0,
158
+ "aria-haspopup": "dialog",
159
+ "aria-expanded": isOpen,
160
+ "aria-controls": isOpen ? panelId : void 0,
161
+ children: [
162
+ /* @__PURE__ */ jsx("span", {
163
+ className: "puzzle-color-picker__trigger-swatch",
164
+ style: triggerSwatchStyle,
165
+ "aria-hidden": "true"
166
+ }),
167
+ /* @__PURE__ */ jsx("span", {
168
+ className: "puzzle-color-picker__trigger-value",
169
+ children: selectedColor ?? placeholder
170
+ }),
171
+ /* @__PURE__ */ jsx("span", {
172
+ className: "puzzle-color-picker__trigger-chevron",
173
+ "aria-hidden": "true",
174
+ children: "▾"
175
+ })
176
+ ]
177
+ }),
178
+ children: /* @__PURE__ */ jsxs("div", {
179
+ id: panelId,
180
+ className: "puzzle-color-picker__panel",
181
+ role: "dialog",
182
+ "aria-label": label ? `Choose ${label}` : "Choose color",
183
+ children: [
184
+ /* @__PURE__ */ jsxs("div", {
185
+ ref: areaRef,
186
+ id: areaId,
187
+ className: "puzzle-color-picker__area",
188
+ style: colorAreaStyle,
189
+ onPointerDown: (event) => {
190
+ event.currentTarget.setPointerCapture(event.pointerId);
191
+ updateArea(event);
192
+ },
193
+ onPointerMove: updateArea,
194
+ onKeyDown: handleAreaKeyDown,
195
+ role: "group",
196
+ "aria-label": "Color saturation and brightness",
197
+ tabIndex: 0,
198
+ children: [
199
+ /* @__PURE__ */ jsx("span", {
200
+ className: "puzzle-color-picker__area-saturation",
201
+ "aria-hidden": "true"
202
+ }),
203
+ /* @__PURE__ */ jsx("span", {
204
+ className: "puzzle-color-picker__area-brightness",
205
+ "aria-hidden": "true"
206
+ }),
207
+ /* @__PURE__ */ jsx("span", {
208
+ className: "puzzle-color-picker__area-indicator",
209
+ style: {
210
+ left: `${hsv.s}%`,
211
+ top: `${100 - hsv.v}%`
212
+ },
213
+ "aria-hidden": "true"
214
+ })
215
+ ]
216
+ }),
217
+ /* @__PURE__ */ jsxs("div", {
218
+ className: "puzzle-color-picker__sliders",
219
+ children: [/* @__PURE__ */ jsxs("label", {
220
+ className: "puzzle-color-picker__slider-row",
221
+ children: [/* @__PURE__ */ jsx("span", { children: "Hue" }), /* @__PURE__ */ jsx("input", {
222
+ type: "range",
223
+ min: 0,
224
+ max: 359,
225
+ value: Math.round(hsv.h),
226
+ "aria-label": "Hue",
227
+ onChange: (event) => updateHsv({
228
+ ...hsv,
229
+ h: Number(event.currentTarget.value)
230
+ }),
231
+ className: "puzzle-color-picker__hue-slider"
232
+ })]
233
+ }), /* @__PURE__ */ jsxs("label", {
234
+ className: "puzzle-color-picker__slider-row",
235
+ children: [/* @__PURE__ */ jsx("span", { children: "Opacity" }), /* @__PURE__ */ jsx("input", {
236
+ type: "range",
237
+ min: 0,
238
+ max: 100,
239
+ value: Math.round(hsv.a * 100),
240
+ "aria-label": "Opacity",
241
+ onChange: (event) => updateHsv({
242
+ ...hsv,
243
+ a: Number(event.currentTarget.value) / 100
244
+ }),
245
+ className: "puzzle-color-picker__opacity-slider",
246
+ style: { "--puzzle-color-picker-hue": hueColor }
247
+ })]
248
+ })]
249
+ }),
250
+ /* @__PURE__ */ jsxs("div", {
251
+ className: "puzzle-color-picker__input-section",
252
+ children: [
253
+ /* @__PURE__ */ jsx("label", {
254
+ htmlFor: `${generatedId}-input`,
255
+ className: "puzzle-color-picker__input-label",
256
+ children: "Color value"
257
+ }),
258
+ /* @__PURE__ */ jsxs("div", {
259
+ className: "puzzle-color-picker__input-row",
260
+ children: [/* @__PURE__ */ jsx("input", {
261
+ id: `${generatedId}-input`,
262
+ type: "text",
263
+ value: inputValue,
264
+ spellCheck: false,
265
+ autoComplete: "off",
266
+ "aria-label": "Color value",
267
+ "aria-invalid": inputError || void 0,
268
+ placeholder: "#RRGGBB",
269
+ onChange: (event) => {
270
+ setInputValue(event.currentTarget.value);
271
+ setInputError(false);
272
+ },
273
+ onKeyDown: (event) => {
274
+ if (event.key === "Enter") {
275
+ event.preventDefault();
276
+ applyInput();
277
+ }
278
+ }
279
+ }), /* @__PURE__ */ jsx("button", {
280
+ type: "button",
281
+ className: "puzzle-color-picker__apply",
282
+ onClick: applyInput,
283
+ children: "Apply"
284
+ })]
285
+ }),
286
+ inputError && /* @__PURE__ */ jsx("p", {
287
+ className: "puzzle-color-picker__error",
288
+ role: "alert",
289
+ children: "Enter a valid CSS color."
290
+ })
291
+ ]
292
+ }),
293
+ (clearable || colors.length > 0) && /* @__PURE__ */ jsxs("div", {
294
+ className: "puzzle-color-picker__swatches-section",
295
+ children: [/* @__PURE__ */ jsx("span", {
296
+ className: "puzzle-color-picker__section-label",
297
+ children: "Presets"
298
+ }), /* @__PURE__ */ jsxs("div", {
299
+ className: "puzzle-color-picker__swatches",
300
+ role: "group",
301
+ "aria-label": "Preset colors",
302
+ children: [clearable && /* @__PURE__ */ jsx("button", {
303
+ type: "button",
304
+ className: "puzzle-color-picker__swatch puzzle-color-picker__clear",
305
+ "aria-label": clearLabel,
306
+ "aria-pressed": selectedColor === void 0,
307
+ title: clearLabel,
308
+ onClick: clearColor,
309
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true" })
310
+ }), colors.map((swatch, index) => {
311
+ const color = getSwatchValue(swatch);
312
+ const swatchLabel = getSwatchLabel(swatch);
313
+ return /* @__PURE__ */ jsx("button", {
314
+ type: "button",
315
+ className: "puzzle-color-picker__swatch",
316
+ style: { background: resolveCssColor(color) ?? color },
317
+ "aria-label": swatchLabel,
318
+ "aria-pressed": selectedColor === color,
319
+ title: swatchLabel,
320
+ "data-selected": selectedColor === color || void 0,
321
+ "data-dark": isColorDark(color) || void 0,
322
+ onClick: () => selectSwatch(color)
323
+ }, `${color}-${index}`);
324
+ })]
325
+ })]
326
+ })
327
+ ]
328
+ })
329
+ }),
330
+ description && /* @__PURE__ */ jsx("span", {
331
+ id: descriptionId,
332
+ className: "puzzle-color-picker__description",
333
+ children: description
334
+ })
335
+ ]
336
+ });
337
+ };
338
+ export { ColorPicker };
339
+
340
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/forms/color-picker/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type PointerEvent,\n} from \"react\";\n\nimport { clamp, cn, isColorDark } from \"@puzzle-ui/core\";\n\nimport { Popout } from \"../../overlays/popout\";\n\nimport {\n DEFAULT_HSV,\n hsvToFormat,\n parseColorToHsvInBrowser,\n resolveCssColor,\n type HSVColor,\n} from \"./color-picker.utils\";\n\nimport type {\n ColorPickerFormat,\n ColorPickerProps,\n ColorPickerSwatch,\n} from \"./color-picker.types\";\n\nimport \"./color-picker.css\";\n\nconst getSwatchValue = (swatch: ColorPickerSwatch): string =>\n typeof swatch === \"string\" ? swatch : swatch.value;\n\nconst getSwatchLabel = (swatch: ColorPickerSwatch): string =>\n typeof swatch === \"string\" ? swatch : (swatch.label ?? swatch.value);\n\nconst ColorPicker = (props: ColorPickerProps) => {\n const {\n value,\n defaultValue,\n onChange,\n label,\n description,\n placeholder = \"Select color\",\n colors = [],\n format = \"hexa\",\n clearable = true,\n clearLabel = \"Clear color\",\n open,\n defaultOpen = false,\n onOpenChange,\n position = \"bottom\",\n align = \"start\",\n disabled = false,\n className,\n style,\n rootClassName,\n popoverClassName,\n id,\n ref,\n ...triggerProps\n } = props;\n\n const generatedId = useId();\n\n const triggerId = id ?? generatedId;\n const labelId = `${generatedId}-label`;\n const descriptionId = `${generatedId}-description`;\n const panelId = `${generatedId}-panel`;\n const areaId = `${generatedId}-area`;\n\n const [internalValue, setInternalValue] = useState<string | undefined>(\n defaultValue,\n );\n\n const isControlled = Object.prototype.hasOwnProperty.call(props, \"value\");\n\n const selectedColor = isControlled ? value : internalValue;\n\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const isOpen = open ?? internalOpen;\n\n const [hsv, setHsv] = useState<HSVColor>(\n () =>\n (selectedColor ? parseColorToHsvInBrowser(selectedColor) : null) ??\n DEFAULT_HSV,\n );\n\n const [inputValue, setInputValue] = useState(selectedColor ?? \"\");\n const [inputError, setInputError] = useState(false);\n\n const areaRef = useRef<HTMLDivElement>(null);\n\n const changeOpen = useCallback(\n (nextOpen: boolean) => {\n if (open === undefined) setInternalOpen(nextOpen);\n\n onOpenChange?.(nextOpen);\n },\n [open, onOpenChange],\n );\n\n const commitColor = useCallback(\n (nextColor: string | undefined) => {\n if (!isControlled) {\n setInternalValue(nextColor);\n }\n\n onChange?.(nextColor);\n\n setInputValue(nextColor ?? \"\");\n setInputError(false);\n },\n [isControlled, onChange],\n );\n\n const updateHsv = useCallback(\n (next: HSVColor) => {\n const normalized: HSVColor = {\n h: ((next.h % 360) + 360) % 360,\n s: clamp(next.s, 0, 100),\n v: clamp(next.v, 0, 100),\n a: clamp(next.a, 0, 1),\n };\n\n setHsv(normalized);\n commitColor(hsvToFormat(normalized, format));\n },\n [commitColor, format],\n );\n\n useEffect(() => {\n if (!selectedColor) {\n setHsv(DEFAULT_HSV);\n return;\n }\n\n const parsed = parseColorToHsvInBrowser(selectedColor);\n\n if (parsed) setHsv(parsed);\n }, [selectedColor]);\n\n useEffect(() => {\n if (!isOpen) {\n setInputValue(selectedColor ?? \"\");\n setInputError(false);\n }\n }, [isOpen, selectedColor]);\n\n const updateArea = (event: PointerEvent<HTMLDivElement>) => {\n if (event.type === \"pointermove\" && event.buttons !== 1) return;\n\n const rect = event.currentTarget.getBoundingClientRect();\n\n if (rect.width <= 0 || rect.height <= 0) return;\n\n const saturation = clamp(\n ((event.clientX - rect.left) / rect.width) * 100,\n 0,\n 100,\n );\n\n const brightness = clamp(\n (1 - (event.clientY - rect.top) / rect.height) * 100,\n 0,\n 100,\n );\n\n updateHsv({\n ...hsv,\n s: saturation,\n v: brightness,\n });\n };\n\n const handleAreaKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const step = event.shiftKey ? 10 : 1;\n\n let saturation = hsv.s;\n let brightness = hsv.v;\n\n switch (event.key) {\n case \"ArrowLeft\":\n saturation -= step;\n break;\n case \"ArrowRight\":\n saturation += step;\n break;\n case \"ArrowUp\":\n brightness += step;\n break;\n case \"ArrowDown\":\n brightness -= step;\n break;\n case \"Home\":\n saturation = 0;\n break;\n case \"End\":\n saturation = 100;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n\n updateHsv({\n ...hsv,\n s: saturation,\n v: brightness,\n });\n };\n\n const selectSwatch = (color: string) => {\n commitColor(color);\n\n const parsed = parseColorToHsvInBrowser(color);\n\n if (parsed) setHsv(parsed);\n };\n\n const applyInput = () => {\n const color = inputValue.trim();\n\n const parsed = parseColorToHsvInBrowser(color);\n\n if (!parsed) {\n setInputError(true);\n return;\n }\n\n commitColor(color);\n setHsv(parsed);\n };\n\n const hueColor = `hsl(${hsv.h}, 100%, 50%)`;\n\n const colorAreaStyle = {\n \"--puzzle-color-picker-hue\": hueColor,\n } as CSSProperties;\n\n const currentColor = selectedColor\n ? (resolveCssColor(selectedColor) ?? selectedColor)\n : undefined;\n\n const triggerSwatchStyle = {\n background: currentColor,\n } as CSSProperties;\n\n const clearColor = () => {\n commitColor(undefined);\n setHsv(DEFAULT_HSV);\n };\n\n return (\n <div\n className={cn(\"puzzle-color-picker\", rootClassName)}\n data-disabled={disabled || undefined}\n >\n {label && (\n <label\n id={labelId}\n className=\"puzzle-color-picker__label\"\n htmlFor={triggerId}\n >\n {label}\n </label>\n )}\n\n <Popout\n position={position}\n align={align}\n action=\"click\"\n open={isOpen}\n onOpenChange={changeOpen}\n closeOnOutsideClick\n closeOnEscape\n contentClassName={cn(\"puzzle-color-picker__popover\", popoverClassName)}\n trigger={\n <button\n {...triggerProps}\n ref={ref}\n id={triggerId}\n type=\"button\"\n className={cn(\"puzzle-color-picker__trigger\", className)}\n style={style}\n disabled={disabled}\n aria-label={label ? undefined : \"Choose color\"}\n aria-labelledby={label ? labelId : undefined}\n aria-describedby={description ? descriptionId : undefined}\n aria-haspopup=\"dialog\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? panelId : undefined}\n >\n <span\n className=\"puzzle-color-picker__trigger-swatch\"\n style={triggerSwatchStyle}\n aria-hidden=\"true\"\n />\n\n <span className=\"puzzle-color-picker__trigger-value\">\n {selectedColor ?? placeholder}\n </span>\n\n <span\n className=\"puzzle-color-picker__trigger-chevron\"\n aria-hidden=\"true\"\n >\n ▾\n </span>\n </button>\n }\n >\n <div\n id={panelId}\n className=\"puzzle-color-picker__panel\"\n role=\"dialog\"\n aria-label={label ? `Choose ${label}` : \"Choose color\"}\n >\n <div\n ref={areaRef}\n id={areaId}\n className=\"puzzle-color-picker__area\"\n style={colorAreaStyle}\n onPointerDown={(event) => {\n event.currentTarget.setPointerCapture(event.pointerId);\n updateArea(event);\n }}\n onPointerMove={updateArea}\n onKeyDown={handleAreaKeyDown}\n role=\"group\"\n aria-label=\"Color saturation and brightness\"\n tabIndex={0}\n >\n <span\n className=\"puzzle-color-picker__area-saturation\"\n aria-hidden=\"true\"\n />\n\n <span\n className=\"puzzle-color-picker__area-brightness\"\n aria-hidden=\"true\"\n />\n\n <span\n className=\"puzzle-color-picker__area-indicator\"\n style={{\n left: `${hsv.s}%`,\n top: `${100 - hsv.v}%`,\n }}\n aria-hidden=\"true\"\n />\n </div>\n\n <div className=\"puzzle-color-picker__sliders\">\n <label className=\"puzzle-color-picker__slider-row\">\n <span>Hue</span>\n\n <input\n type=\"range\"\n min={0}\n max={359}\n value={Math.round(hsv.h)}\n aria-label=\"Hue\"\n onChange={(event) =>\n updateHsv({\n ...hsv,\n h: Number(event.currentTarget.value),\n })\n }\n className=\"puzzle-color-picker__hue-slider\"\n />\n </label>\n\n <label className=\"puzzle-color-picker__slider-row\">\n <span>Opacity</span>\n\n <input\n type=\"range\"\n min={0}\n max={100}\n value={Math.round(hsv.a * 100)}\n aria-label=\"Opacity\"\n onChange={(event) =>\n updateHsv({\n ...hsv,\n a: Number(event.currentTarget.value) / 100,\n })\n }\n className=\"puzzle-color-picker__opacity-slider\"\n style={\n {\n \"--puzzle-color-picker-hue\": hueColor,\n } as CSSProperties\n }\n />\n </label>\n </div>\n\n <div className=\"puzzle-color-picker__input-section\">\n <label\n htmlFor={`${generatedId}-input`}\n className=\"puzzle-color-picker__input-label\"\n >\n Color value\n </label>\n\n <div className=\"puzzle-color-picker__input-row\">\n <input\n id={`${generatedId}-input`}\n type=\"text\"\n value={inputValue}\n spellCheck={false}\n autoComplete=\"off\"\n aria-label=\"Color value\"\n aria-invalid={inputError || undefined}\n placeholder=\"#RRGGBB\"\n onChange={(event) => {\n setInputValue(event.currentTarget.value);\n setInputError(false);\n }}\n onKeyDown={(event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n applyInput();\n }\n }}\n />\n\n <button\n type=\"button\"\n className=\"puzzle-color-picker__apply\"\n onClick={applyInput}\n >\n Apply\n </button>\n </div>\n\n {inputError && (\n <p className=\"puzzle-color-picker__error\" role=\"alert\">\n Enter a valid CSS color.\n </p>\n )}\n </div>\n\n {(clearable || colors.length > 0) && (\n <div className=\"puzzle-color-picker__swatches-section\">\n <span className=\"puzzle-color-picker__section-label\">\n Presets\n </span>\n\n <div\n className=\"puzzle-color-picker__swatches\"\n role=\"group\"\n aria-label=\"Preset colors\"\n >\n {clearable && (\n <button\n type=\"button\"\n className=\"puzzle-color-picker__swatch puzzle-color-picker__clear\"\n aria-label={clearLabel}\n aria-pressed={selectedColor === undefined}\n title={clearLabel}\n onClick={clearColor}\n >\n <span aria-hidden=\"true\" />\n </button>\n )}\n\n {colors.map((swatch, index) => {\n const color = getSwatchValue(swatch);\n const swatchLabel = getSwatchLabel(swatch);\n\n return (\n <button\n key={`${color}-${index}`}\n type=\"button\"\n className=\"puzzle-color-picker__swatch\"\n style={{\n background: resolveCssColor(color) ?? color,\n }}\n aria-label={swatchLabel}\n aria-pressed={selectedColor === color}\n title={swatchLabel}\n data-selected={selectedColor === color || undefined}\n data-dark={isColorDark(color) || undefined}\n onClick={() => selectSwatch(color)}\n />\n );\n })}\n </div>\n </div>\n )}\n </div>\n </Popout>\n\n {description && (\n <span id={descriptionId} className=\"puzzle-color-picker__description\">\n {description}\n </span>\n )}\n </div>\n );\n};\n\nexport {\n ColorPicker,\n type ColorPickerProps,\n type ColorPickerFormat,\n type ColorPickerSwatch,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-upload.utils.cjs","names":[],"sources":["../../../../src/components/forms/file-upload/file-upload.utils.ts"],"sourcesContent":["import type {\n FileUploadRejection,\n FileUploadRejectionCode,\n} from \"./file-upload.types\";\n\ntype FileValidationOptions = {\n accept?: readonly string[];\n maxFileSize?: number;\n maxTotalSize?: number;\n maxFiles?: number;\n multiple?: boolean;\n};\n\ntype FileValidationResult = {\n accepted: File[];\n rejected: FileUploadRejection[];\n};\n\nconst formatFileSize = (bytes: number): string => {\n if (bytes === 0) return \"0 B\";\n\n if (!Number.isFinite(bytes) || bytes < 0) return \"0 B\";\n\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const index = Math.min(\n Math.floor(Math.log(bytes) / Math.log(1024)),\n units.length - 1,\n );\n\n const value = bytes / 1024 ** index;\n\n return `${value >= 10 || index === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[index]}`;\n};\n\nconst matchesMimeType = (\n fileType: string,\n acceptedTypes: readonly string[],\n): boolean => {\n const normalizedType = fileType.split(\";\")[0].trim().toLowerCase();\n\n if (!normalizedType) return false;\n\n return acceptedTypes.some((acceptedType) => {\n const accepted = acceptedType.trim().toLowerCase();\n\n if (!accepted) return false;\n\n if (accepted.endsWith(\"/*\")) {\n return normalizedType.startsWith(accepted.slice(0, -1));\n }\n\n return normalizedType === accepted;\n });\n};\n\nconst createRejection = (\n file: File,\n code: FileUploadRejectionCode,\n message: string,\n): FileUploadRejection => ({\n file,\n code,\n message,\n});\n\nconst validateFile = (\n file: File,\n options: Pick<FileValidationOptions, \"accept\" | \"maxFileSize\">,\n): FileUploadRejection | undefined => {\n const { accept = [], maxFileSize } = options;\n\n if (accept.length > 0 && !matchesMimeType(file.type, accept)) {\n return createRejection(file, \"file-type\", \"This file type is not allowed.\");\n }\n\n if (maxFileSize !== undefined && file.size > maxFileSize) {\n return createRejection(\n file,\n \"file-too-large\",\n `File exceeds the maximum size of ${formatFileSize(maxFileSize)}.`,\n );\n }\n\n return undefined;\n};\n\nconst validateFiles = (\n incomingFiles: readonly File[],\n existingFiles: readonly File[],\n options: FileValidationOptions = {},\n): FileValidationResult => {\n const { multiple = false, maxFiles, maxTotalSize } = options;\n\n const accepted: File[] = [];\n const rejected: FileUploadRejection[] = [];\n\n const currentFiles = multiple ? [...existingFiles] : [];\n\n let totalSize = currentFiles.reduce((total, file) => total + file.size, 0);\n\n let fileCount = currentFiles.length;\n\n incomingFiles.forEach((file) => {\n const validationError = validateFile(file, options);\n\n if (validationError) {\n rejected.push(validationError);\n return;\n }\n\n if (!multiple && accepted.length > 0) {\n rejected.push(\n createRejection(\n file,\n \"too-many-files\",\n \"Only one file can be selected.\",\n ),\n );\n return;\n }\n\n if (multiple && maxFiles !== undefined && fileCount >= maxFiles) {\n rejected.push(\n createRejection(\n file,\n \"too-many-files\",\n `You can select a maximum of ${maxFiles} files.`,\n ),\n );\n return;\n }\n\n if (maxTotalSize !== undefined && totalSize + file.size > maxTotalSize) {\n rejected.push(\n createRejection(\n file,\n \"total-too-large\",\n `The combined file size exceeds ${formatFileSize(maxTotalSize)}.`,\n ),\n );\n return;\n }\n\n accepted.push(file);\n\n totalSize += file.size;\n fileCount++;\n });\n\n return { accepted, rejected };\n};\n\nexport {\n formatFileSize,\n matchesMimeType,\n createRejection,\n validateFile,\n validateFiles,\n};\n\nexport type { FileValidationOptions, FileValidationResult };\n"],"mappings":";AAkBA,MAAM,kBAAkB,UAA0B;CAChD,IAAI,UAAU,GAAG,OAAO;CAExB,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CAEjD,MAAM,QAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;CAAI;CAE1C,MAAM,QAAQ,KAAK,IACjB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC,GAC3C,MAAM,SAAS,CACjB;CAEA,MAAM,QAAQ,QAAQ,QAAQ;CAE9B,OAAO,GAAG,SAAS,MAAM,UAAU,IAAI,MAAM,QAAQ,CAAC,IAAI,MAAM,QAAQ,CAAC,EAAE,GAAG,MAAM;AACtF;AAEA,MAAM,mBACJ,UACA,kBACY;CACZ,MAAM,iBAAiB,SAAS,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,YAAY;CAEjE,IAAI,CAAC,gBAAgB,OAAO;CAE5B,OAAO,cAAc,MAAM,iBAAiB;EAC1C,MAAM,WAAW,aAAa,KAAK,CAAC,CAAC,YAAY;EAEjD,IAAI,CAAC,UAAU,OAAO;EAEtB,IAAI,SAAS,SAAS,IAAI,GACxB,OAAO,eAAe,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;EAGxD,OAAO,mBAAmB;CAC5B,CAAC;AACH;AAEA,MAAM,mBACJ,MACA,MACA,aACyB;CACzB;CACA;CACA;AACF;AAEA,MAAM,gBACJ,MACA,YACoC;CACpC,MAAM,EAAE,SAAS,CAAC,GAAG,gBAAgB;CAErC,IAAI,OAAO,SAAS,KAAK,CAAC,gBAAgB,KAAK,MAAM,MAAM,GACzD,OAAO,gBAAgB,MAAM,aAAa,gCAAgC;CAG5E,IAAI,gBAAgB,KAAA,KAAa,KAAK,OAAO,aAC3C,OAAO,gBACL,MACA,kBACA,oCAAoC,eAAe,WAAW,EAAE,EAClE;AAIJ;AAEA,MAAM,iBACJ,eACA,eACA,UAAiC,CAAC,MACT;CACzB,MAAM,EAAE,WAAW,OAAO,UAAU,iBAAiB;CAErD,MAAM,WAAmB,CAAC;CAC1B,MAAM,WAAkC,CAAC;CAEzC,MAAM,eAAe,WAAW,CAAC,GAAG,aAAa,IAAI,CAAC;CAEtD,IAAI,YAAY,aAAa,QAAQ,OAAO,SAAS,QAAQ,KAAK,MAAM,CAAC;CAEzE,IAAI,YAAY,aAAa;CAE7B,cAAc,SAAS,SAAS;EAC9B,MAAM,kBAAkB,aAAa,MAAM,OAAO;EAElD,IAAI,iBAAiB;GACnB,SAAS,KAAK,eAAe;GAC7B;EACF;EAEA,IAAI,CAAC,YAAY,SAAS,SAAS,GAAG;GACpC,SAAS,KACP,gBACE,MACA,kBACA,gCACF,CACF;GACA;EACF;EAEA,IAAI,YAAY,aAAa,KAAA,KAAa,aAAa,UAAU;GAC/D,SAAS,KACP,gBACE,MACA,kBACA,+BAA+B,SAAS,QAC1C,CACF;GACA;EACF;EAEA,IAAI,iBAAiB,KAAA,KAAa,YAAY,KAAK,OAAO,cAAc;GACtE,SAAS,KACP,gBACE,MACA,mBACA,kCAAkC,eAAe,YAAY,EAAE,EACjE,CACF;GACA;EACF;EAEA,SAAS,KAAK,IAAI;EAElB,aAAa,KAAK;EAClB;CACF,CAAC;CAED,OAAO;EAAE;EAAU;CAAS;AAC9B"}
1
+ {"version":3,"file":"file-upload.utils.cjs","names":[],"sources":["../../../../src/components/forms/file-upload/file-upload.utils.ts"],"sourcesContent":["import type {\n FileUploadRejection,\n FileUploadRejectionCode,\n} from \"./file-upload.types\";\n\ntype FileValidationOptions = {\n accept?: readonly string[];\n maxFileSize?: number;\n maxTotalSize?: number;\n maxFiles?: number;\n multiple?: boolean;\n};\n\ntype FileValidationResult = {\n accepted: File[];\n rejected: FileUploadRejection[];\n};\n\nconst formatFileSize = (bytes: number): string => {\n if (bytes === 0) return \"0 B\";\n\n if (!Number.isFinite(bytes) || bytes < 0) return \"0 B\";\n\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const index = Math.min(\n Math.floor(Math.log(bytes) / Math.log(1024)),\n units.length - 1,\n );\n\n const value = bytes / 1024 ** index;\n\n return `${value >= 10 || index === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[index]}`;\n};\n\nconst matchesMimeType = (\n fileType: string,\n acceptedTypes: readonly string[],\n): boolean => {\n const normalizedType = fileType.split(\";\")[0]!.trim().toLowerCase();\n\n if (!normalizedType) return false;\n\n return acceptedTypes.some((acceptedType) => {\n const accepted = acceptedType.trim().toLowerCase();\n\n if (!accepted) return false;\n\n if (accepted.endsWith(\"/*\")) {\n return normalizedType.startsWith(accepted.slice(0, -1));\n }\n\n return normalizedType === accepted;\n });\n};\n\nconst createRejection = (\n file: File,\n code: FileUploadRejectionCode,\n message: string,\n): FileUploadRejection => ({\n file,\n code,\n message,\n});\n\nconst validateFile = (\n file: File,\n options: Pick<FileValidationOptions, \"accept\" | \"maxFileSize\">,\n): FileUploadRejection | undefined => {\n const { accept = [], maxFileSize } = options;\n\n if (accept.length > 0 && !matchesMimeType(file.type, accept)) {\n return createRejection(file, \"file-type\", \"This file type is not allowed.\");\n }\n\n if (maxFileSize !== undefined && file.size > maxFileSize) {\n return createRejection(\n file,\n \"file-too-large\",\n `File exceeds the maximum size of ${formatFileSize(maxFileSize)}.`,\n );\n }\n\n return undefined;\n};\n\nconst validateFiles = (\n incomingFiles: readonly File[],\n existingFiles: readonly File[],\n options: FileValidationOptions = {},\n): FileValidationResult => {\n const { multiple = false, maxFiles, maxTotalSize } = options;\n\n const accepted: File[] = [];\n const rejected: FileUploadRejection[] = [];\n\n const currentFiles = multiple ? [...existingFiles] : [];\n\n let totalSize = currentFiles.reduce((total, file) => total + file.size, 0);\n\n let fileCount = currentFiles.length;\n\n incomingFiles.forEach((file) => {\n const validationError = validateFile(file, options);\n\n if (validationError) {\n rejected.push(validationError);\n return;\n }\n\n if (!multiple && accepted.length > 0) {\n rejected.push(\n createRejection(\n file,\n \"too-many-files\",\n \"Only one file can be selected.\",\n ),\n );\n return;\n }\n\n if (multiple && maxFiles !== undefined && fileCount >= maxFiles) {\n rejected.push(\n createRejection(\n file,\n \"too-many-files\",\n `You can select a maximum of ${maxFiles} files.`,\n ),\n );\n return;\n }\n\n if (maxTotalSize !== undefined && totalSize + file.size > maxTotalSize) {\n rejected.push(\n createRejection(\n file,\n \"total-too-large\",\n `The combined file size exceeds ${formatFileSize(maxTotalSize)}.`,\n ),\n );\n return;\n }\n\n accepted.push(file);\n\n totalSize += file.size;\n fileCount++;\n });\n\n return { accepted, rejected };\n};\n\nexport {\n formatFileSize,\n matchesMimeType,\n createRejection,\n validateFile,\n validateFiles,\n};\n\nexport type { FileValidationOptions, FileValidationResult };\n"],"mappings":";AAkBA,MAAM,kBAAkB,UAA0B;CAChD,IAAI,UAAU,GAAG,OAAO;CAExB,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CAEjD,MAAM,QAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;CAAI;CAE1C,MAAM,QAAQ,KAAK,IACjB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC,GAC3C,MAAM,SAAS,CACjB;CAEA,MAAM,QAAQ,QAAQ,QAAQ;CAE9B,OAAO,GAAG,SAAS,MAAM,UAAU,IAAI,MAAM,QAAQ,CAAC,IAAI,MAAM,QAAQ,CAAC,EAAE,GAAG,MAAM;AACtF;AAEA,MAAM,mBACJ,UACA,kBACY;CACZ,MAAM,iBAAiB,SAAS,MAAM,GAAG,CAAC,CAAC,EAAE,CAAE,KAAK,CAAC,CAAC,YAAY;CAElE,IAAI,CAAC,gBAAgB,OAAO;CAE5B,OAAO,cAAc,MAAM,iBAAiB;EAC1C,MAAM,WAAW,aAAa,KAAK,CAAC,CAAC,YAAY;EAEjD,IAAI,CAAC,UAAU,OAAO;EAEtB,IAAI,SAAS,SAAS,IAAI,GACxB,OAAO,eAAe,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;EAGxD,OAAO,mBAAmB;CAC5B,CAAC;AACH;AAEA,MAAM,mBACJ,MACA,MACA,aACyB;CACzB;CACA;CACA;AACF;AAEA,MAAM,gBACJ,MACA,YACoC;CACpC,MAAM,EAAE,SAAS,CAAC,GAAG,gBAAgB;CAErC,IAAI,OAAO,SAAS,KAAK,CAAC,gBAAgB,KAAK,MAAM,MAAM,GACzD,OAAO,gBAAgB,MAAM,aAAa,gCAAgC;CAG5E,IAAI,gBAAgB,KAAA,KAAa,KAAK,OAAO,aAC3C,OAAO,gBACL,MACA,kBACA,oCAAoC,eAAe,WAAW,EAAE,EAClE;AAIJ;AAEA,MAAM,iBACJ,eACA,eACA,UAAiC,CAAC,MACT;CACzB,MAAM,EAAE,WAAW,OAAO,UAAU,iBAAiB;CAErD,MAAM,WAAmB,CAAC;CAC1B,MAAM,WAAkC,CAAC;CAEzC,MAAM,eAAe,WAAW,CAAC,GAAG,aAAa,IAAI,CAAC;CAEtD,IAAI,YAAY,aAAa,QAAQ,OAAO,SAAS,QAAQ,KAAK,MAAM,CAAC;CAEzE,IAAI,YAAY,aAAa;CAE7B,cAAc,SAAS,SAAS;EAC9B,MAAM,kBAAkB,aAAa,MAAM,OAAO;EAElD,IAAI,iBAAiB;GACnB,SAAS,KAAK,eAAe;GAC7B;EACF;EAEA,IAAI,CAAC,YAAY,SAAS,SAAS,GAAG;GACpC,SAAS,KACP,gBACE,MACA,kBACA,gCACF,CACF;GACA;EACF;EAEA,IAAI,YAAY,aAAa,KAAA,KAAa,aAAa,UAAU;GAC/D,SAAS,KACP,gBACE,MACA,kBACA,+BAA+B,SAAS,QAC1C,CACF;GACA;EACF;EAEA,IAAI,iBAAiB,KAAA,KAAa,YAAY,KAAK,OAAO,cAAc;GACtE,SAAS,KACP,gBACE,MACA,mBACA,kCAAkC,eAAe,YAAY,EAAE,EACjE,CACF;GACA;EACF;EAEA,SAAS,KAAK,IAAI;EAElB,aAAa,KAAK;EAClB;CACF,CAAC;CAED,OAAO;EAAE;EAAU;CAAS;AAC9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-upload.utils.js","names":[],"sources":["../../../../src/components/forms/file-upload/file-upload.utils.ts"],"sourcesContent":["import type {\n FileUploadRejection,\n FileUploadRejectionCode,\n} from \"./file-upload.types\";\n\ntype FileValidationOptions = {\n accept?: readonly string[];\n maxFileSize?: number;\n maxTotalSize?: number;\n maxFiles?: number;\n multiple?: boolean;\n};\n\ntype FileValidationResult = {\n accepted: File[];\n rejected: FileUploadRejection[];\n};\n\nconst formatFileSize = (bytes: number): string => {\n if (bytes === 0) return \"0 B\";\n\n if (!Number.isFinite(bytes) || bytes < 0) return \"0 B\";\n\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const index = Math.min(\n Math.floor(Math.log(bytes) / Math.log(1024)),\n units.length - 1,\n );\n\n const value = bytes / 1024 ** index;\n\n return `${value >= 10 || index === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[index]}`;\n};\n\nconst matchesMimeType = (\n fileType: string,\n acceptedTypes: readonly string[],\n): boolean => {\n const normalizedType = fileType.split(\";\")[0].trim().toLowerCase();\n\n if (!normalizedType) return false;\n\n return acceptedTypes.some((acceptedType) => {\n const accepted = acceptedType.trim().toLowerCase();\n\n if (!accepted) return false;\n\n if (accepted.endsWith(\"/*\")) {\n return normalizedType.startsWith(accepted.slice(0, -1));\n }\n\n return normalizedType === accepted;\n });\n};\n\nconst createRejection = (\n file: File,\n code: FileUploadRejectionCode,\n message: string,\n): FileUploadRejection => ({\n file,\n code,\n message,\n});\n\nconst validateFile = (\n file: File,\n options: Pick<FileValidationOptions, \"accept\" | \"maxFileSize\">,\n): FileUploadRejection | undefined => {\n const { accept = [], maxFileSize } = options;\n\n if (accept.length > 0 && !matchesMimeType(file.type, accept)) {\n return createRejection(file, \"file-type\", \"This file type is not allowed.\");\n }\n\n if (maxFileSize !== undefined && file.size > maxFileSize) {\n return createRejection(\n file,\n \"file-too-large\",\n `File exceeds the maximum size of ${formatFileSize(maxFileSize)}.`,\n );\n }\n\n return undefined;\n};\n\nconst validateFiles = (\n incomingFiles: readonly File[],\n existingFiles: readonly File[],\n options: FileValidationOptions = {},\n): FileValidationResult => {\n const { multiple = false, maxFiles, maxTotalSize } = options;\n\n const accepted: File[] = [];\n const rejected: FileUploadRejection[] = [];\n\n const currentFiles = multiple ? [...existingFiles] : [];\n\n let totalSize = currentFiles.reduce((total, file) => total + file.size, 0);\n\n let fileCount = currentFiles.length;\n\n incomingFiles.forEach((file) => {\n const validationError = validateFile(file, options);\n\n if (validationError) {\n rejected.push(validationError);\n return;\n }\n\n if (!multiple && accepted.length > 0) {\n rejected.push(\n createRejection(\n file,\n \"too-many-files\",\n \"Only one file can be selected.\",\n ),\n );\n return;\n }\n\n if (multiple && maxFiles !== undefined && fileCount >= maxFiles) {\n rejected.push(\n createRejection(\n file,\n \"too-many-files\",\n `You can select a maximum of ${maxFiles} files.`,\n ),\n );\n return;\n }\n\n if (maxTotalSize !== undefined && totalSize + file.size > maxTotalSize) {\n rejected.push(\n createRejection(\n file,\n \"total-too-large\",\n `The combined file size exceeds ${formatFileSize(maxTotalSize)}.`,\n ),\n );\n return;\n }\n\n accepted.push(file);\n\n totalSize += file.size;\n fileCount++;\n });\n\n return { accepted, rejected };\n};\n\nexport {\n formatFileSize,\n matchesMimeType,\n createRejection,\n validateFile,\n validateFiles,\n};\n\nexport type { FileValidationOptions, FileValidationResult };\n"],"mappings":";AAkBA,MAAM,kBAAkB,UAA0B;CAChD,IAAI,UAAU,GAAG,OAAO;CAExB,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CAEjD,MAAM,QAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;CAAI;CAE1C,MAAM,QAAQ,KAAK,IACjB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC,GAC3C,MAAM,SAAS,CACjB;CAEA,MAAM,QAAQ,QAAQ,QAAQ;CAE9B,OAAO,GAAG,SAAS,MAAM,UAAU,IAAI,MAAM,QAAQ,CAAC,IAAI,MAAM,QAAQ,CAAC,EAAE,GAAG,MAAM;AACtF;AAEA,MAAM,mBACJ,UACA,kBACY;CACZ,MAAM,iBAAiB,SAAS,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,YAAY;CAEjE,IAAI,CAAC,gBAAgB,OAAO;CAE5B,OAAO,cAAc,MAAM,iBAAiB;EAC1C,MAAM,WAAW,aAAa,KAAK,CAAC,CAAC,YAAY;EAEjD,IAAI,CAAC,UAAU,OAAO;EAEtB,IAAI,SAAS,SAAS,IAAI,GACxB,OAAO,eAAe,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;EAGxD,OAAO,mBAAmB;CAC5B,CAAC;AACH;AAEA,MAAM,mBACJ,MACA,MACA,aACyB;CACzB;CACA;CACA;AACF;AAEA,MAAM,gBACJ,MACA,YACoC;CACpC,MAAM,EAAE,SAAS,CAAC,GAAG,gBAAgB;CAErC,IAAI,OAAO,SAAS,KAAK,CAAC,gBAAgB,KAAK,MAAM,MAAM,GACzD,OAAO,gBAAgB,MAAM,aAAa,gCAAgC;CAG5E,IAAI,gBAAgB,KAAA,KAAa,KAAK,OAAO,aAC3C,OAAO,gBACL,MACA,kBACA,oCAAoC,eAAe,WAAW,EAAE,EAClE;AAIJ;AAEA,MAAM,iBACJ,eACA,eACA,UAAiC,CAAC,MACT;CACzB,MAAM,EAAE,WAAW,OAAO,UAAU,iBAAiB;CAErD,MAAM,WAAmB,CAAC;CAC1B,MAAM,WAAkC,CAAC;CAEzC,MAAM,eAAe,WAAW,CAAC,GAAG,aAAa,IAAI,CAAC;CAEtD,IAAI,YAAY,aAAa,QAAQ,OAAO,SAAS,QAAQ,KAAK,MAAM,CAAC;CAEzE,IAAI,YAAY,aAAa;CAE7B,cAAc,SAAS,SAAS;EAC9B,MAAM,kBAAkB,aAAa,MAAM,OAAO;EAElD,IAAI,iBAAiB;GACnB,SAAS,KAAK,eAAe;GAC7B;EACF;EAEA,IAAI,CAAC,YAAY,SAAS,SAAS,GAAG;GACpC,SAAS,KACP,gBACE,MACA,kBACA,gCACF,CACF;GACA;EACF;EAEA,IAAI,YAAY,aAAa,KAAA,KAAa,aAAa,UAAU;GAC/D,SAAS,KACP,gBACE,MACA,kBACA,+BAA+B,SAAS,QAC1C,CACF;GACA;EACF;EAEA,IAAI,iBAAiB,KAAA,KAAa,YAAY,KAAK,OAAO,cAAc;GACtE,SAAS,KACP,gBACE,MACA,mBACA,kCAAkC,eAAe,YAAY,EAAE,EACjE,CACF;GACA;EACF;EAEA,SAAS,KAAK,IAAI;EAElB,aAAa,KAAK;EAClB;CACF,CAAC;CAED,OAAO;EAAE;EAAU;CAAS;AAC9B"}
1
+ {"version":3,"file":"file-upload.utils.js","names":[],"sources":["../../../../src/components/forms/file-upload/file-upload.utils.ts"],"sourcesContent":["import type {\n FileUploadRejection,\n FileUploadRejectionCode,\n} from \"./file-upload.types\";\n\ntype FileValidationOptions = {\n accept?: readonly string[];\n maxFileSize?: number;\n maxTotalSize?: number;\n maxFiles?: number;\n multiple?: boolean;\n};\n\ntype FileValidationResult = {\n accepted: File[];\n rejected: FileUploadRejection[];\n};\n\nconst formatFileSize = (bytes: number): string => {\n if (bytes === 0) return \"0 B\";\n\n if (!Number.isFinite(bytes) || bytes < 0) return \"0 B\";\n\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const index = Math.min(\n Math.floor(Math.log(bytes) / Math.log(1024)),\n units.length - 1,\n );\n\n const value = bytes / 1024 ** index;\n\n return `${value >= 10 || index === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[index]}`;\n};\n\nconst matchesMimeType = (\n fileType: string,\n acceptedTypes: readonly string[],\n): boolean => {\n const normalizedType = fileType.split(\";\")[0]!.trim().toLowerCase();\n\n if (!normalizedType) return false;\n\n return acceptedTypes.some((acceptedType) => {\n const accepted = acceptedType.trim().toLowerCase();\n\n if (!accepted) return false;\n\n if (accepted.endsWith(\"/*\")) {\n return normalizedType.startsWith(accepted.slice(0, -1));\n }\n\n return normalizedType === accepted;\n });\n};\n\nconst createRejection = (\n file: File,\n code: FileUploadRejectionCode,\n message: string,\n): FileUploadRejection => ({\n file,\n code,\n message,\n});\n\nconst validateFile = (\n file: File,\n options: Pick<FileValidationOptions, \"accept\" | \"maxFileSize\">,\n): FileUploadRejection | undefined => {\n const { accept = [], maxFileSize } = options;\n\n if (accept.length > 0 && !matchesMimeType(file.type, accept)) {\n return createRejection(file, \"file-type\", \"This file type is not allowed.\");\n }\n\n if (maxFileSize !== undefined && file.size > maxFileSize) {\n return createRejection(\n file,\n \"file-too-large\",\n `File exceeds the maximum size of ${formatFileSize(maxFileSize)}.`,\n );\n }\n\n return undefined;\n};\n\nconst validateFiles = (\n incomingFiles: readonly File[],\n existingFiles: readonly File[],\n options: FileValidationOptions = {},\n): FileValidationResult => {\n const { multiple = false, maxFiles, maxTotalSize } = options;\n\n const accepted: File[] = [];\n const rejected: FileUploadRejection[] = [];\n\n const currentFiles = multiple ? [...existingFiles] : [];\n\n let totalSize = currentFiles.reduce((total, file) => total + file.size, 0);\n\n let fileCount = currentFiles.length;\n\n incomingFiles.forEach((file) => {\n const validationError = validateFile(file, options);\n\n if (validationError) {\n rejected.push(validationError);\n return;\n }\n\n if (!multiple && accepted.length > 0) {\n rejected.push(\n createRejection(\n file,\n \"too-many-files\",\n \"Only one file can be selected.\",\n ),\n );\n return;\n }\n\n if (multiple && maxFiles !== undefined && fileCount >= maxFiles) {\n rejected.push(\n createRejection(\n file,\n \"too-many-files\",\n `You can select a maximum of ${maxFiles} files.`,\n ),\n );\n return;\n }\n\n if (maxTotalSize !== undefined && totalSize + file.size > maxTotalSize) {\n rejected.push(\n createRejection(\n file,\n \"total-too-large\",\n `The combined file size exceeds ${formatFileSize(maxTotalSize)}.`,\n ),\n );\n return;\n }\n\n accepted.push(file);\n\n totalSize += file.size;\n fileCount++;\n });\n\n return { accepted, rejected };\n};\n\nexport {\n formatFileSize,\n matchesMimeType,\n createRejection,\n validateFile,\n validateFiles,\n};\n\nexport type { FileValidationOptions, FileValidationResult };\n"],"mappings":";AAkBA,MAAM,kBAAkB,UAA0B;CAChD,IAAI,UAAU,GAAG,OAAO;CAExB,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CAEjD,MAAM,QAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;CAAI;CAE1C,MAAM,QAAQ,KAAK,IACjB,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC,GAC3C,MAAM,SAAS,CACjB;CAEA,MAAM,QAAQ,QAAQ,QAAQ;CAE9B,OAAO,GAAG,SAAS,MAAM,UAAU,IAAI,MAAM,QAAQ,CAAC,IAAI,MAAM,QAAQ,CAAC,EAAE,GAAG,MAAM;AACtF;AAEA,MAAM,mBACJ,UACA,kBACY;CACZ,MAAM,iBAAiB,SAAS,MAAM,GAAG,CAAC,CAAC,EAAE,CAAE,KAAK,CAAC,CAAC,YAAY;CAElE,IAAI,CAAC,gBAAgB,OAAO;CAE5B,OAAO,cAAc,MAAM,iBAAiB;EAC1C,MAAM,WAAW,aAAa,KAAK,CAAC,CAAC,YAAY;EAEjD,IAAI,CAAC,UAAU,OAAO;EAEtB,IAAI,SAAS,SAAS,IAAI,GACxB,OAAO,eAAe,WAAW,SAAS,MAAM,GAAG,EAAE,CAAC;EAGxD,OAAO,mBAAmB;CAC5B,CAAC;AACH;AAEA,MAAM,mBACJ,MACA,MACA,aACyB;CACzB;CACA;CACA;AACF;AAEA,MAAM,gBACJ,MACA,YACoC;CACpC,MAAM,EAAE,SAAS,CAAC,GAAG,gBAAgB;CAErC,IAAI,OAAO,SAAS,KAAK,CAAC,gBAAgB,KAAK,MAAM,MAAM,GACzD,OAAO,gBAAgB,MAAM,aAAa,gCAAgC;CAG5E,IAAI,gBAAgB,KAAA,KAAa,KAAK,OAAO,aAC3C,OAAO,gBACL,MACA,kBACA,oCAAoC,eAAe,WAAW,EAAE,EAClE;AAIJ;AAEA,MAAM,iBACJ,eACA,eACA,UAAiC,CAAC,MACT;CACzB,MAAM,EAAE,WAAW,OAAO,UAAU,iBAAiB;CAErD,MAAM,WAAmB,CAAC;CAC1B,MAAM,WAAkC,CAAC;CAEzC,MAAM,eAAe,WAAW,CAAC,GAAG,aAAa,IAAI,CAAC;CAEtD,IAAI,YAAY,aAAa,QAAQ,OAAO,SAAS,QAAQ,KAAK,MAAM,CAAC;CAEzE,IAAI,YAAY,aAAa;CAE7B,cAAc,SAAS,SAAS;EAC9B,MAAM,kBAAkB,aAAa,MAAM,OAAO;EAElD,IAAI,iBAAiB;GACnB,SAAS,KAAK,eAAe;GAC7B;EACF;EAEA,IAAI,CAAC,YAAY,SAAS,SAAS,GAAG;GACpC,SAAS,KACP,gBACE,MACA,kBACA,gCACF,CACF;GACA;EACF;EAEA,IAAI,YAAY,aAAa,KAAA,KAAa,aAAa,UAAU;GAC/D,SAAS,KACP,gBACE,MACA,kBACA,+BAA+B,SAAS,QAC1C,CACF;GACA;EACF;EAEA,IAAI,iBAAiB,KAAA,KAAa,YAAY,KAAK,OAAO,cAAc;GACtE,SAAS,KACP,gBACE,MACA,mBACA,kCAAkC,eAAe,YAAY,EAAE,EACjE,CACF;GACA;EACF;EAEA,SAAS,KAAK,IAAI;EAElB,aAAa,KAAK;EAClB;CACF,CAAC;CAED,OAAO;EAAE;EAAU;CAAS;AAC9B"}
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Icon } from "@puzzle-ui/icons";
7
7
  // src/components/forms/input/number-input.tsx
8
- const clamp = (value, min, max) => Math.min(max ?? Infinity, Math.max(min ?? -Infinity, value));
8
+ const clamp$1 = (value, min, max) => Math.min(max ?? Infinity, Math.max(min ?? -Infinity, value));
9
9
  const getPrecision = (value) => {
10
10
  const valueString = String(value);
11
11
  if (valueString.includes("e-")) return Number(valueString.split("e-")[1]) || 0;
@@ -18,8 +18,8 @@ const NumberInput = ({ value, initialValue = 0, onChange, min, max, step = 1, la
18
18
  const safeStep = Number.isFinite(step) && step > 0 ? step : 1;
19
19
  const safeLargeStep = largeStep !== void 0 && Number.isFinite(largeStep) && largeStep > 0 ? largeStep : safeStep * 10;
20
20
  const allowsNegative = min === void 0 || min < 0;
21
- const [internalValue, setInternalValue] = useState(() => clamp(Number.isFinite(initialValue) ? initialValue : 0, min, max));
22
- const currentValue = clamp(isControlled ? value : internalValue, min, max);
21
+ const [internalValue, setInternalValue] = useState(() => clamp$1(Number.isFinite(initialValue) ? initialValue : 0, min, max));
22
+ const currentValue = clamp$1(isControlled ? value : internalValue, min, max);
23
23
  const [draft, setDraft] = useState(() => String(Number(currentValue.toFixed(digits))));
24
24
  const currentValueRef = useRef(currentValue);
25
25
  const focusedRef = useRef(false);
@@ -32,7 +32,7 @@ const NumberInput = ({ value, initialValue = 0, onChange, min, max, step = 1, la
32
32
  if (!focusedRef.current) setDraft(String(Number(currentValue.toFixed(digits))));
33
33
  }, [currentValue, digits]);
34
34
  const commit = useCallback((nextValue) => {
35
- const rounded = Math.round(clamp(nextValue, min, max) * factor) / factor;
35
+ const rounded = Math.round(clamp$1(nextValue, min, max) * factor) / factor;
36
36
  if (!Number.isFinite(rounded) || rounded === currentValueRef.current) return;
37
37
  currentValueRef.current = rounded;
38
38
  if (!isControlled) setInternalValue(rounded);
@@ -119,7 +119,7 @@ const NumberInput = ({ value, initialValue = 0, onChange, min, max, step = 1, la
119
119
  const handleBlur = (event) => {
120
120
  focusedRef.current = false;
121
121
  const parsed = Number(draft.replace(",", "."));
122
- const nextValue = draft.trim() !== "" && Number.isFinite(parsed) ? clamp(Math.round(parsed * factor) / factor, min, max) : currentValueRef.current;
122
+ const nextValue = draft.trim() !== "" && Number.isFinite(parsed) ? clamp$1(Math.round(parsed * factor) / factor, min, max) : currentValueRef.current;
123
123
  commit(nextValue);
124
124
  setDraft(String(Number(nextValue.toFixed(digits))));
125
125
  onBlur?.(event);
@@ -1 +1 @@
1
- {"version":3,"file":"number-input.js","names":[],"sources":["../../../../src/components/forms/input/number-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type PointerEvent as ReactPointerEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type ChangeEvent,\n type FocusEvent,\n} from \"react\";\n\nimport { cn } from \"@puzzle-ui/core\";\nimport { Icon } from \"@puzzle-ui/icons\";\n\nimport \"./input.css\";\n\ntype NumberInputProps = Omit<\n ComponentPropsWithoutRef<\"input\">,\n \"type\" | \"value\" | \"defaultValue\" | \"onChange\" | \"step\" | \"min\" | \"max\"\n> & {\n value?: number;\n initialValue?: number;\n\n onChange?: (value: number) => void;\n\n min?: number;\n max?: number;\n step?: number;\n\n /** Amount applied by PageUp and PageDown. Defaults to ten times `step`. */\n largeStep?: number;\n\n fractionDigits?: number;\n\n longPressDelay?: number;\n repeatInterval?: number;\n\n /** Accessible label of the decrement button. */\n decrementLabel?: string;\n\n /** Accessible label of the increment button. */\n incrementLabel?: string;\n};\n\ntype Direction = -1 | 1;\n\nconst clamp = (value: number, min?: number, max?: number) =>\n Math.min(max ?? Infinity, Math.max(min ?? -Infinity, value));\n\nconst getPrecision = (value: number) => {\n const valueString = String(value);\n\n if (valueString.includes(\"e-\")) {\n return Number(valueString.split(\"e-\")[1]) || 0;\n }\n\n return valueString.split(\".\")[1]?.length ?? 0;\n};\n\nconst NumberInput = ({\n value,\n initialValue = 0,\n onChange,\n\n min,\n max,\n step = 1,\n largeStep,\n\n fractionDigits = getPrecision(step),\n\n longPressDelay = 450,\n repeatInterval = 120,\n\n decrementLabel = \"Decrease value\",\n incrementLabel = \"Increase value\",\n\n disabled,\n readOnly,\n className,\n\n onBlur,\n onFocus,\n onKeyDown,\n\n ...inputProps\n}: NumberInputProps) => {\n const isControlled = value !== undefined;\n\n const digits = Math.min(12, Math.max(0, Math.trunc(fractionDigits)));\n\n const factor = 10 ** digits;\n\n const safeStep = Number.isFinite(step) && step > 0 ? step : 1;\n\n const safeLargeStep =\n largeStep !== undefined && Number.isFinite(largeStep) && largeStep > 0\n ? largeStep\n : safeStep * 10;\n\n const allowsNegative = min === undefined || min < 0;\n\n const [internalValue, setInternalValue] = useState(() =>\n clamp(Number.isFinite(initialValue) ? initialValue : 0, min, max),\n );\n\n const currentValue = clamp(isControlled ? value : internalValue, min, max);\n\n const [draft, setDraft] = useState(() =>\n String(Number(currentValue.toFixed(digits))),\n );\n\n const currentValueRef = useRef(currentValue);\n const focusedRef = useRef(false);\n\n const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const repeatTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const holdStartedAtRef = useRef(0);\n const holdDirectionRef = useRef<Direction>(1);\n\n currentValueRef.current = currentValue;\n\n useEffect(() => {\n if (!focusedRef.current) {\n setDraft(String(Number(currentValue.toFixed(digits))));\n }\n }, [currentValue, digits]);\n\n const commit = useCallback(\n (nextValue: number) => {\n const rounded = Math.round(clamp(nextValue, min, max) * factor) / factor;\n\n if (!Number.isFinite(rounded) || rounded === currentValueRef.current) {\n return;\n }\n\n currentValueRef.current = rounded;\n\n if (!isControlled) {\n setInternalValue(rounded);\n }\n\n setDraft(String(rounded));\n\n onChange?.(rounded);\n },\n [factor, isControlled, max, min, onChange],\n );\n\n const changeBy = useCallback(\n (direction: Direction, amount = safeStep) => {\n const next =\n Math.round((currentValueRef.current + direction * amount) * factor) /\n factor;\n\n commit(next);\n },\n [commit, factor, safeStep],\n );\n\n const stopHold = useCallback(() => {\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n }\n\n if (repeatTimerRef.current) {\n clearTimeout(repeatTimerRef.current);\n }\n\n holdTimerRef.current = null;\n repeatTimerRef.current = null;\n }, []);\n\n const scheduleRepeat = useCallback(() => {\n const elapsed = Date.now() - holdStartedAtRef.current;\n\n const interval = Math.max(\n 45,\n repeatInterval - Math.floor(elapsed / 1800) * 20,\n );\n\n repeatTimerRef.current = setTimeout(() => {\n changeBy(holdDirectionRef.current);\n scheduleRepeat();\n }, interval);\n }, [changeBy, repeatInterval]);\n\n const startHold =\n (direction: Direction) => (event: ReactPointerEvent<HTMLButtonElement>) => {\n if (disabled || readOnly || event.button !== 0) {\n return;\n }\n\n event.preventDefault();\n\n stopHold();\n\n holdDirectionRef.current = direction;\n holdStartedAtRef.current = Date.now();\n\n changeBy(direction);\n\n holdTimerRef.current = setTimeout(scheduleRepeat, longPressDelay);\n };\n\n const handleStepperKeyDown =\n (direction: Direction) =>\n (event: ReactKeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== \" \" && event.key !== \"Enter\") {\n return;\n }\n\n event.preventDefault();\n changeBy(direction);\n };\n\n const handleInputKeyDown = (event: ReactKeyboardEvent<HTMLInputElement>) => {\n onKeyDown?.(event);\n\n if (event.defaultPrevented || disabled || readOnly) {\n return;\n }\n\n switch (event.key) {\n case \"ArrowUp\":\n changeBy(1);\n break;\n case \"ArrowDown\":\n changeBy(-1);\n break;\n case \"PageUp\":\n changeBy(1, safeLargeStep);\n break;\n case \"PageDown\":\n changeBy(-1, safeLargeStep);\n break;\n case \"Home\":\n if (min === undefined) return;\n commit(min);\n break;\n case \"End\":\n if (max === undefined) return;\n commit(max);\n break;\n default:\n return;\n }\n\n event.preventDefault();\n };\n\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {\n const nextDraft = event.target.value;\n\n setDraft(nextDraft);\n\n if (\n nextDraft.trim() === \"\" ||\n nextDraft === \"-\" ||\n nextDraft === \".\" ||\n nextDraft === \"-.\"\n ) {\n return;\n }\n\n const parsed = Number(nextDraft.replace(\",\", \".\"));\n\n if (Number.isFinite(parsed)) {\n commit(parsed);\n }\n };\n\n const handleBlur = (event: FocusEvent<HTMLInputElement>) => {\n focusedRef.current = false;\n\n const parsed = Number(draft.replace(\",\", \".\"));\n\n const nextValue =\n draft.trim() !== \"\" && Number.isFinite(parsed)\n ? clamp(Math.round(parsed * factor) / factor, min, max)\n : currentValueRef.current;\n\n commit(nextValue);\n\n setDraft(String(Number(nextValue.toFixed(digits))));\n\n onBlur?.(event);\n };\n\n useEffect(() => stopHold, [stopHold]);\n\n const atMinimum = min !== undefined && currentValue <= min;\n\n const atMaximum = max !== undefined && currentValue >= max;\n\n const trimmedDraft = draft.trim();\n const draftIsIncomplete =\n trimmedDraft === \"\" ||\n trimmedDraft === \"-\" ||\n trimmedDraft === \".\" ||\n trimmedDraft === \"-.\";\n const draftValue = Number(trimmedDraft.replace(\",\", \".\"));\n\n const draftIsInvalid =\n !draftIsIncomplete &&\n (!Number.isFinite(draftValue) ||\n (min !== undefined && draftValue < min) ||\n (max !== undefined && draftValue > max));\n\n const stepperDisabled = disabled || readOnly;\n\n return (\n <div\n className={cn(\"puzzle-input\", \"puzzle-input--number\", className)}\n data-disabled={disabled || undefined}\n >\n <button\n type=\"button\"\n className=\"puzzle-input__stepper\"\n aria-label={decrementLabel}\n tabIndex={-1}\n disabled={stepperDisabled || atMinimum}\n onPointerDown={startHold(-1)}\n onPointerUp={stopHold}\n onPointerLeave={stopHold}\n onPointerCancel={stopHold}\n onKeyDown={handleStepperKeyDown(-1)}\n >\n <Icon name=\"lucide:minus\" />\n </button>\n\n <input\n {...inputProps}\n type=\"text\"\n role=\"spinbutton\"\n aria-valuenow={currentValue}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-invalid={draftIsInvalid || inputProps[\"aria-invalid\"]}\n inputMode={allowsNegative ? \"text\" : digits > 0 ? \"decimal\" : \"numeric\"}\n pattern={`${allowsNegative ? \"-?\" : \"\"}[0-9]*${digits > 0 ? \"[.,]?[0-9]*\" : \"\"}`}\n disabled={disabled}\n readOnly={readOnly}\n value={draft}\n className=\"puzzle-input__control\"\n onFocus={(event) => {\n focusedRef.current = true;\n onFocus?.(event);\n }}\n onChange={handleInputChange}\n onKeyDown={handleInputKeyDown}\n onBlur={handleBlur}\n />\n\n <button\n type=\"button\"\n className=\"puzzle-input__stepper\"\n aria-label={incrementLabel}\n tabIndex={-1}\n disabled={stepperDisabled || atMaximum}\n onPointerDown={startHold(1)}\n onPointerUp={stopHold}\n onPointerLeave={stopHold}\n onPointerCancel={stopHold}\n onKeyDown={handleStepperKeyDown(1)}\n >\n <Icon name=\"lucide:plus\" />\n </button>\n </div>\n );\n};\n\nexport { NumberInput, type NumberInputProps };\n"],"mappings":"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"}
1
+ {"version":3,"file":"number-input.js","names":["clamp"],"sources":["../../../../src/components/forms/input/number-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type PointerEvent as ReactPointerEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type ChangeEvent,\n type FocusEvent,\n} from \"react\";\n\nimport { cn } from \"@puzzle-ui/core\";\nimport { Icon } from \"@puzzle-ui/icons\";\n\nimport \"./input.css\";\n\ntype NumberInputProps = Omit<\n ComponentPropsWithoutRef<\"input\">,\n \"type\" | \"value\" | \"defaultValue\" | \"onChange\" | \"step\" | \"min\" | \"max\"\n> & {\n value?: number;\n initialValue?: number;\n\n onChange?: (value: number) => void;\n\n min?: number;\n max?: number;\n step?: number;\n\n /** Amount applied by PageUp and PageDown. Defaults to ten times `step`. */\n largeStep?: number;\n\n fractionDigits?: number;\n\n longPressDelay?: number;\n repeatInterval?: number;\n\n /** Accessible label of the decrement button. */\n decrementLabel?: string;\n\n /** Accessible label of the increment button. */\n incrementLabel?: string;\n};\n\ntype Direction = -1 | 1;\n\nconst clamp = (value: number, min?: number, max?: number) =>\n Math.min(max ?? Infinity, Math.max(min ?? -Infinity, value));\n\nconst getPrecision = (value: number) => {\n const valueString = String(value);\n\n if (valueString.includes(\"e-\")) {\n return Number(valueString.split(\"e-\")[1]) || 0;\n }\n\n return valueString.split(\".\")[1]?.length ?? 0;\n};\n\nconst NumberInput = ({\n value,\n initialValue = 0,\n onChange,\n\n min,\n max,\n step = 1,\n largeStep,\n\n fractionDigits = getPrecision(step),\n\n longPressDelay = 450,\n repeatInterval = 120,\n\n decrementLabel = \"Decrease value\",\n incrementLabel = \"Increase value\",\n\n disabled,\n readOnly,\n className,\n\n onBlur,\n onFocus,\n onKeyDown,\n\n ...inputProps\n}: NumberInputProps) => {\n const isControlled = value !== undefined;\n\n const digits = Math.min(12, Math.max(0, Math.trunc(fractionDigits)));\n\n const factor = 10 ** digits;\n\n const safeStep = Number.isFinite(step) && step > 0 ? step : 1;\n\n const safeLargeStep =\n largeStep !== undefined && Number.isFinite(largeStep) && largeStep > 0\n ? largeStep\n : safeStep * 10;\n\n const allowsNegative = min === undefined || min < 0;\n\n const [internalValue, setInternalValue] = useState(() =>\n clamp(Number.isFinite(initialValue) ? initialValue : 0, min, max),\n );\n\n const currentValue = clamp(isControlled ? value : internalValue, min, max);\n\n const [draft, setDraft] = useState(() =>\n String(Number(currentValue.toFixed(digits))),\n );\n\n const currentValueRef = useRef(currentValue);\n const focusedRef = useRef(false);\n\n const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const repeatTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const holdStartedAtRef = useRef(0);\n const holdDirectionRef = useRef<Direction>(1);\n\n currentValueRef.current = currentValue;\n\n useEffect(() => {\n if (!focusedRef.current) {\n setDraft(String(Number(currentValue.toFixed(digits))));\n }\n }, [currentValue, digits]);\n\n const commit = useCallback(\n (nextValue: number) => {\n const rounded = Math.round(clamp(nextValue, min, max) * factor) / factor;\n\n if (!Number.isFinite(rounded) || rounded === currentValueRef.current) {\n return;\n }\n\n currentValueRef.current = rounded;\n\n if (!isControlled) {\n setInternalValue(rounded);\n }\n\n setDraft(String(rounded));\n\n onChange?.(rounded);\n },\n [factor, isControlled, max, min, onChange],\n );\n\n const changeBy = useCallback(\n (direction: Direction, amount = safeStep) => {\n const next =\n Math.round((currentValueRef.current + direction * amount) * factor) /\n factor;\n\n commit(next);\n },\n [commit, factor, safeStep],\n );\n\n const stopHold = useCallback(() => {\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n }\n\n if (repeatTimerRef.current) {\n clearTimeout(repeatTimerRef.current);\n }\n\n holdTimerRef.current = null;\n repeatTimerRef.current = null;\n }, []);\n\n const scheduleRepeat = useCallback(() => {\n const elapsed = Date.now() - holdStartedAtRef.current;\n\n const interval = Math.max(\n 45,\n repeatInterval - Math.floor(elapsed / 1800) * 20,\n );\n\n repeatTimerRef.current = setTimeout(() => {\n changeBy(holdDirectionRef.current);\n scheduleRepeat();\n }, interval);\n }, [changeBy, repeatInterval]);\n\n const startHold =\n (direction: Direction) => (event: ReactPointerEvent<HTMLButtonElement>) => {\n if (disabled || readOnly || event.button !== 0) {\n return;\n }\n\n event.preventDefault();\n\n stopHold();\n\n holdDirectionRef.current = direction;\n holdStartedAtRef.current = Date.now();\n\n changeBy(direction);\n\n holdTimerRef.current = setTimeout(scheduleRepeat, longPressDelay);\n };\n\n const handleStepperKeyDown =\n (direction: Direction) =>\n (event: ReactKeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== \" \" && event.key !== \"Enter\") {\n return;\n }\n\n event.preventDefault();\n changeBy(direction);\n };\n\n const handleInputKeyDown = (event: ReactKeyboardEvent<HTMLInputElement>) => {\n onKeyDown?.(event);\n\n if (event.defaultPrevented || disabled || readOnly) {\n return;\n }\n\n switch (event.key) {\n case \"ArrowUp\":\n changeBy(1);\n break;\n case \"ArrowDown\":\n changeBy(-1);\n break;\n case \"PageUp\":\n changeBy(1, safeLargeStep);\n break;\n case \"PageDown\":\n changeBy(-1, safeLargeStep);\n break;\n case \"Home\":\n if (min === undefined) return;\n commit(min);\n break;\n case \"End\":\n if (max === undefined) return;\n commit(max);\n break;\n default:\n return;\n }\n\n event.preventDefault();\n };\n\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {\n const nextDraft = event.target.value;\n\n setDraft(nextDraft);\n\n if (\n nextDraft.trim() === \"\" ||\n nextDraft === \"-\" ||\n nextDraft === \".\" ||\n nextDraft === \"-.\"\n ) {\n return;\n }\n\n const parsed = Number(nextDraft.replace(\",\", \".\"));\n\n if (Number.isFinite(parsed)) {\n commit(parsed);\n }\n };\n\n const handleBlur = (event: FocusEvent<HTMLInputElement>) => {\n focusedRef.current = false;\n\n const parsed = Number(draft.replace(\",\", \".\"));\n\n const nextValue =\n draft.trim() !== \"\" && Number.isFinite(parsed)\n ? clamp(Math.round(parsed * factor) / factor, min, max)\n : currentValueRef.current;\n\n commit(nextValue);\n\n setDraft(String(Number(nextValue.toFixed(digits))));\n\n onBlur?.(event);\n };\n\n useEffect(() => stopHold, [stopHold]);\n\n const atMinimum = min !== undefined && currentValue <= min;\n\n const atMaximum = max !== undefined && currentValue >= max;\n\n const trimmedDraft = draft.trim();\n const draftIsIncomplete =\n trimmedDraft === \"\" ||\n trimmedDraft === \"-\" ||\n trimmedDraft === \".\" ||\n trimmedDraft === \"-.\";\n const draftValue = Number(trimmedDraft.replace(\",\", \".\"));\n\n const draftIsInvalid =\n !draftIsIncomplete &&\n (!Number.isFinite(draftValue) ||\n (min !== undefined && draftValue < min) ||\n (max !== undefined && draftValue > max));\n\n const stepperDisabled = disabled || readOnly;\n\n return (\n <div\n className={cn(\"puzzle-input\", \"puzzle-input--number\", className)}\n data-disabled={disabled || undefined}\n >\n <button\n type=\"button\"\n className=\"puzzle-input__stepper\"\n aria-label={decrementLabel}\n tabIndex={-1}\n disabled={stepperDisabled || atMinimum}\n onPointerDown={startHold(-1)}\n onPointerUp={stopHold}\n onPointerLeave={stopHold}\n onPointerCancel={stopHold}\n onKeyDown={handleStepperKeyDown(-1)}\n >\n <Icon name=\"lucide:minus\" />\n </button>\n\n <input\n {...inputProps}\n type=\"text\"\n role=\"spinbutton\"\n aria-valuenow={currentValue}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-invalid={draftIsInvalid || inputProps[\"aria-invalid\"]}\n inputMode={allowsNegative ? \"text\" : digits > 0 ? \"decimal\" : \"numeric\"}\n pattern={`${allowsNegative ? \"-?\" : \"\"}[0-9]*${digits > 0 ? \"[.,]?[0-9]*\" : \"\"}`}\n disabled={disabled}\n readOnly={readOnly}\n value={draft}\n className=\"puzzle-input__control\"\n onFocus={(event) => {\n focusedRef.current = true;\n onFocus?.(event);\n }}\n onChange={handleInputChange}\n onKeyDown={handleInputKeyDown}\n onBlur={handleBlur}\n />\n\n <button\n type=\"button\"\n className=\"puzzle-input__stepper\"\n aria-label={incrementLabel}\n tabIndex={-1}\n disabled={stepperDisabled || atMaximum}\n onPointerDown={startHold(1)}\n onPointerUp={stopHold}\n onPointerLeave={stopHold}\n onPointerCancel={stopHold}\n onKeyDown={handleStepperKeyDown(1)}\n >\n <Icon name=\"lucide:plus\" />\n </button>\n </div>\n );\n};\n\nexport { NumberInput, type NumberInputProps };\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { InputGroup } from "./input-group.cjs";
2
+ import { InputGroupAddonAlign, InputGroupAddonProps, InputGroupProps } from "./input-group.types.cjs";
3
+ export { InputGroup, type InputGroupAddonAlign, type InputGroupAddonProps, type InputGroupProps };
@@ -0,0 +1,3 @@
1
+ import { InputGroup } from "./input-group.js";
2
+ import { InputGroupAddonAlign, InputGroupAddonProps, InputGroupProps } from "./input-group.types.js";
3
+ export { InputGroup, type InputGroupAddonAlign, type InputGroupAddonProps, type InputGroupProps };
@@ -0,0 +1,18 @@
1
+ const require_input_group_utils = require("./input-group.utils.cjs");
2
+ ;/* empty css */
3
+ let _puzzle_ui_core = require("@puzzle-ui/core");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ // src/components/forms/input-group/input-group.tsx
6
+ const InputGroupAddon = ({ align, className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7
+ ...props,
8
+ className: (0, _puzzle_ui_core.cn)("puzzle-input-group__addon", className),
9
+ "data-align": require_input_group_utils.resolveInputGroupAddonAlign(align)
10
+ });
11
+ const InputGroupRoot = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12
+ ...props,
13
+ className: (0, _puzzle_ui_core.cn)("puzzle-input-group", className)
14
+ });
15
+ const InputGroup = Object.assign(InputGroupRoot, { Addon: InputGroupAddon });
16
+ exports.InputGroup = InputGroup;
17
+
18
+ //# sourceMappingURL=input-group.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-group.cjs","names":["cn","resolveInputGroupAddonAlign"],"sources":["../../../../src/components/forms/input-group/input-group.tsx"],"sourcesContent":["import { cn } from \"@puzzle-ui/core\";\n\nimport { resolveInputGroupAddonAlign } from \"./input-group.utils\";\nimport type {\n InputGroupAddonProps,\n InputGroupProps,\n} from \"./input-group.types\";\n\nimport \"./input-group.css\";\n\nconst InputGroupAddon = ({\n align,\n className,\n ...props\n}: InputGroupAddonProps) => (\n <div\n {...props}\n className={cn(\"puzzle-input-group__addon\", className)}\n data-align={resolveInputGroupAddonAlign(align)}\n />\n);\n\nconst InputGroupRoot = ({ className, ...props }: InputGroupProps) => (\n <div {...props} className={cn(\"puzzle-input-group\", className)} />\n);\n\nconst InputGroup = Object.assign(InputGroupRoot, {\n Addon: InputGroupAddon,\n});\n\nexport { InputGroup };\n"],"mappings":";;;;;AAUA,MAAM,mBAAmB,EACvB,OACA,WACA,GAAG,YAEH,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;CACE,GAAI;CACJ,YAAA,GAAWA,gBAAAA,GAAAA,CAAG,6BAA6B,SAAS;CACpD,cAAYC,0BAAAA,4BAA4B,KAAK;AAC9C,CAAA;AAGH,MAAM,kBAAkB,EAAE,WAAW,GAAG,YACtC,iBAAA,GAAA,kBAAA,IAAA,CAAC,OAAD;CAAK,GAAI;CAAO,YAAA,GAAWD,gBAAAA,GAAAA,CAAG,sBAAsB,SAAS;AAAI,CAAA;AAGnE,MAAM,aAAa,OAAO,OAAO,gBAAgB,EAC/C,OAAO,gBACT,CAAC"}