@inkra/react 0.2.0 → 0.4.3

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 (246) hide show
  1. package/README.md +101 -7
  2. package/dist/Button.js +3 -2
  3. package/dist/PencilIcon.d.ts +10 -0
  4. package/dist/PencilIcon.js +83 -39
  5. package/dist/PencilLine.js +9 -1
  6. package/dist/PencilText.js +15 -9
  7. package/dist/accordion/Accordion.js +65 -5
  8. package/dist/alert-dialog/index.d.ts +1 -1
  9. package/dist/alert-dialog/index.js +18 -6
  10. package/dist/audio-player/index.d.ts +2 -0
  11. package/dist/audio-player/index.js +126 -31
  12. package/dist/calendar/index.d.ts +4 -1
  13. package/dist/calendar/index.js +31 -12
  14. package/dist/carousel/index.js +13 -6
  15. package/dist/choice-grid/index.d.ts +6 -1
  16. package/dist/choice-grid/index.js +61 -33
  17. package/dist/color-picker/GradientEditor.d.ts +39 -0
  18. package/dist/color-picker/GradientEditor.js +63 -0
  19. package/dist/color-picker/gradient.d.ts +25 -0
  20. package/dist/color-picker/gradient.js +60 -0
  21. package/dist/color-picker/index.d.ts +27 -7
  22. package/dist/color-picker/index.js +134 -32
  23. package/dist/color-swatch/index.d.ts +10 -0
  24. package/dist/color-swatch/index.js +9 -0
  25. package/dist/command/index.d.ts +24 -2
  26. package/dist/command/index.js +29 -6
  27. package/dist/content/index.d.ts +5 -0
  28. package/dist/content/index.js +12 -3
  29. package/dist/context-menu/index.d.ts +2 -2
  30. package/dist/context-menu/index.js +4 -3
  31. package/dist/data-table/index.d.ts +7 -1
  32. package/dist/data-table/index.js +64 -16
  33. package/dist/data-table/model.d.ts +2 -0
  34. package/dist/data-table/model.js +7 -0
  35. package/dist/date-picker/index.d.ts +2 -0
  36. package/dist/date-picker/index.js +10 -9
  37. package/dist/day-agenda/index.d.ts +6 -1
  38. package/dist/day-agenda/index.js +46 -21
  39. package/dist/day-agenda/layout.js +65 -9
  40. package/dist/display/ProgressRing.js +7 -3
  41. package/dist/display/index.d.ts +4 -1
  42. package/dist/display/index.js +3 -2
  43. package/dist/dockable-panel/index.js +5 -5
  44. package/dist/drawer/index.d.ts +1 -1
  45. package/dist/drawer/index.js +9 -6
  46. package/dist/dropdown-menu/index.d.ts +1 -1
  47. package/dist/dropdown-menu/index.js +3 -2
  48. package/dist/editable/index.d.ts +24 -2
  49. package/dist/editable/index.js +142 -37
  50. package/dist/empty/illustrations.d.ts +8 -0
  51. package/dist/empty/illustrations.js +78 -0
  52. package/dist/empty/index.d.ts +6 -1
  53. package/dist/empty/index.js +11 -3
  54. package/dist/flip/index.js +28 -8
  55. package/dist/form/EditablePencilText.js +34 -0
  56. package/dist/form/Field.js +11 -10
  57. package/dist/form/Input.js +1 -1
  58. package/dist/form/Textarea.d.ts +3 -1
  59. package/dist/form/Textarea.js +6 -4
  60. package/dist/hotkey/index.d.ts +1 -0
  61. package/dist/hotkey/index.js +6 -4
  62. package/dist/hover-card/index.js +1 -1
  63. package/dist/image-cropper/index.js +4 -2
  64. package/dist/image-viewer/VideoResource.d.ts +21 -0
  65. package/dist/image-viewer/VideoResource.js +194 -0
  66. package/dist/image-viewer/index.d.ts +14 -0
  67. package/dist/image-viewer/index.js +118 -65
  68. package/dist/image-viewer/lucideGeometry.d.ts +3 -0
  69. package/dist/image-viewer/lucideGeometry.js +19 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +2 -0
  72. package/dist/input-number/index.d.ts +6 -0
  73. package/dist/input-number/index.js +131 -15
  74. package/dist/input-number/model.d.ts +13 -0
  75. package/dist/input-number/model.js +50 -0
  76. package/dist/internal/cache.d.ts +4 -0
  77. package/dist/internal/cache.js +18 -1
  78. package/dist/internal/iconGeometry.d.ts +2 -0
  79. package/dist/internal/iconGeometry.js +32 -0
  80. package/dist/internal/renderTokens.d.ts +4 -0
  81. package/dist/internal/renderTokens.js +4 -0
  82. package/dist/internal/stroke.d.ts +7 -0
  83. package/dist/internal/stroke.js +63 -7
  84. package/dist/internal/surfaceBudget.d.ts +20 -0
  85. package/dist/internal/surfaceBudget.js +69 -0
  86. package/dist/internal/surfaceRenderer.js +87 -28
  87. package/dist/internal/surfaceVisibility.d.ts +3 -0
  88. package/dist/internal/surfaceVisibility.js +60 -0
  89. package/dist/internal/textRenderer.d.ts +2 -0
  90. package/dist/internal/textRenderer.js +3 -3
  91. package/dist/internal/usePencilSurface.js +17 -1
  92. package/dist/internal/wash.js +4 -3
  93. package/dist/journey-map/terrain.js +10 -5
  94. package/dist/layout/index.js +1 -1
  95. package/dist/log/index.js +29 -22
  96. package/dist/markdown/CodeBlock.d.ts +19 -0
  97. package/dist/markdown/CodeBlock.js +86 -0
  98. package/dist/markdown/Deletion.d.ts +2 -0
  99. package/dist/markdown/Deletion.js +69 -0
  100. package/dist/markdown/icons.d.ts +14 -0
  101. package/dist/markdown/icons.js +16 -0
  102. package/dist/markdown/index.d.ts +17 -0
  103. package/dist/markdown/index.js +175 -0
  104. package/dist/node-graph/index.d.ts +2 -0
  105. package/dist/node-graph/index.js +49 -25
  106. package/dist/number-ticker/index.d.ts +11 -9
  107. package/dist/number-ticker/index.js +14 -11
  108. package/dist/number-ticker/model.d.ts +38 -5
  109. package/dist/number-ticker/model.js +139 -10
  110. package/dist/number-ticker/precision.d.ts +7 -0
  111. package/dist/number-ticker/precision.js +141 -0
  112. package/dist/overflow-list/index.js +5 -2
  113. package/dist/overlay/Overlay.d.ts +1 -1
  114. package/dist/overlay/Overlay.js +5 -3
  115. package/dist/overlay/Popover.d.ts +1 -1
  116. package/dist/overlay/Popover.js +6 -5
  117. package/dist/overlay/themeBridge.d.ts +12 -1
  118. package/dist/overlay/themeBridge.js +41 -3
  119. package/dist/pagination/index.d.ts +10 -1
  120. package/dist/pagination/index.js +6 -4
  121. package/dist/pan-zoom/index.d.ts +2 -0
  122. package/dist/pan-zoom/index.js +7 -6
  123. package/dist/pencil.d.ts +1 -0
  124. package/dist/pencil.js +4 -4
  125. package/dist/radar/index.d.ts +4 -0
  126. package/dist/radar/index.js +41 -21
  127. package/dist/rating/index.d.ts +4 -0
  128. package/dist/rating/index.js +128 -24
  129. package/dist/resizable/index.js +1 -1
  130. package/dist/scroll-area/index.d.ts +1 -1
  131. package/dist/scroll-area/index.js +5 -2
  132. package/dist/select/MultipleSelect.js +5 -4
  133. package/dist/select/SearchableSelect.js +61 -6
  134. package/dist/select/Select.d.ts +1 -0
  135. package/dist/select/Select.js +3 -1
  136. package/dist/select/StepperSelect.d.ts +29 -0
  137. package/dist/select/StepperSelect.js +119 -0
  138. package/dist/select/index.d.ts +2 -0
  139. package/dist/select/index.js +1 -0
  140. package/dist/select/stepperModel.d.ts +7 -0
  141. package/dist/select/stepperModel.js +29 -0
  142. package/dist/selection/Checkbox.js +4 -3
  143. package/dist/selection/RadioGroup.js +29 -13
  144. package/dist/selection/Switch.js +4 -3
  145. package/dist/selection/useNativeChecked.d.ts +2 -1
  146. package/dist/selection/useNativeChecked.js +26 -8
  147. package/dist/slider/index.js +50 -6
  148. package/dist/sortable/index.js +15 -6
  149. package/dist/sparkline/index.js +17 -2
  150. package/dist/sparkline/model.js +11 -4
  151. package/dist/styles/accordion.css +228 -0
  152. package/dist/styles/alert-dialog.css +139 -0
  153. package/dist/styles/audio-player.css +22 -0
  154. package/dist/styles/base.css +638 -0
  155. package/dist/styles/breadcrumb.css +7 -0
  156. package/dist/styles/calendar.css +62 -0
  157. package/dist/styles/carousel.css +173 -0
  158. package/dist/styles/choice-grid.css +226 -0
  159. package/dist/styles/color-picker.css +281 -0
  160. package/dist/styles/color-swatch.css +47 -0
  161. package/dist/styles/command.css +52 -0
  162. package/dist/styles/content.css +79 -0
  163. package/dist/styles/context-menu.css +35 -0
  164. package/dist/styles/data-table.css +31 -0
  165. package/dist/styles/date-picker.css +107 -0
  166. package/dist/styles/day-agenda.css +30 -0
  167. package/dist/styles/display.css +304 -0
  168. package/dist/styles/dockable-panel.css +110 -0
  169. package/dist/styles/drawer.css +133 -0
  170. package/dist/styles/dropdown-menu.css +68 -0
  171. package/dist/styles/editable.css +145 -0
  172. package/dist/styles/empty.css +108 -0
  173. package/dist/styles/flip.css +101 -0
  174. package/dist/styles/form.css +634 -0
  175. package/dist/styles/heatmap.css +256 -0
  176. package/dist/styles/hotkey.css +189 -0
  177. package/dist/styles/hover-card.css +27 -0
  178. package/dist/styles/image-cropper.css +205 -0
  179. package/dist/styles/image-viewer.css +422 -0
  180. package/dist/styles/input-number.css +21 -0
  181. package/dist/styles/journey-map.css +19 -0
  182. package/dist/styles/keypad.css +92 -0
  183. package/dist/styles/layout.css +72 -0
  184. package/dist/styles/leaflet.css +661 -0
  185. package/dist/styles/log.css +203 -0
  186. package/dist/styles/markdown.css +379 -0
  187. package/dist/styles/node-graph.css +142 -0
  188. package/dist/styles/number-ticker.css +83 -0
  189. package/dist/styles/overflow-list.css +51 -0
  190. package/dist/styles/overlay.css +284 -0
  191. package/dist/styles/pagination.css +21 -0
  192. package/dist/styles/pan-zoom.css +136 -0
  193. package/dist/styles/radar.css +1 -0
  194. package/dist/styles/rating.css +121 -0
  195. package/dist/styles/resizable.css +108 -0
  196. package/dist/styles/scroll-area.css +122 -0
  197. package/dist/styles/select.css +557 -0
  198. package/dist/styles/selection.css +264 -0
  199. package/dist/styles/slider.css +145 -0
  200. package/dist/styles/slot.css +109 -0
  201. package/dist/styles/sortable.css +159 -0
  202. package/dist/styles/sparkline.css +24 -0
  203. package/dist/styles/steps.css +27 -0
  204. package/dist/styles/tabs.css +79 -0
  205. package/dist/styles/tag-input.css +213 -0
  206. package/dist/styles/time-picker.css +66 -0
  207. package/dist/styles/timeline.css +24 -0
  208. package/dist/styles/toast.css +160 -0
  209. package/dist/styles/toggle-group.css +107 -0
  210. package/dist/styles/tooltip.css +78 -0
  211. package/dist/styles/transfer.css +148 -0
  212. package/dist/styles/tree-select.css +131 -0
  213. package/dist/styles/tree.css +59 -0
  214. package/dist/styles/typewriter.css +62 -0
  215. package/dist/styles/upload.css +60 -0
  216. package/dist/styles/virtual-list.css +112 -0
  217. package/dist/styles.css +2241 -443
  218. package/dist/tabs/index.d.ts +9 -0
  219. package/dist/tabs/index.js +66 -16
  220. package/dist/tabs/useOverflow.d.ts +20 -0
  221. package/dist/tabs/useOverflow.js +145 -0
  222. package/dist/tag-input/index.d.ts +7 -2
  223. package/dist/tag-input/index.js +70 -26
  224. package/dist/time-picker/index.js +38 -20
  225. package/dist/toast/index.d.ts +19 -11
  226. package/dist/toast/index.js +48 -8
  227. package/dist/toast/manager.d.ts +42 -0
  228. package/dist/toast/manager.js +85 -0
  229. package/dist/toast/useToastNavigation.d.ts +3 -0
  230. package/dist/toast/useToastNavigation.js +75 -0
  231. package/dist/tokens.css +4 -0
  232. package/dist/tooltip/Tooltip.d.ts +1 -1
  233. package/dist/tooltip/Tooltip.js +6 -5
  234. package/dist/transfer/index.d.ts +5 -0
  235. package/dist/transfer/index.js +93 -29
  236. package/dist/transfer/model.d.ts +4 -0
  237. package/dist/transfer/model.js +37 -11
  238. package/dist/tree/index.d.ts +5 -1
  239. package/dist/tree/index.js +5 -5
  240. package/dist/tree-select/index.js +67 -30
  241. package/dist/tree-select/model.d.ts +11 -5
  242. package/dist/tree-select/model.js +111 -83
  243. package/dist/typewriter/index.js +10 -10
  244. package/dist/upload/index.js +30 -16
  245. package/dist/virtual-list/index.js +10 -7
  246. package/package.json +416 -5
@@ -0,0 +1,39 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ColorGradient } from './gradient.js';
3
+ import type { ColorValue } from './color.js';
4
+ export interface ColorGradientLabels {
5
+ type?: string;
6
+ solid?: string;
7
+ linear?: string;
8
+ radial?: string;
9
+ conic?: string;
10
+ preview?: string;
11
+ stops?: string;
12
+ stop?: string;
13
+ stopColor?: string;
14
+ addStop?: string;
15
+ removeStop?: string;
16
+ reverse?: string;
17
+ position?: string;
18
+ angle?: string;
19
+ centerX?: string;
20
+ centerY?: string;
21
+ shape?: string;
22
+ circle?: string;
23
+ ellipse?: string;
24
+ extent?: string;
25
+ closestSide?: string;
26
+ closestCorner?: string;
27
+ farthestSide?: string;
28
+ farthestCorner?: string;
29
+ repeating?: string;
30
+ }
31
+ export declare const defaultGradientLabels: Required<ColorGradientLabels>;
32
+ export declare function GradientEditor({ value, disabled, maxStops, labels, onChange, renderColorEditor }: {
33
+ value: ColorGradient;
34
+ disabled: boolean;
35
+ maxStops: number;
36
+ labels: Required<ColorGradientLabels>;
37
+ onChange: (value: ColorGradient, source: 'stop' | 'geometry') => void;
38
+ renderColorEditor: (value: ColorValue, onChange: (value: ColorValue) => void) => ReactNode;
39
+ }): import("react").JSX.Element;
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useId, useRef, useState } from 'react';
3
+ import { __iconData as addIcon } from 'lucide-react/dist/esm/icons/plus.mjs';
4
+ import { __iconData as removeIcon } from 'lucide-react/dist/esm/icons/minus.mjs';
5
+ import { __iconData as reverseIcon } from 'lucide-react/dist/esm/icons/arrow-left-right.mjs';
6
+ import { Button } from '../Button.js';
7
+ import { ColorSwatch } from '../color-swatch/index.js';
8
+ import { Field } from '../form/Field.js';
9
+ import { InputNumber } from '../input-number/index.js';
10
+ import { PencilIcon } from '../PencilIcon.js';
11
+ import { PencilSurface } from '../PencilSurface.js';
12
+ import { PencilText } from '../PencilText.js';
13
+ import { Select } from '../select/index.js';
14
+ import { Slider } from '../slider/index.js';
15
+ import { Switch } from '../selection/Switch.js';
16
+ import { ToggleGroup } from '../toggle-group/index.js';
17
+ import { formatColorPaint } from './gradient.js';
18
+ export const defaultGradientLabels = {
19
+ type: 'Color type', solid: 'Solid', linear: 'Linear', radial: 'Radial', conic: 'Conic',
20
+ preview: 'Gradient preview', stops: 'Color stops', stop: 'Stop', stopColor: 'Stop color',
21
+ addStop: 'Add stop', removeStop: 'Remove stop', reverse: 'Reverse gradient',
22
+ position: 'Stop position', angle: 'Angle', centerX: 'Center X', centerY: 'Center Y',
23
+ shape: 'Shape', circle: 'Circle', ellipse: 'Ellipse', extent: 'Extent',
24
+ closestSide: 'Closest side', closestCorner: 'Closest corner',
25
+ farthestSide: 'Farthest side', farthestCorner: 'Farthest corner', repeating: 'Repeat gradient',
26
+ };
27
+ const iconPaths = (data) => data.node.filter(([tag]) => tag === 'path').map(([, attrs]) => ({ d: attrs.d }));
28
+ const actions = { add: iconPaths(addIcon), remove: iconPaths(removeIcon), reverse: iconPaths(reverseIcon) };
29
+ export function GradientEditor({ value, disabled, maxStops, labels, onChange, renderColorEditor }) {
30
+ const id = useId();
31
+ const counter = useRef(0);
32
+ const [selected, setSelected] = useState();
33
+ const stop = value.stops.find(stop => stop.id === selected) ?? value.stops[0];
34
+ const updateStop = (patch) => onChange({
35
+ ...value, stops: value.stops.map(item => item.id === stop.id ? { ...item, ...patch } : item),
36
+ }, 'stop');
37
+ const numberField = (label, current, max, update) => _jsx(Field, { label: label, disabled: disabled, children: _jsx(InputNumber, { controls: false, min: 0, max: max, value: current, onValueChange: next => { if (next !== null)
38
+ update(Math.min(max, Math.max(0, next))); } }) });
39
+ function addStop() {
40
+ // Insert in the widest interval, preserving existing stops and their identities.
41
+ let left = value.stops[0];
42
+ let right = value.stops[1];
43
+ for (let index = 1; index < value.stops.length - 1; index++) {
44
+ if (value.stops[index + 1].position - value.stops[index].position > right.position - left.position) {
45
+ left = value.stops[index];
46
+ right = value.stops[index + 1];
47
+ }
48
+ }
49
+ let nextId;
50
+ do {
51
+ nextId = `${id}-${counter.current++}`;
52
+ } while (value.stops.some(item => item.id === nextId));
53
+ onChange({ ...value, stops: [...value.stops, { id: nextId, color: stop.color, position: (left.position + right.position) / 2 }] }, 'stop');
54
+ setSelected(nextId);
55
+ }
56
+ return _jsxs("div", { className: "inkra-color-picker__gradient", children: [_jsxs("div", { className: "inkra-color-picker__gradient-preview", role: "img", "aria-label": `${labels.preview}: ${formatColorPaint(value)}`, children: [_jsx("span", { className: "inkra-color-picker__checker", "aria-hidden": "true" }), _jsx("span", { className: "inkra-color-picker__current", "aria-hidden": "true", style: { background: formatColorPaint(value) } }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", variant: "outline", className: "inkra-color-picker__palette-outline" })] }), _jsxs("div", { className: "inkra-color-picker__stop-toolbar", children: [_jsx("div", { className: "inkra-color-picker__stops", role: "group", "aria-label": labels.stops, children: value.stops.map((item, index) => _jsxs("span", { className: "inkra-color-picker__stop", children: [_jsx(ColorSwatch, { color: item.color, selected: item.id === stop.id, disabled: disabled, label: `${labels.stop} ${index + 1}: ${item.color} (${item.position}%)`, onClick: () => setSelected(item.id) }), _jsx(PencilText, { "aria-hidden": "true", children: `${item.position}%` })] }, item.id)) }), _jsxs("div", { className: "inkra-color-picker__stop-actions", children: [_jsx(Button, { size: "icon", variant: "ghost", "aria-label": labels.addStop, title: labels.addStop, disabled: disabled || value.stops.length >= maxStops, onClick: addStop, children: _jsx(PencilIcon, { paths: actions.add, size: 20 }) }), _jsx(Button, { size: "icon", variant: "ghost", "aria-label": labels.removeStop, title: labels.removeStop, disabled: disabled || value.stops.length <= 2, onClick: () => {
57
+ const index = value.stops.indexOf(stop);
58
+ const stops = value.stops.filter(item => item.id !== stop.id);
59
+ onChange({ ...value, stops }, 'stop');
60
+ setSelected(stops[Math.min(index, stops.length - 1)].id);
61
+ }, children: _jsx(PencilIcon, { paths: actions.remove, size: 20 }) }), _jsx(Button, { size: "icon", variant: "ghost", "aria-label": labels.reverse, title: labels.reverse, disabled: disabled, onClick: () => onChange({ ...value, stops: [...value.stops].reverse().map(item => ({ ...item, position: 100 - item.position })) }, 'stop'), children: _jsx(PencilIcon, { paths: actions.reverse, size: 20 }) })] })] }), _jsxs("div", { className: "inkra-color-picker__stop-position", children: [numberField(labels.position, stop.position, 100, position => updateStop({ position })), _jsx(Slider, { "aria-label": labels.position, disabled: disabled, value: [stop.position], min: 0, max: 100, step: 1, getValueLabel: value => `${value}%`, onValueChange: ([position]) => updateStop({ position }) })] }), renderColorEditor(stop.color, color => updateStop({ color })), _jsxs("div", { className: "inkra-color-picker__geometry", children: [value.type !== 'radial' && numberField(labels.angle, value.angle ?? 0, 359, angle => onChange({ ...value, angle }, 'geometry')), value.type !== 'linear' && _jsxs(_Fragment, { children: [numberField(labels.centerX, value.center?.x ?? 50, 100, x => onChange({ ...value, center: { x, y: value.center?.y ?? 50 } }, 'geometry')), numberField(labels.centerY, value.center?.y ?? 50, 100, y => onChange({ ...value, center: { x: value.center?.x ?? 50, y } }, 'geometry'))] })] }), value.type === 'radial' && _jsxs("div", { className: "inkra-color-picker__radial-options", children: [_jsxs(ToggleGroup.Root, { "aria-label": labels.shape, disabled: disabled, value: [value.shape ?? 'ellipse'], size: "sm", onValueChange: values => { if (values[0])
62
+ onChange({ ...value, shape: values[0] }, 'geometry'); }, children: [_jsx(ToggleGroup.Item, { value: "circle", children: labels.circle }), _jsx(ToggleGroup.Item, { value: "ellipse", children: labels.ellipse })] }), _jsxs(Select.Root, { disabled: disabled, value: value.extent ?? 'farthest-corner', onValueChange: extent => onChange({ ...value, extent: extent }, 'geometry'), children: [_jsx(Select.Trigger, { "aria-label": labels.extent, children: _jsx(Select.Value, {}) }), _jsx(Select.Portal, { children: _jsx(Select.Content, { position: "popper", children: _jsx(Select.Viewport, { children: [['closest-side', labels.closestSide], ['closest-corner', labels.closestCorner], ['farthest-side', labels.farthestSide], ['farthest-corner', labels.farthestCorner]].map(([key, label]) => _jsxs(Select.Item, { value: key, children: [_jsx(Select.ItemText, { children: label }), _jsx(Select.ItemIndicator, {})] }, key)) }) }) })] })] }), _jsx(Switch, { label: labels.repeating, disabled: disabled, checked: value.repeating ?? false, onCheckedChange: repeating => onChange({ ...value, repeating }, 'geometry') })] });
63
+ }
@@ -0,0 +1,25 @@
1
+ import type { ColorValue } from './color.js';
2
+ export type ColorPaintType = 'solid' | 'linear' | 'radial' | 'conic';
3
+ export interface ColorGradientStop {
4
+ id?: string;
5
+ color: string;
6
+ position: number;
7
+ }
8
+ export interface ColorGradient {
9
+ type: Exclude<ColorPaintType, 'solid'>;
10
+ stops: readonly ColorGradientStop[];
11
+ angle?: number;
12
+ center?: {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ shape?: 'circle' | 'ellipse';
17
+ extent?: 'closest-side' | 'closest-corner' | 'farthest-side' | 'farthest-corner';
18
+ repeating?: boolean;
19
+ }
20
+ export type ColorPaint = ColorValue | ColorGradient;
21
+ export declare const colorPaintTypes: readonly ColorPaintType[];
22
+ export declare function normalizeColorPaint(input: string | ColorGradient): ColorPaint | null;
23
+ /** Serialize structured paint only; arbitrary CSS expressions are not parsed. */
24
+ export declare function formatColorPaint(input: string | ColorGradient): string;
25
+ export declare function createColorGradient(type: ColorGradient['type'], color?: string): ColorGradient;
@@ -0,0 +1,60 @@
1
+ import { DEFAULT_COLOR_VALUE, normalizeColorValue } from './color.js';
2
+ export const colorPaintTypes = ['solid', 'linear', 'radial', 'conic'];
3
+ const extents = ['closest-side', 'closest-corner', 'farthest-side', 'farthest-corner'];
4
+ const finite = (value) => typeof value === 'number' && Number.isFinite(value);
5
+ const percent = (value) => Math.round(Math.min(100, Math.max(0, value)) * 100) / 100;
6
+ export function normalizeColorPaint(input) {
7
+ if (typeof input === 'string')
8
+ return normalizeColorValue(input);
9
+ if (!input || !['linear', 'radial', 'conic'].includes(input.type)
10
+ || !Array.isArray(input.stops) || input.stops.length < 2 || input.stops.length > 64
11
+ || (input.angle !== undefined && !finite(input.angle))
12
+ || (input.center !== undefined && (!input.center || !finite(input.center.x) || !finite(input.center.y)))
13
+ || (input.shape !== undefined && !['circle', 'ellipse'].includes(input.shape))
14
+ || (input.extent !== undefined && !extents.includes(input.extent)))
15
+ return null;
16
+ const ids = new Set();
17
+ const stops = [];
18
+ for (const [index, stop] of input.stops.entries()) {
19
+ if (!stop || typeof stop.color !== 'string' || !finite(stop.position))
20
+ return null;
21
+ const color = normalizeColorValue(stop.color);
22
+ if (!color)
23
+ return null;
24
+ let id = typeof stop.id === 'string' && stop.id ? stop.id : `stop-${index}`;
25
+ while (ids.has(id))
26
+ id += '-duplicate';
27
+ ids.add(id);
28
+ stops.push({ id, color, position: percent(stop.position) });
29
+ }
30
+ stops.sort((a, b) => a.position - b.position);
31
+ const angle = input.angle ?? (input.type === 'linear' ? 90 : 0);
32
+ return {
33
+ type: input.type, stops,
34
+ angle: (Math.round(((angle % 360 + 360) % 360) * 100) / 100) % 360,
35
+ center: { x: percent(input.center?.x ?? 50), y: percent(input.center?.y ?? 50) },
36
+ shape: input.shape ?? 'ellipse', extent: input.extent ?? 'farthest-corner',
37
+ repeating: input.repeating === true,
38
+ };
39
+ }
40
+ /** Serialize structured paint only; arbitrary CSS expressions are not parsed. */
41
+ export function formatColorPaint(input) {
42
+ const value = normalizeColorPaint(input);
43
+ if (!value)
44
+ return DEFAULT_COLOR_VALUE;
45
+ if (typeof value === 'string')
46
+ return value;
47
+ const stops = value.stops.map(stop => `${stop.color} ${stop.position}%`).join(', ');
48
+ const at = `at ${value.center.x}% ${value.center.y}%`;
49
+ const geometry = value.type === 'linear' ? `${value.angle}deg`
50
+ : value.type === 'radial' ? `${value.shape} ${value.extent} ${at}`
51
+ : `from ${value.angle}deg ${at}`;
52
+ return `${value.repeating ? 'repeating-' : ''}${value.type}-gradient(${geometry}, ${stops})`;
53
+ }
54
+ export function createColorGradient(type, color = DEFAULT_COLOR_VALUE) {
55
+ const start = normalizeColorValue(color) ?? DEFAULT_COLOR_VALUE;
56
+ return normalizeColorPaint({ type, stops: [
57
+ { id: 'start', color: start, position: 0 },
58
+ { id: 'end', color: start === '#e6c17bff' ? DEFAULT_COLOR_VALUE : '#e6c17bff', position: 100 },
59
+ ] });
60
+ }
@@ -1,7 +1,9 @@
1
1
  import type { FieldsetHTMLAttributes, ReactNode } from 'react';
2
+ import type { ColorGradientLabels } from './GradientEditor.js';
3
+ import type { ColorGradient, ColorPaint, ColorPaintType } from './gradient.js';
2
4
  import type { ColorValue } from './color.js';
3
5
  export interface ColorPreset {
4
- value: string;
6
+ value: string | ColorGradient;
5
7
  label?: string;
6
8
  }
7
9
  export interface ColorPickerLabels {
@@ -12,20 +14,38 @@ export interface ColorPickerLabels {
12
14
  invalid?: string;
13
15
  }
14
16
  export interface ColorPickerChangeDetails {
15
- source: 'palette' | 'alpha' | 'input' | 'preset';
17
+ source: 'palette' | 'alpha' | 'input' | 'preset' | 'type' | 'stop' | 'geometry';
16
18
  }
17
- export interface ColorPickerProps extends Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'defaultValue' | 'onChange' | 'value'> {
18
- value?: string;
19
- defaultValue?: string;
20
- onValueChange?: (value: ColorValue, details: ColorPickerChangeDetails) => void;
19
+ export type ColorPickerPresentation = 'inline' | 'compact' | 'popover' | 'swatches';
20
+ export interface ColorPickerBaseProps extends Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'defaultValue' | 'onChange' | 'value'> {
21
21
  onInvalidInput?: (input: string) => void;
22
22
  presets?: readonly (string | ColorPreset)[];
23
23
  label?: ReactNode;
24
24
  labels?: ColorPickerLabels;
25
+ gradientLabels?: ColorGradientLabels;
25
26
  name?: string;
26
27
  showAlpha?: boolean;
28
+ presentation?: ColorPickerPresentation;
27
29
  seed?: string | number;
28
30
  }
29
- export declare function ColorPicker({ className, defaultValue, disabled, id, label, labels, name, onInvalidInput, onValueChange, presets, seed, showAlpha, value, ...props }: ColorPickerProps): import("react").JSX.Element;
31
+ export type ColorPickerProps = ColorPickerBaseProps & ({
32
+ allowGradient?: false;
33
+ value?: string;
34
+ defaultValue?: string;
35
+ onValueChange?: (value: ColorValue, details: ColorPickerChangeDetails) => void;
36
+ colorTypes?: never;
37
+ maxStops?: never;
38
+ } | {
39
+ allowGradient: true;
40
+ value?: string | ColorGradient;
41
+ defaultValue?: string | ColorGradient;
42
+ onValueChange?: (value: ColorPaint, details: ColorPickerChangeDetails) => void;
43
+ colorTypes?: readonly ColorPaintType[];
44
+ maxStops?: number;
45
+ });
46
+ export declare function ColorPicker(options: ColorPickerProps): import("react").JSX.Element;
30
47
  export { DEFAULT_COLOR_VALUE, colorAlphaPercent, colorRgbValue, formatColorValue, normalizeColorValue, parseColorValue, withColorAlpha, withColorRgb, } from './color.js';
31
48
  export type { ColorChannels, ColorValue } from './color.js';
49
+ export { createColorGradient, formatColorPaint, normalizeColorPaint } from './gradient.js';
50
+ export type { ColorGradient, ColorGradientStop, ColorPaint, ColorPaintType } from './gradient.js';
51
+ export type { ColorGradientLabels } from './GradientEditor.js';
@@ -1,10 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useId, useMemo, useState } from 'react';
2
+ import { useEffect, useId, useMemo, useRef, useState } from 'react';
3
+ import { Button } from '../Button.js';
4
+ import { ColorSwatch } from '../color-swatch/index.js';
3
5
  import { Input } from '../form/Input.js';
6
+ import { Popover } from '../overlay/Popover.js';
4
7
  import { PencilLine } from '../PencilLine.js';
5
8
  import { PencilSurface } from '../PencilSurface.js';
6
9
  import { PencilText } from '../PencilText.js';
7
10
  import { Slider } from '../slider/index.js';
11
+ import { ToggleGroup } from '../toggle-group/index.js';
12
+ import { GradientEditor, defaultGradientLabels } from './GradientEditor.js';
13
+ import { colorPaintTypes, createColorGradient, formatColorPaint, normalizeColorPaint } from './gradient.js';
8
14
  import { DEFAULT_COLOR_VALUE, colorAlphaPercent, colorRgbValue, normalizeColorValue, withColorAlpha, withColorRgb, } from './color.js';
9
15
  const defaultLabels = {
10
16
  palette: 'Open color palette',
@@ -13,57 +19,153 @@ const defaultLabels = {
13
19
  presets: 'Color presets',
14
20
  invalid: 'Use #RGB, #RGBA, #RRGGBB, or #RRGGBBAA.',
15
21
  };
16
- function presetData(preset) {
22
+ function presetData(preset, allowGradient) {
17
23
  const input = typeof preset === 'string' ? preset : preset.value;
18
- const value = normalizeColorValue(input);
24
+ const value = typeof input === 'string' || allowGradient ? normalizeColorPaint(input) : null;
19
25
  if (!value)
20
26
  return null;
21
- return { value, label: typeof preset === 'string' ? value : preset.label ?? value };
27
+ return { value, label: typeof preset === 'string' ? preset : preset.label ?? formatColorPaint(value) };
22
28
  }
23
- export function ColorPicker({ className, defaultValue = DEFAULT_COLOR_VALUE, disabled = false, id, label = 'Color', labels, name, onInvalidInput, onValueChange, presets = [], seed, showAlpha = true, value, ...props }) {
29
+ export function ColorPicker(options) {
30
+ const { allowGradient = false, colorTypes = colorPaintTypes, maxStops = 8, gradientLabels, className, defaultValue = DEFAULT_COLOR_VALUE, disabled = false, form, id, label = 'Color', labels, name, onInvalidInput, onValueChange: _onValueChange, presets = [], presentation = 'inline', seed, showAlpha = true, value, ...props } = options;
24
31
  const generatedId = useId();
25
32
  const resolvedId = id ?? `inkra-color-picker-${generatedId}`;
26
33
  const resolvedSeed = seed ?? resolvedId;
27
- const controlledValue = value === undefined ? null : normalizeColorValue(value);
28
- const normalizedDefault = normalizeColorValue(defaultValue) ?? DEFAULT_COLOR_VALUE;
34
+ const controlled = value !== undefined;
35
+ const normalized = (input) => typeof input === 'string' || allowGradient ? normalizeColorPaint(input) : null;
36
+ const controlledValue = useMemo(() => value === undefined ? null : normalized(value), [value, allowGradient]);
37
+ const normalizedDefault = normalized(defaultValue) ?? DEFAULT_COLOR_VALUE;
38
+ const defaultValueRef = useRef(normalizedDefault);
29
39
  const [uncontrolledValue, setUncontrolledValue] = useState(normalizedDefault);
30
- const currentValue = controlledValue ?? (value === undefined ? uncontrolledValue : DEFAULT_COLOR_VALUE);
31
- const [draft, setDraft] = useState(currentValue);
40
+ const currentValue = controlledValue ?? (controlled ? DEFAULT_COLOR_VALUE : uncontrolledValue);
41
+ const currentValueRef = useRef(currentValue);
42
+ const gradient = typeof currentValue === 'string' ? null : currentValue;
43
+ const currentColor = typeof currentValue === 'string' ? currentValue : currentValue.stops[0].color;
44
+ const cssValue = formatColorPaint(currentValue);
45
+ const currentType = gradient?.type ?? 'solid';
46
+ const gradientMemory = useRef(null);
47
+ const availableTypes = [...new Set(colorTypes.filter(type => colorPaintTypes.includes(type)))];
48
+ if (!availableTypes.includes(currentType))
49
+ availableTypes.unshift(currentType);
50
+ const stopLimit = Number.isFinite(maxStops) ? Math.min(64, Math.max(2, Math.floor(maxStops))) : 8;
51
+ const fieldsetRef = useRef(null);
52
+ const hiddenInputRef = useRef(null);
53
+ const composingRef = useRef(false);
54
+ const skipNextBlurRef = useRef(false);
55
+ const [draft, setDraft] = useState(currentColor);
56
+ const [open, setOpen] = useState(false);
32
57
  const copy = { ...defaultLabels, ...labels };
33
- const parsedPresets = useMemo(() => presets.map(presetData).filter((preset) => preset !== null), [presets]);
58
+ const gradientCopy = { ...defaultGradientLabels, ...gradientLabels };
59
+ const parsedPresets = useMemo(() => presets.map(preset => presetData(preset, allowGradient)).filter((preset) => preset !== null), [presets, allowGradient]);
34
60
  const inputInvalid = normalizeColorValue(draft) === null;
35
- const rgbValue = colorRgbValue(currentValue) ?? '#4b96aa';
36
- const alpha = colorAlphaPercent(currentValue);
37
- useEffect(() => setDraft(currentValue), [currentValue]);
38
- const commit = (next, source) => {
39
- if (value === undefined)
61
+ const rgbValue = colorRgbValue(currentColor) ?? '#4b96aa';
62
+ const alpha = colorAlphaPercent(currentColor);
63
+ useEffect(() => { setOpen(false); }, [disabled, presentation]);
64
+ useEffect(() => {
65
+ currentValueRef.current = currentValue;
66
+ setDraft(currentColor);
67
+ if (gradient)
68
+ gradientMemory.current = gradient;
69
+ }, [currentValue, currentColor]);
70
+ useEffect(() => {
71
+ const ownerForm = hiddenInputRef.current?.form ?? fieldsetRef.current?.form;
72
+ if (!ownerForm)
73
+ return undefined;
74
+ let timer;
75
+ const reset = (event) => {
76
+ clearTimeout(timer);
77
+ timer = setTimeout(() => {
78
+ if (event.defaultPrevented)
79
+ return;
80
+ const restored = controlled ? currentValueRef.current : defaultValueRef.current;
81
+ if (!controlled)
82
+ setUncontrolledValue(restored);
83
+ setDraft(typeof restored === 'string' ? restored : restored.stops[0].color);
84
+ gradientMemory.current = typeof restored === 'string' ? null : restored;
85
+ if (hiddenInputRef.current)
86
+ hiddenInputRef.current.value = formatColorPaint(restored);
87
+ composingRef.current = false;
88
+ skipNextBlurRef.current = false;
89
+ }, 0);
90
+ };
91
+ ownerForm.addEventListener('reset', reset);
92
+ return () => {
93
+ clearTimeout(timer);
94
+ ownerForm.removeEventListener('reset', reset);
95
+ };
96
+ }, [controlled, form, name]);
97
+ const commit = (input, source) => {
98
+ if (disabled)
99
+ return;
100
+ const next = normalized(input);
101
+ if (!next)
102
+ return;
103
+ if (JSON.stringify(next) === JSON.stringify(currentValue)) {
104
+ setDraft(currentColor);
105
+ return;
106
+ }
107
+ if (!controlled) {
40
108
  setUncontrolledValue(next);
109
+ setDraft(typeof next === 'string' ? next : next.stops[0].color);
110
+ }
41
111
  else
42
- setDraft(currentValue);
43
- onValueChange?.(next, { source });
112
+ setDraft(currentColor);
113
+ if (options.allowGradient)
114
+ options.onValueChange?.(next, { source });
115
+ else if (typeof next === 'string')
116
+ options.onValueChange?.(next, { source });
44
117
  };
45
118
  const submitDraft = () => {
119
+ if (disabled || composingRef.current)
120
+ return;
46
121
  const normalized = normalizeColorValue(draft);
47
122
  if (normalized)
48
123
  commit(normalized, 'input');
49
124
  else
50
125
  onInvalidInput?.(draft);
51
126
  };
52
- return _jsxs("fieldset", { ...props, className: ['inkra-color-picker', className].filter(Boolean).join(' '), "data-alpha": showAlpha || undefined, "data-controlled": value !== undefined || undefined, "data-invalid-value": value !== undefined && controlledValue === null || undefined, disabled: disabled, id: resolvedId, children: [_jsx("legend", { className: "inkra-color-picker__legend", children: typeof label === 'string' ? _jsx(PencilText, { seed: `${resolvedSeed}-legend`, children: label }) : label }), _jsxs("div", { className: "inkra-color-picker__main", children: [_jsxs("label", { className: "inkra-color-picker__palette", "aria-label": copy.palette, title: copy.palette, children: [_jsx("span", { className: "inkra-color-picker__checker", "aria-hidden": "true" }), _jsx("span", { className: "inkra-color-picker__current", "aria-hidden": "true", style: { backgroundColor: currentValue } }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-color-picker__palette-outline", variant: "outline", seed: `${resolvedSeed}-palette` }), _jsx("input", { "aria-label": copy.palette, className: "inkra-color-picker__native", disabled: disabled, type: "color", value: rgbValue, onChange: event => {
53
- const next = withColorRgb(currentValue, event.currentTarget.value);
127
+ // With no usable presets, retain the editor rather than rendering an empty control.
128
+ const swatchesOnly = presentation === 'swatches' && parsedPresets.length > 0;
129
+ const editor = _jsxs("div", { className: "inkra-color-picker__editor", "data-presentation": presentation, children: [allowGradient && !swatchesOnly && _jsx(ToggleGroup.Root, { className: "inkra-color-picker__types", "aria-label": gradientCopy.type, disabled: disabled, size: "sm", value: [currentType], onValueChange: values => {
130
+ const type = values[0];
131
+ if (!type || type === currentType)
132
+ return;
133
+ if (type === 'solid')
134
+ commit(currentColor, 'type');
135
+ else
136
+ commit(gradient ? { ...gradient, type } : gradientMemory.current ? { ...gradientMemory.current, type } : createColorGradient(type, currentColor), 'type');
137
+ }, children: availableTypes.map(type => _jsx(ToggleGroup.Item, { value: type, children: gradientCopy[type] }, type)) }), !swatchesOnly && gradient && _jsx(GradientEditor, { value: gradient, disabled: disabled, maxStops: stopLimit, labels: gradientCopy, onChange: commit, renderColorEditor: (color, onChange) => _jsx(ColorPicker, { label: gradientCopy.stopColor, labels: labels, disabled: disabled, showAlpha: showAlpha, value: color, onValueChange: onChange, seed: `${resolvedSeed}-stop` }) }), !swatchesOnly && !gradient && _jsxs(_Fragment, { children: [_jsxs("div", { className: "inkra-color-picker__main", children: [_jsxs("label", { className: "inkra-color-picker__palette", "aria-label": copy.palette, title: copy.palette, children: [_jsx("span", { className: "inkra-color-picker__checker", "aria-hidden": "true" }), _jsx("span", { className: "inkra-color-picker__current", "aria-hidden": "true", style: { backgroundColor: currentColor } }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-color-picker__palette-outline", variant: "outline", seed: `${resolvedSeed}-palette` }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-color-picker__focus", variant: "outline", seed: `${resolvedSeed}-palette-focus` }), _jsx("input", { "aria-label": copy.palette, className: "inkra-color-picker__native", disabled: disabled, type: "color", value: rgbValue, onChange: event => {
138
+ const next = withColorRgb(currentColor, event.currentTarget.value);
139
+ if (next)
140
+ commit(next, 'palette');
141
+ } })] }), _jsxs("div", { className: "inkra-color-picker__value-field", children: [_jsx("label", { className: "inkra-color-picker__label", htmlFor: `${resolvedId}-value`, children: _jsx(PencilText, { seed: `${resolvedSeed}-value-label`, children: copy.value }) }), _jsx(Input, { id: `${resolvedId}-value`, "aria-describedby": inputInvalid ? `${resolvedId}-error` : undefined, "aria-invalid": inputInvalid || undefined, autoCapitalize: "off", autoComplete: "off", className: "inkra-color-picker__input", disabled: disabled, inputMode: "text", spellCheck: false, value: draft, onBlur: () => {
142
+ if (skipNextBlurRef.current) {
143
+ skipNextBlurRef.current = false;
144
+ return;
145
+ }
146
+ submitDraft();
147
+ }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { composingRef.current = false; }, onKeyDown: event => {
148
+ if (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)
149
+ return;
150
+ if (event.key === 'Enter') {
151
+ event.preventDefault();
152
+ skipNextBlurRef.current = true;
153
+ submitDraft();
154
+ }
155
+ if (event.key === 'Escape') {
156
+ event.preventDefault();
157
+ skipNextBlurRef.current = true;
158
+ setDraft(currentColor);
159
+ }
160
+ }, onValueChange: next => {
161
+ skipNextBlurRef.current = false;
162
+ setDraft(next);
163
+ } }), _jsx("span", { className: "inkra-color-picker__error", id: `${resolvedId}-error`, "aria-live": "polite", hidden: !inputInvalid, children: inputInvalid ? copy.invalid : '' })] })] }), showAlpha ? _jsxs("div", { className: "inkra-color-picker__alpha", children: [_jsxs("div", { className: "inkra-color-picker__alpha-heading", children: [_jsx("span", { id: `${resolvedId}-alpha-label`, children: _jsx(PencilText, { seed: `${resolvedSeed}-alpha-label`, children: copy.alpha }) }), _jsxs("output", { htmlFor: `${resolvedId}-alpha`, children: [alpha, "%"] })] }), _jsx(Slider, { "aria-labelledby": `${resolvedId}-alpha-label`, disabled: disabled, id: `${resolvedId}-alpha`, max: 100, min: 0, seed: `${resolvedSeed}-alpha`, value: [alpha], getValueLabel: number => `${number}%`, onValueChange: values => {
164
+ const next = withColorAlpha(currentColor, values[0]);
54
165
  if (next)
55
- commit(next, 'palette');
56
- } })] }), _jsxs("div", { className: "inkra-color-picker__value-field", children: [_jsx("label", { className: "inkra-color-picker__label", htmlFor: `${resolvedId}-value`, children: _jsx(PencilText, { seed: `${resolvedSeed}-value-label`, children: copy.value }) }), _jsx(Input, { id: `${resolvedId}-value`, "aria-describedby": inputInvalid ? `${resolvedId}-error` : undefined, "aria-invalid": inputInvalid || undefined, autoCapitalize: "off", autoComplete: "off", className: "inkra-color-picker__input", disabled: disabled, inputMode: "text", spellCheck: false, value: draft, onBlur: submitDraft, onKeyDown: event => {
57
- if (event.key === 'Enter') {
58
- event.preventDefault();
59
- submitDraft();
60
- }
61
- if (event.key === 'Escape')
62
- setDraft(currentValue);
63
- }, onValueChange: setDraft }), _jsx("span", { className: "inkra-color-picker__error", id: `${resolvedId}-error`, "aria-live": "polite", children: inputInvalid ? copy.invalid : '' })] })] }), showAlpha ? _jsxs("div", { className: "inkra-color-picker__alpha", children: [_jsxs("div", { className: "inkra-color-picker__alpha-heading", children: [_jsx("span", { id: `${resolvedId}-alpha-label`, children: _jsx(PencilText, { seed: `${resolvedSeed}-alpha-label`, children: copy.alpha }) }), _jsxs("output", { htmlFor: `${resolvedId}-alpha`, children: [alpha, "%"] })] }), _jsx(Slider, { "aria-labelledby": `${resolvedId}-alpha-label`, disabled: disabled, id: `${resolvedId}-alpha`, max: 100, min: 0, seed: `${resolvedSeed}-alpha`, value: [alpha], getValueLabel: number => `${number}%`, onValueChange: values => {
64
- const next = withColorAlpha(currentValue, values[0]);
65
- if (next)
66
- commit(next, 'alpha');
67
- } })] }) : null, parsedPresets.length ? _jsxs(_Fragment, { children: [_jsx(PencilLine, { className: "inkra-color-picker__divider", seed: `${resolvedSeed}-divider` }), _jsx("div", { className: "inkra-color-picker__presets", role: "group", "aria-label": copy.presets, children: parsedPresets.map((preset, index) => _jsxs("button", { type: "button", "aria-label": preset.label, "aria-pressed": preset.value === currentValue, className: "inkra-color-picker__preset", disabled: disabled, title: preset.label, onClick: () => commit(preset.value, 'preset'), children: [_jsx("span", { className: "inkra-color-picker__checker", "aria-hidden": "true" }), _jsx("span", { className: "inkra-color-picker__preset-color", "aria-hidden": "true", style: { backgroundColor: preset.value } }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-color-picker__preset-outline", variant: preset.value === currentValue ? 'selection' : 'outline', seed: `${resolvedSeed}-preset-${index}` })] }, `${preset.value}-${index}`)) })] }) : null, name ? _jsx("input", { type: "hidden", name: name, value: currentValue }) : null] });
166
+ commit(next, 'alpha');
167
+ } })] }) : null] }), parsedPresets.length ? _jsxs(_Fragment, { children: [!swatchesOnly && _jsx(PencilLine, { className: "inkra-color-picker__divider", seed: `${resolvedSeed}-divider` }), _jsx("div", { className: "inkra-color-picker__presets", role: "group", "aria-label": copy.presets, children: parsedPresets.map((preset, index) => _jsx(ColorSwatch, { color: typeof preset.value === 'string' ? preset.value : preset.value.stops[0].color, background: typeof preset.value === 'string' ? undefined : formatColorPaint(preset.value), label: preset.label, selected: formatColorPaint(preset.value) === cssValue, className: "inkra-color-picker__preset", disabled: disabled, seed: `${resolvedSeed}-preset-${index}`, onClick: () => commit(preset.value, 'preset') }, `${formatColorPaint(preset.value)}-${index}`)) })] }) : null] });
168
+ return _jsxs("fieldset", { ...props, className: ['inkra-color-picker', className].filter(Boolean).join(' '), "data-presentation": presentation, "data-alpha": showAlpha || undefined, "data-controlled": value !== undefined || undefined, "data-invalid-value": value !== undefined && controlledValue === null || undefined, disabled: disabled, form: form, id: resolvedId, ref: fieldsetRef, children: [_jsx("legend", { className: "inkra-color-picker__legend", id: `${resolvedId}-legend`, children: typeof label === 'string' ? _jsx(PencilText, { seed: `${resolvedSeed}-legend`, children: label }) : label }), presentation === 'popover' ? _jsxs(Popover.Root, { open: open && !disabled, onOpenChange: setOpen, children: [_jsx(Popover.Trigger, { asChild: true, children: _jsx(Button, { variant: "outline", className: "inkra-color-picker__trigger", disabled: disabled, "aria-label": `${copy.palette}: ${typeof currentValue === 'string' ? currentValue : gradientCopy[currentValue.type]}`, children: _jsxs("span", { className: "inkra-color-picker__trigger-content", children: [_jsxs("span", { className: "inkra-color-picker__preview", "aria-hidden": "true", children: [_jsx("span", { className: "inkra-color-picker__checker" }), _jsx("span", { className: "inkra-color-picker__current", style: { background: cssValue } })] }), _jsx(PencilText, { children: typeof currentValue === 'string' ? currentValue : gradientCopy[currentValue.type] })] }) }) }), _jsx(Popover.Portal, { children: _jsx(Popover.Content, { className: "inkra-color-picker__popover", sideOffset: 8, align: "start", "aria-label": typeof label === 'string' ? label : copy.palette, "aria-describedby": undefined, children: editor }) })] }) : editor, name ? _jsx("input", { ref: hiddenInputRef, type: "hidden", form: form, name: name, value: cssValue }) : null] });
68
169
  }
69
170
  export { DEFAULT_COLOR_VALUE, colorAlphaPercent, colorRgbValue, formatColorValue, normalizeColorValue, parseColorValue, withColorAlpha, withColorRgb, } from './color.js';
171
+ export { createColorGradient, formatColorPaint, normalizeColorPaint } from './gradient.js';
@@ -0,0 +1,10 @@
1
+ import type { ButtonHTMLAttributes, CSSProperties } from 'react';
2
+ export interface ColorSwatchProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'color' | 'aria-pressed'> {
3
+ color: string;
4
+ background?: CSSProperties['background'];
5
+ label: string;
6
+ selected?: boolean;
7
+ size?: 'sm' | 'md' | 'lg';
8
+ seed?: string | number;
9
+ }
10
+ export declare const ColorSwatch: import("react").ForwardRefExoticComponent<ColorSwatchProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useId } from 'react';
3
+ import { Button } from '../Button.js';
4
+ import { PencilSurface } from '../PencilSurface.js';
5
+ export const ColorSwatch = forwardRef(function ColorSwatch({ color, background, label, selected = false, size = 'md', seed, className, ...props }, ref) {
6
+ const id = useId();
7
+ const resolvedSeed = seed ?? `inkra-color-swatch-${id}`;
8
+ return _jsx(Button, { title: label, ...props, "aria-label": props['aria-label'] ?? label, "aria-pressed": selected, className: ['inkra-color-swatch', className].filter(Boolean).join(' '), "data-swatch-size": size, ref: ref, seed: resolvedSeed, size: size === 'lg' ? 'icon-lg' : 'icon', variant: selected ? 'outline' : 'ghost', children: _jsx("span", { className: "inkra-color-swatch__pigment", style: { color }, "aria-hidden": "true", children: background ? _jsx("span", { className: "inkra-color-swatch__background", style: { background }, children: _jsx(PencilSurface, { as: "span", className: "inkra-color-swatch__wash", variant: "outline", seed: `${resolvedSeed}-wash` }) }) : _jsx(PencilSurface, { as: "span", className: "inkra-color-swatch__wash", variant: "wash", seed: `${resolvedSeed}-wash`, style: { '--inkra-accent': 'currentColor' } }) }) });
9
+ });
@@ -1,6 +1,9 @@
1
1
  import { Command as Primitive } from 'cmdk';
2
2
  import type { ComponentPropsWithoutRef, HTMLAttributes, ReactNode } from 'react';
3
- export type CommandRootProps = Omit<ComponentPropsWithoutRef<typeof Primitive>, 'asChild'>;
3
+ export type CommandRootProps = Omit<ComponentPropsWithoutRef<typeof Primitive>, 'asChild'> & {
4
+ /** Disable when a parent dialog already supplies the paper and outline. */
5
+ surface?: boolean;
6
+ };
4
7
  export type CommandInputProps = Omit<ComponentPropsWithoutRef<typeof Primitive.Input>, 'asChild'>;
5
8
  export type CommandListProps = Omit<ComponentPropsWithoutRef<typeof Primitive.List>, 'asChild'>;
6
9
  export interface CommandItemProps extends Omit<ComponentPropsWithoutRef<typeof Primitive.Item>, 'asChild'> {
@@ -12,7 +15,26 @@ export type CommandEmptyProps = Omit<ComponentPropsWithoutRef<typeof Primitive.E
12
15
  export type CommandSeparatorProps = Omit<ComponentPropsWithoutRef<typeof Primitive.Separator>, 'asChild' | 'children'>;
13
16
  export type CommandShortcutProps = HTMLAttributes<HTMLElement>;
14
17
  export declare const Command: {
15
- Root: import("react").ForwardRefExoticComponent<CommandRootProps & import("react").RefAttributes<HTMLDivElement>>;
18
+ Root: import("react").ForwardRefExoticComponent<Omit<Omit<{
19
+ children?: React.ReactNode;
20
+ } & Pick<Pick<import("react").DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof HTMLAttributes<HTMLDivElement>> & {
21
+ ref?: React.Ref<HTMLDivElement>;
22
+ } & {
23
+ asChild?: boolean;
24
+ }, "key" | keyof HTMLAttributes<HTMLDivElement> | "asChild"> & {
25
+ label?: string;
26
+ shouldFilter?: boolean;
27
+ filter?: (value: string, search: string, keywords?: string[]) => number;
28
+ defaultValue?: string;
29
+ value?: string;
30
+ onValueChange?: (value: string) => void;
31
+ loop?: boolean;
32
+ disablePointerSelection?: boolean;
33
+ vimBindings?: boolean;
34
+ } & import("react").RefAttributes<HTMLDivElement>, "ref">, "asChild"> & {
35
+ /** Disable when a parent dialog already supplies the paper and outline. */
36
+ surface?: boolean;
37
+ } & import("react").RefAttributes<HTMLDivElement>>;
16
38
  Input: import("react").ForwardRefExoticComponent<CommandInputProps & import("react").RefAttributes<HTMLInputElement>>;
17
39
  List: import("react").ForwardRefExoticComponent<CommandListProps & import("react").RefAttributes<HTMLDivElement>>;
18
40
  Group: import("react").ForwardRefExoticComponent<CommandGroupProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,28 +1,51 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Command as Primitive } from 'cmdk';
3
- import { forwardRef, useRef } from 'react';
3
+ import { __iconData as searchIcon } from 'lucide-react/dist/esm/icons/search.mjs';
4
+ import { forwardRef, useCallback, useRef } from 'react';
4
5
  import { PencilSurface } from '../PencilSurface.js';
5
6
  import { PencilText } from '../PencilText.js';
6
7
  import { PencilLine } from '../PencilLine.js';
8
+ import { EditablePencilText } from '../form/EditablePencilText.js';
9
+ import { PencilIcon } from '../PencilIcon.js';
10
+ const searchPaths = searchIcon.node.map(([tag, attributes]) => {
11
+ if (tag === 'circle') {
12
+ const cx = Number(attributes.cx);
13
+ const cy = Number(attributes.cy);
14
+ const r = Number(attributes.r);
15
+ return { d: `M${cx - r} ${cy}a${r} ${r} 0 1 0 ${r * 2} 0a${r} ${r} 0 1 0 -${r * 2} 0` };
16
+ }
17
+ return { d: attributes.d };
18
+ });
7
19
  function classes(base, extra) { return [base, extra].filter(Boolean).join(' '); }
8
20
  function copy(value) { return typeof value === 'string' ? _jsx(PencilText, { children: value }) : value; }
9
- const Root = forwardRef(function Root({ className, children, loop = true, onKeyDown, onCompositionStartCapture, onCompositionEndCapture, ...props }, ref) {
21
+ function assignRef(ref, value) {
22
+ if (typeof ref === 'function')
23
+ ref(value);
24
+ else if (ref)
25
+ ref.current = value;
26
+ }
27
+ const Root = forwardRef(function Root({ className, children, loop = true, surface = true, onKeyDown, onCompositionStartCapture, onCompositionEndCapture, ...props }, ref) {
10
28
  const composing = useRef(false);
11
29
  return _jsx(Primitive, { ...props, ref: ref, loop: loop, className: classes('inkra-command', className), onCompositionStartCapture: event => { composing.current = true; onCompositionStartCapture?.(event); }, onCompositionEndCapture: event => { composing.current = false; onCompositionEndCapture?.(event); }, onKeyDown: event => {
12
30
  // Capture composition state as well as native flags before cmdk handles Enter.
13
31
  if (event.key === 'Enter' && (composing.current || event.nativeEvent.isComposing || event.keyCode === 229))
14
32
  event.preventDefault();
15
33
  onKeyDown?.(event);
16
- }, children: _jsx(PencilSurface, { variant: "paper", radius: 8, className: "inkra-command__paper", children: children }) });
34
+ }, children: surface ? _jsx(PencilSurface, { variant: "paper", radius: 8, className: "inkra-command__paper", children: children }) : children });
17
35
  });
18
- const Input = forwardRef(function Input({ className, ...props }, ref) {
19
- return _jsxs("div", { className: "inkra-command__input-wrap", children: [_jsx(Primitive.Input, { ...props, ref: ref, className: classes('inkra-command__input', className) }), _jsx(PencilLine, { className: "inkra-command__input-line" })] });
36
+ const Input = forwardRef(function Input({ className, defaultValue, placeholder, value, ...props }, ref) {
37
+ const inputRef = useRef(null);
38
+ const captureRef = useCallback((node) => {
39
+ inputRef.current = node;
40
+ assignRef(ref, node);
41
+ }, [ref]);
42
+ return _jsxs("div", { className: "inkra-command__input-wrap inkra-field-control-hit", children: [_jsxs("div", { className: "inkra-command__input-frame", children: [_jsx("span", { "aria-hidden": "true", className: "inkra-command__input-icon", children: _jsx(PencilIcon, { paths: searchPaths, size: 22, sourceSize: 24 }) }), _jsx(Primitive.Input, { ...props, defaultValue: defaultValue, placeholder: placeholder, value: value, ref: captureRef, className: classes('inkra-command__input', className) }), _jsx(EditablePencilText, { controlRef: inputRef, defaultValue: defaultValue, placeholder: placeholder, seed: "command-input", value: value })] }), _jsx(PencilLine, { className: "inkra-command__input-line" }), _jsx(PencilLine, { className: "inkra-command__input-focus-line", seed: "command-input-focus", weight: 2.2 })] });
20
43
  });
21
44
  const List = forwardRef(function List({ className, ...props }, ref) {
22
45
  return _jsx(Primitive.List, { ...props, ref: ref, className: classes('inkra-command__list', className) });
23
46
  });
24
47
  const Item = forwardRef(function Item({ className, children, icon, shortcut, ...props }, ref) {
25
- return _jsxs(Primitive.Item, { ...props, ref: ref, className: classes('inkra-command__item', className), children: [_jsx(PencilSurface, { as: "span", variant: "selection", "aria-hidden": "true", className: "inkra-command__highlight" }), icon != null && _jsx("span", { "aria-hidden": "true", className: "inkra-command__icon", children: icon }), _jsx("span", { className: "inkra-command__label", children: copy(children) }), shortcut != null && _jsx(Shortcut, { children: shortcut })] });
48
+ return _jsxs(Primitive.Item, { ...props, ref: ref, className: classes('inkra-command__item', className), children: [_jsx(PencilSurface, { as: "span", variant: "selection", "aria-hidden": "true", className: "inkra-command__highlight" }), _jsx(PencilSurface, { as: "span", variant: "outline", "aria-hidden": "true", className: "inkra-command__item-focus" }), icon != null && _jsx("span", { "aria-hidden": "true", className: "inkra-command__icon", children: icon }), _jsx("span", { className: "inkra-command__label", children: copy(children) }), shortcut != null && _jsx(Shortcut, { children: shortcut })] });
26
49
  });
27
50
  const Group = forwardRef(function Group({ className, heading, ...props }, ref) {
28
51
  return _jsx(Primitive.Group, { ...props, heading: copy(heading), ref: ref, className: classes('inkra-command__group', className) });
@@ -43,6 +43,11 @@ export interface AlertProps extends HTMLAttributes<HTMLDivElement> {
43
43
  size?: ContentSize;
44
44
  appearance?: 'soft' | 'outline';
45
45
  actions?: ReactNode;
46
+ closable?: boolean;
47
+ open?: boolean;
48
+ defaultOpen?: boolean;
49
+ onOpenChange?: (open: boolean) => void;
50
+ closeLabel?: string;
46
51
  }
47
52
  export declare const Alert: {
48
53
  Root: import("react").ForwardRefExoticComponent<AlertProps & import("react").RefAttributes<HTMLDivElement>>;