@inkra/react 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 (245) hide show
  1. package/README.md +113 -3
  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.js +14 -4
  9. package/dist/audio-player/index.d.ts +2 -0
  10. package/dist/audio-player/index.js +131 -34
  11. package/dist/breadcrumb/index.js +4 -1
  12. package/dist/calendar/index.d.ts +4 -1
  13. package/dist/calendar/index.js +37 -12
  14. package/dist/calendar/locales.d.ts +7 -0
  15. package/dist/calendar/locales.js +2 -0
  16. package/dist/carousel/index.js +20 -10
  17. package/dist/choice-grid/index.d.ts +6 -1
  18. package/dist/choice-grid/index.js +63 -33
  19. package/dist/color-picker/GradientEditor.d.ts +40 -0
  20. package/dist/color-picker/GradientEditor.js +64 -0
  21. package/dist/color-picker/gradient.d.ts +25 -0
  22. package/dist/color-picker/gradient.js +60 -0
  23. package/dist/color-picker/index.d.ts +27 -7
  24. package/dist/color-picker/index.js +144 -40
  25. package/dist/color-swatch/index.d.ts +10 -0
  26. package/dist/color-swatch/index.js +9 -0
  27. package/dist/command/index.js +27 -4
  28. package/dist/content/index.d.ts +5 -0
  29. package/dist/content/index.js +15 -3
  30. package/dist/context-menu/index.js +1 -1
  31. package/dist/data-table/index.d.ts +7 -1
  32. package/dist/data-table/index.js +67 -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 +20 -21
  37. package/dist/day-agenda/index.d.ts +6 -1
  38. package/dist/day-agenda/index.js +49 -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 +15 -13
  44. package/dist/drawer/index.js +11 -5
  45. package/dist/editable/index.d.ts +24 -2
  46. package/dist/editable/index.js +148 -40
  47. package/dist/empty/illustrations.d.ts +8 -0
  48. package/dist/empty/illustrations.js +78 -0
  49. package/dist/empty/index.d.ts +6 -1
  50. package/dist/empty/index.js +11 -3
  51. package/dist/flip/index.js +28 -8
  52. package/dist/form/Field.js +11 -10
  53. package/dist/form/Input.js +4 -1
  54. package/dist/form/Textarea.d.ts +3 -1
  55. package/dist/form/Textarea.js +9 -5
  56. package/dist/heatmap/index.js +4 -1
  57. package/dist/hotkey/index.d.ts +1 -0
  58. package/dist/hotkey/index.js +9 -4
  59. package/dist/hover-card/index.js +1 -1
  60. package/dist/i18n/catalog.d.ts +5 -0
  61. package/dist/i18n/catalog.js +289 -0
  62. package/dist/i18n/index.d.ts +23 -0
  63. package/dist/i18n/index.js +50 -0
  64. package/dist/image-cropper/index.js +21 -17
  65. package/dist/image-viewer/VideoResource.d.ts +21 -0
  66. package/dist/image-viewer/VideoResource.js +196 -0
  67. package/dist/image-viewer/index.d.ts +14 -0
  68. package/dist/image-viewer/index.js +141 -86
  69. package/dist/image-viewer/lucideGeometry.d.ts +3 -0
  70. package/dist/image-viewer/lucideGeometry.js +19 -0
  71. package/dist/index.d.ts +4 -0
  72. package/dist/index.js +4 -0
  73. package/dist/input-number/index.js +13 -5
  74. package/dist/internal/cache.d.ts +4 -0
  75. package/dist/internal/cache.js +18 -1
  76. package/dist/internal/iconGeometry.d.ts +2 -0
  77. package/dist/internal/iconGeometry.js +32 -0
  78. package/dist/internal/renderTokens.d.ts +4 -0
  79. package/dist/internal/renderTokens.js +4 -0
  80. package/dist/internal/stroke.d.ts +7 -0
  81. package/dist/internal/stroke.js +63 -7
  82. package/dist/internal/surfaceBudget.d.ts +20 -0
  83. package/dist/internal/surfaceBudget.js +69 -0
  84. package/dist/internal/surfaceRenderer.js +87 -28
  85. package/dist/internal/surfaceVisibility.d.ts +3 -0
  86. package/dist/internal/surfaceVisibility.js +60 -0
  87. package/dist/internal/textRenderer.d.ts +3 -0
  88. package/dist/internal/textRenderer.js +13 -5
  89. package/dist/internal/usePencilSurface.js +17 -1
  90. package/dist/internal/wash.js +4 -3
  91. package/dist/journey-map/index.d.ts +1 -1
  92. package/dist/journey-map/index.js +6 -3
  93. package/dist/journey-map/terrain.js +10 -5
  94. package/dist/keypad/index.js +4 -1
  95. package/dist/layout/index.js +1 -1
  96. package/dist/log/index.js +42 -33
  97. package/dist/markdown/CodeBlock.d.ts +19 -0
  98. package/dist/markdown/CodeBlock.js +86 -0
  99. package/dist/markdown/Deletion.d.ts +2 -0
  100. package/dist/markdown/Deletion.js +69 -0
  101. package/dist/markdown/icons.d.ts +14 -0
  102. package/dist/markdown/icons.js +16 -0
  103. package/dist/markdown/index.d.ts +17 -0
  104. package/dist/markdown/index.js +177 -0
  105. package/dist/node-graph/index.d.ts +3 -1
  106. package/dist/node-graph/index.js +63 -28
  107. package/dist/number-ticker/index.js +4 -1
  108. package/dist/number-ticker/precision.js +9 -2
  109. package/dist/overflow-list/index.js +9 -6
  110. package/dist/overlay/Overlay.js +8 -3
  111. package/dist/overlay/Popover.js +3 -3
  112. package/dist/overlay/themeBridge.js +14 -2
  113. package/dist/pagination/index.d.ts +10 -1
  114. package/dist/pagination/index.js +13 -9
  115. package/dist/pan-zoom/index.d.ts +2 -0
  116. package/dist/pan-zoom/index.js +12 -8
  117. package/dist/pencil.d.ts +1 -0
  118. package/dist/pencil.js +4 -4
  119. package/dist/radar/index.d.ts +2 -0
  120. package/dist/radar/index.js +44 -21
  121. package/dist/range-slider/index.js +4 -3
  122. package/dist/rating/index.d.ts +4 -0
  123. package/dist/rating/index.js +131 -24
  124. package/dist/resizable/index.d.ts +1 -1
  125. package/dist/resizable/index.js +5 -2
  126. package/dist/scroll-area/index.d.ts +1 -1
  127. package/dist/scroll-area/index.js +5 -2
  128. package/dist/select/MultipleSelect.js +5 -4
  129. package/dist/select/SearchableSelect.js +64 -7
  130. package/dist/select/Select.d.ts +1 -0
  131. package/dist/select/Select.js +6 -2
  132. package/dist/select/StepperSelect.d.ts +29 -0
  133. package/dist/select/StepperSelect.js +122 -0
  134. package/dist/select/index.d.ts +2 -0
  135. package/dist/select/index.js +1 -0
  136. package/dist/select/stepperModel.d.ts +7 -0
  137. package/dist/select/stepperModel.js +29 -0
  138. package/dist/selection/Checkbox.js +4 -3
  139. package/dist/selection/RadioGroup.js +29 -13
  140. package/dist/selection/Switch.js +4 -3
  141. package/dist/selection/useNativeChecked.d.ts +2 -1
  142. package/dist/selection/useNativeChecked.js +26 -8
  143. package/dist/slider/index.js +50 -6
  144. package/dist/sortable/index.js +17 -11
  145. package/dist/sparkline/index.js +21 -6
  146. package/dist/sparkline/model.js +11 -4
  147. package/dist/steps/index.js +4 -1
  148. package/dist/styles/accordion.css +228 -0
  149. package/dist/styles/alert-dialog.css +139 -0
  150. package/dist/styles/audio-player.css +22 -0
  151. package/dist/styles/base.css +638 -0
  152. package/dist/styles/breadcrumb.css +7 -0
  153. package/dist/styles/calendar.css +62 -0
  154. package/dist/styles/carousel.css +173 -0
  155. package/dist/styles/choice-grid.css +226 -0
  156. package/dist/styles/color-picker.css +281 -0
  157. package/dist/styles/color-swatch.css +47 -0
  158. package/dist/styles/command.css +52 -0
  159. package/dist/styles/content.css +79 -0
  160. package/dist/styles/context-menu.css +35 -0
  161. package/dist/styles/data-table.css +31 -0
  162. package/dist/styles/date-picker.css +107 -0
  163. package/dist/styles/day-agenda.css +30 -0
  164. package/dist/styles/display.css +304 -0
  165. package/dist/styles/dockable-panel.css +110 -0
  166. package/dist/styles/drawer.css +133 -0
  167. package/dist/styles/dropdown-menu.css +68 -0
  168. package/dist/styles/editable.css +145 -0
  169. package/dist/styles/empty.css +108 -0
  170. package/dist/styles/flip.css +101 -0
  171. package/dist/styles/form.css +634 -0
  172. package/dist/styles/heatmap.css +256 -0
  173. package/dist/styles/hotkey.css +189 -0
  174. package/dist/styles/hover-card.css +27 -0
  175. package/dist/styles/image-cropper.css +205 -0
  176. package/dist/styles/image-viewer.css +422 -0
  177. package/dist/styles/input-number.css +21 -0
  178. package/dist/styles/journey-map.css +19 -0
  179. package/dist/styles/keypad.css +92 -0
  180. package/dist/styles/layout.css +72 -0
  181. package/dist/styles/leaflet.css +661 -0
  182. package/dist/styles/log.css +203 -0
  183. package/dist/styles/markdown.css +379 -0
  184. package/dist/styles/node-graph.css +142 -0
  185. package/dist/styles/number-ticker.css +83 -0
  186. package/dist/styles/overflow-list.css +51 -0
  187. package/dist/styles/overlay.css +284 -0
  188. package/dist/styles/pagination.css +21 -0
  189. package/dist/styles/pan-zoom.css +136 -0
  190. package/dist/styles/radar.css +1 -0
  191. package/dist/styles/rating.css +121 -0
  192. package/dist/styles/resizable.css +108 -0
  193. package/dist/styles/scroll-area.css +122 -0
  194. package/dist/styles/select.css +554 -0
  195. package/dist/styles/selection.css +264 -0
  196. package/dist/styles/slider.css +145 -0
  197. package/dist/styles/slot.css +109 -0
  198. package/dist/styles/sortable.css +159 -0
  199. package/dist/styles/sparkline.css +24 -0
  200. package/dist/styles/steps.css +27 -0
  201. package/dist/styles/tabs.css +79 -0
  202. package/dist/styles/tag-input.css +213 -0
  203. package/dist/styles/time-picker.css +66 -0
  204. package/dist/styles/timeline.css +24 -0
  205. package/dist/styles/toast.css +160 -0
  206. package/dist/styles/toggle-group.css +107 -0
  207. package/dist/styles/tooltip.css +78 -0
  208. package/dist/styles/transfer.css +148 -0
  209. package/dist/styles/tree-select.css +131 -0
  210. package/dist/styles/tree.css +59 -0
  211. package/dist/styles/typewriter.css +62 -0
  212. package/dist/styles/upload.css +60 -0
  213. package/dist/styles/virtual-list.css +112 -0
  214. package/dist/styles.css +2091 -421
  215. package/dist/tabs/index.d.ts +9 -0
  216. package/dist/tabs/index.js +69 -16
  217. package/dist/tabs/useOverflow.d.ts +20 -0
  218. package/dist/tabs/useOverflow.js +145 -0
  219. package/dist/tag-input/index.d.ts +7 -2
  220. package/dist/tag-input/index.js +81 -42
  221. package/dist/theme/index.d.ts +11 -0
  222. package/dist/theme/index.js +23 -0
  223. package/dist/time-picker/index.js +45 -27
  224. package/dist/timeline/index.js +4 -1
  225. package/dist/toast/index.d.ts +19 -11
  226. package/dist/toast/index.js +51 -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.js +3 -3
  233. package/dist/transfer/index.d.ts +5 -0
  234. package/dist/transfer/index.js +120 -53
  235. package/dist/transfer/model.d.ts +4 -0
  236. package/dist/transfer/model.js +37 -11
  237. package/dist/tree/index.d.ts +5 -1
  238. package/dist/tree/index.js +11 -6
  239. package/dist/tree-select/index.js +70 -31
  240. package/dist/tree-select/model.d.ts +11 -5
  241. package/dist/tree-select/model.js +111 -83
  242. package/dist/typewriter/index.js +10 -10
  243. package/dist/upload/index.js +50 -33
  244. package/dist/virtual-list/index.js +11 -6
  245. package/package.json +423 -3
@@ -1,69 +1,173 @@
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 { useInkraLocale } from '../i18n/index.js';
3
+ import { useEffect, useId, useMemo, useRef, useState } from 'react';
4
+ import { Button } from '../Button.js';
5
+ import { ColorSwatch } from '../color-swatch/index.js';
3
6
  import { Input } from '../form/Input.js';
7
+ import { Popover } from '../overlay/Popover.js';
4
8
  import { PencilLine } from '../PencilLine.js';
5
9
  import { PencilSurface } from '../PencilSurface.js';
6
10
  import { PencilText } from '../PencilText.js';
7
11
  import { Slider } from '../slider/index.js';
12
+ import { ToggleGroup } from '../toggle-group/index.js';
13
+ import { GradientEditor, defaultGradientLabels } from './GradientEditor.js';
14
+ import { colorPaintTypes, createColorGradient, formatColorPaint, normalizeColorPaint } from './gradient.js';
8
15
  import { DEFAULT_COLOR_VALUE, colorAlphaPercent, colorRgbValue, normalizeColorValue, withColorAlpha, withColorRgb, } from './color.js';
9
- const defaultLabels = {
10
- palette: 'Open color palette',
11
- value: 'Color value',
12
- alpha: 'Opacity',
13
- presets: 'Color presets',
14
- invalid: 'Use #RGB, #RGBA, #RRGGBB, or #RRGGBBAA.',
15
- };
16
- function presetData(preset) {
16
+ const defaultLabels = (t) => ({
17
+ palette: t('color.palette'),
18
+ value: t('color.value'),
19
+ alpha: t('color.alpha'),
20
+ presets: t('color.presets'),
21
+ invalid: t('color.invalid'),
22
+ });
23
+ function presetData(preset, allowGradient) {
17
24
  const input = typeof preset === 'string' ? preset : preset.value;
18
- const value = normalizeColorValue(input);
25
+ const value = typeof input === 'string' || allowGradient ? normalizeColorPaint(input) : null;
19
26
  if (!value)
20
27
  return null;
21
- return { value, label: typeof preset === 'string' ? value : preset.label ?? value };
28
+ return { value, label: typeof preset === 'string' ? preset : preset.label ?? formatColorPaint(value) };
22
29
  }
23
- export function ColorPicker({ className, defaultValue = DEFAULT_COLOR_VALUE, disabled = false, id, label = 'Color', labels, name, onInvalidInput, onValueChange, presets = [], seed, showAlpha = true, value, ...props }) {
30
+ export function ColorPicker(options) {
31
+ const { t } = useInkraLocale();
32
+ const { allowGradient = false, colorTypes = colorPaintTypes, maxStops = 8, gradientLabels, className, defaultValue = DEFAULT_COLOR_VALUE, disabled = false, form, id, label = t('color.label'), labels, name, onInvalidInput, onValueChange: _onValueChange, presets = [], presentation = 'inline', seed, showAlpha = true, value, ...props } = options;
24
33
  const generatedId = useId();
25
34
  const resolvedId = id ?? `inkra-color-picker-${generatedId}`;
26
35
  const resolvedSeed = seed ?? resolvedId;
27
- const controlledValue = value === undefined ? null : normalizeColorValue(value);
28
- const normalizedDefault = normalizeColorValue(defaultValue) ?? DEFAULT_COLOR_VALUE;
36
+ const controlled = value !== undefined;
37
+ const normalized = (input) => typeof input === 'string' || allowGradient ? normalizeColorPaint(input) : null;
38
+ const controlledValue = useMemo(() => value === undefined ? null : normalized(value), [value, allowGradient]);
39
+ const normalizedDefault = normalized(defaultValue) ?? DEFAULT_COLOR_VALUE;
40
+ const defaultValueRef = useRef(normalizedDefault);
29
41
  const [uncontrolledValue, setUncontrolledValue] = useState(normalizedDefault);
30
- const currentValue = controlledValue ?? (value === undefined ? uncontrolledValue : DEFAULT_COLOR_VALUE);
31
- const [draft, setDraft] = useState(currentValue);
32
- const copy = { ...defaultLabels, ...labels };
33
- const parsedPresets = useMemo(() => presets.map(presetData).filter((preset) => preset !== null), [presets]);
42
+ const currentValue = controlledValue ?? (controlled ? DEFAULT_COLOR_VALUE : uncontrolledValue);
43
+ const currentValueRef = useRef(currentValue);
44
+ const gradient = typeof currentValue === 'string' ? null : currentValue;
45
+ const currentColor = typeof currentValue === 'string' ? currentValue : currentValue.stops[0].color;
46
+ const cssValue = formatColorPaint(currentValue);
47
+ const currentType = gradient?.type ?? 'solid';
48
+ const gradientMemory = useRef(null);
49
+ const availableTypes = [...new Set(colorTypes.filter(type => colorPaintTypes.includes(type)))];
50
+ if (!availableTypes.includes(currentType))
51
+ availableTypes.unshift(currentType);
52
+ const stopLimit = Number.isFinite(maxStops) ? Math.min(64, Math.max(2, Math.floor(maxStops))) : 8;
53
+ const fieldsetRef = useRef(null);
54
+ const hiddenInputRef = useRef(null);
55
+ const composingRef = useRef(false);
56
+ const skipNextBlurRef = useRef(false);
57
+ const [draft, setDraft] = useState(currentColor);
58
+ const [open, setOpen] = useState(false);
59
+ const copy = { ...defaultLabels(t), ...labels };
60
+ const gradientCopy = { ...defaultGradientLabels(t), ...gradientLabels };
61
+ const parsedPresets = useMemo(() => presets.map(preset => presetData(preset, allowGradient)).filter((preset) => preset !== null), [presets, allowGradient]);
34
62
  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)
63
+ const rgbValue = colorRgbValue(currentColor) ?? '#4b96aa';
64
+ const alpha = colorAlphaPercent(currentColor);
65
+ useEffect(() => { setOpen(false); }, [disabled, presentation]);
66
+ useEffect(() => {
67
+ currentValueRef.current = currentValue;
68
+ setDraft(currentColor);
69
+ if (gradient)
70
+ gradientMemory.current = gradient;
71
+ }, [currentValue, currentColor]);
72
+ useEffect(() => {
73
+ const ownerForm = hiddenInputRef.current?.form ?? fieldsetRef.current?.form;
74
+ if (!ownerForm)
75
+ return undefined;
76
+ let timer;
77
+ const reset = (event) => {
78
+ clearTimeout(timer);
79
+ timer = setTimeout(() => {
80
+ if (event.defaultPrevented)
81
+ return;
82
+ const restored = controlled ? currentValueRef.current : defaultValueRef.current;
83
+ if (!controlled)
84
+ setUncontrolledValue(restored);
85
+ setDraft(typeof restored === 'string' ? restored : restored.stops[0].color);
86
+ gradientMemory.current = typeof restored === 'string' ? null : restored;
87
+ if (hiddenInputRef.current)
88
+ hiddenInputRef.current.value = formatColorPaint(restored);
89
+ composingRef.current = false;
90
+ skipNextBlurRef.current = false;
91
+ }, 0);
92
+ };
93
+ ownerForm.addEventListener('reset', reset);
94
+ return () => {
95
+ clearTimeout(timer);
96
+ ownerForm.removeEventListener('reset', reset);
97
+ };
98
+ }, [controlled, form, name]);
99
+ const commit = (input, source) => {
100
+ if (disabled)
101
+ return;
102
+ const next = normalized(input);
103
+ if (!next)
104
+ return;
105
+ if (JSON.stringify(next) === JSON.stringify(currentValue)) {
106
+ setDraft(currentColor);
107
+ return;
108
+ }
109
+ if (!controlled) {
40
110
  setUncontrolledValue(next);
111
+ setDraft(typeof next === 'string' ? next : next.stops[0].color);
112
+ }
41
113
  else
42
- setDraft(currentValue);
43
- onValueChange?.(next, { source });
114
+ setDraft(currentColor);
115
+ if (options.allowGradient)
116
+ options.onValueChange?.(next, { source });
117
+ else if (typeof next === 'string')
118
+ options.onValueChange?.(next, { source });
44
119
  };
45
120
  const submitDraft = () => {
121
+ if (disabled || composingRef.current)
122
+ return;
46
123
  const normalized = normalizeColorValue(draft);
47
124
  if (normalized)
48
125
  commit(normalized, 'input');
49
126
  else
50
127
  onInvalidInput?.(draft);
51
128
  };
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);
129
+ // With no usable presets, retain the editor rather than rendering an empty control.
130
+ const swatchesOnly = presentation === 'swatches' && parsedPresets.length > 0;
131
+ 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 => {
132
+ const type = values[0];
133
+ if (!type || type === currentType)
134
+ return;
135
+ if (type === 'solid')
136
+ commit(currentColor, 'type');
137
+ else
138
+ commit(gradient ? { ...gradient, type } : gradientMemory.current ? { ...gradientMemory.current, type } : createColorGradient(type, currentColor), 'type');
139
+ }, 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 => {
140
+ const next = withColorRgb(currentColor, event.currentTarget.value);
141
+ if (next)
142
+ commit(next, 'palette');
143
+ } })] }), _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: () => {
144
+ if (skipNextBlurRef.current) {
145
+ skipNextBlurRef.current = false;
146
+ return;
147
+ }
148
+ submitDraft();
149
+ }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { composingRef.current = false; }, onKeyDown: event => {
150
+ if (event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)
151
+ return;
152
+ if (event.key === 'Enter') {
153
+ event.preventDefault();
154
+ skipNextBlurRef.current = true;
155
+ submitDraft();
156
+ }
157
+ if (event.key === 'Escape') {
158
+ event.preventDefault();
159
+ skipNextBlurRef.current = true;
160
+ setDraft(currentColor);
161
+ }
162
+ }, onValueChange: next => {
163
+ skipNextBlurRef.current = false;
164
+ setDraft(next);
165
+ } }), _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 => {
166
+ const next = withColorAlpha(currentColor, values[0]);
54
167
  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] });
168
+ commit(next, 'alpha');
169
+ } })] }) : 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] });
170
+ 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
171
  }
69
172
  export { DEFAULT_COLOR_VALUE, colorAlphaPercent, colorRgbValue, formatColorValue, normalizeColorValue, parseColorValue, withColorAlpha, withColorRgb, } from './color.js';
173
+ 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,11 +1,29 @@
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; }
21
+ function assignRef(ref, value) {
22
+ if (typeof ref === 'function')
23
+ ref(value);
24
+ else if (ref)
25
+ ref.current = value;
26
+ }
9
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 => {
@@ -15,14 +33,19 @@ const Root = forwardRef(function Root({ className, children, loop = true, surfac
15
33
  onKeyDown?.(event);
16
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>>;
@@ -1,6 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
3
- import { createContext, useContext, forwardRef, useId } from 'react';
4
+ import { createContext, useContext, forwardRef, useId, useState } from 'react';
5
+ import { Button } from '../Button.js';
6
+ import { PencilIcon } from '../PencilIcon.js';
4
7
  import { PencilSurface } from '../PencilSurface.js';
5
8
  import { PencilText } from '../PencilText.js';
6
9
  import { PencilLine } from '../PencilLine.js';
@@ -29,8 +32,13 @@ const CardFooter = forwardRef(function CardFooter({ className, children, divided
29
32
  return _jsxs("div", { ...props, ref: ref, "data-justify": justify, "data-padding": padding, "data-orientation": orientation, className: classes('inkra-card__footer', className), children: [(divided ?? inherited) && _jsx(PencilLine, { className: "inkra-card__divider" }), children] });
30
33
  });
31
34
  export const Card = { Root: CardRoot, Header: CardHeader, Title: CardTitle, Description: CardDescription, Content: CardContent, Footer: CardFooter };
32
- const AlertRoot = forwardRef(function AlertRoot({ className, variant = 'info', icon, seed, style, children, role = 'status', size = 'md', appearance = 'soft', actions, ...props }, ref) {
35
+ const AlertRoot = forwardRef(function AlertRoot(inkraProps, ref) {
36
+ const { t } = useInkraLocale();
37
+ const { className, variant = 'info', icon, seed, style, children, role = 'status', size = 'md', appearance = 'soft', actions, closable = false, open, defaultOpen = true, onOpenChange, closeLabel = t('alert.dismiss'), ...props } = inkraProps;
33
38
  const id = useId();
39
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
40
+ if (!(open ?? internalOpen))
41
+ return null;
34
42
  const tokens = {
35
43
  color: `var(--inkra-${variant}-foreground)`,
36
44
  '--inkra-paper': `var(--inkra-${variant}-surface)`,
@@ -40,7 +48,11 @@ const AlertRoot = forwardRef(function AlertRoot({ className, variant = 'info', i
40
48
  '--inkra-border': `var(--inkra-${variant}-foreground)`,
41
49
  ...style,
42
50
  };
43
- return _jsxs(PencilSurface, { ...props, ref: ref, role: role, className: classes('inkra-alert', className), "data-variant": variant, "data-size": size, "data-appearance": appearance, variant: appearance === 'outline' ? 'outline' : 'action', radius: 8, style: tokens, seed: seed ?? `alert-${id}`, children: [icon && _jsx("span", { className: "inkra-alert__icon", "aria-hidden": "true", children: icon }), _jsxs("div", { className: "inkra-alert__body", children: [children, actions != null && _jsx("div", { className: "inkra-alert__actions", children: actions })] })] });
51
+ return _jsxs(PencilSurface, { ...props, ref: ref, role: role, className: classes('inkra-alert', className), "data-variant": variant, "data-size": size, "data-appearance": appearance, variant: appearance === 'outline' ? 'outline' : 'action', radius: 8, style: tokens, seed: seed ?? `alert-${id}`, children: [icon && _jsx("span", { className: "inkra-alert__icon", "aria-hidden": "true", children: icon }), _jsxs("div", { className: "inkra-alert__body", children: [children, actions != null && _jsx("div", { className: "inkra-alert__actions", children: actions })] }), closable && _jsx(Button, { className: "inkra-alert__close", variant: "ghost", size: "icon", "aria-label": closeLabel, title: closeLabel, onClick: () => {
52
+ if (open === undefined)
53
+ setInternalOpen(false);
54
+ onOpenChange?.(false);
55
+ }, children: _jsx(PencilIcon, { paths: [{ d: 'M18 6 6 18M6 6l12 12' }], sourceSize: 24, size: 20 }) })] });
44
56
  });
45
57
  const AlertTitle = forwardRef(function AlertTitle({ className, children, ...props }, ref) {
46
58
  return _jsx("p", { ...props, ref: ref, className: classes('inkra-alert__title', className), children: copy(children) });
@@ -223,7 +223,7 @@ export const ContextMenuSubContent = forwardRef(function ContextMenuSubContent({
223
223
  props = usePortalThemeProps(props);
224
224
  return (_jsx(Primitive.Portal, { children: _jsx(Primitive.SubContent, { ...props, className: classes('inkra-menu', 'inkra-context-menu__sub-content', className), collisionPadding: collisionPadding, ref: ref, sideOffset: sideOffset, children: _jsx(PencilSurface, { className: "inkra-menu__paper", radius: 8, seed: seed ?? `context-submenu-${id}`, variant: "paper", children: children }) }) }));
225
225
  });
226
- export const ContextMenu = Object.freeze({
226
+ export const ContextMenu = /* @__PURE__ */ Object.freeze({
227
227
  Root: ContextMenuRoot,
228
228
  Trigger: ContextMenuTrigger,
229
229
  Portal: ContextMenuPortal,
@@ -25,6 +25,12 @@ export interface DataTableProps<TData> extends Omit<HTMLAttributes<HTMLDivElemen
25
25
  defaultSorting?: SortingState;
26
26
  onSortingChange?: OnChangeFn<SortingState>;
27
27
  pagination?: boolean;
28
+ /** Supply only the current server page in data. rowCount is the known total. */
29
+ manualPagination?: boolean;
30
+ rowCount?: number;
31
+ /** Server owns sorting/filtering; callbacks still report the requested state. */
32
+ manualSorting?: boolean;
33
+ manualFiltering?: boolean;
28
34
  paginationState?: PaginationState;
29
35
  defaultPagination?: PaginationState;
30
36
  onPaginationChange?: OnChangeFn<PaginationState>;
@@ -44,4 +50,4 @@ export interface DataTableProps<TData> extends Omit<HTMLAttributes<HTMLDivElemen
44
50
  pageLabel?: (page: number, pageCount: number) => ReactNode;
45
51
  sortStatusLabel?: (columnId: string, direction: false | 'asc' | 'desc') => string;
46
52
  }
47
- export declare function DataTable<TData>({ data, columns, getRowId, caption, density, layout, striped, hoverable, stickyHeader, captionSide, filterable, filterLabel, filterPlaceholder, globalFilter, defaultGlobalFilter, onGlobalFilterChange, sorting, defaultSorting, onSortingChange, pagination, paginationState, defaultPagination, onPaginationChange, selectable, rowSelection, defaultRowSelection, onRowSelectionChange, getRowCanSelect, loading, loadingMessage, emptyMessage, selectAllLabel, selectRowLabel, previousPageLabel, nextPageLabel, paginationLabel, pageLabel, sortStatusLabel, className, ...props }: DataTableProps<TData>): import("react").JSX.Element;
53
+ export declare function DataTable<TData>(inkraProps: DataTableProps<TData>): import("react").JSX.Element;
@@ -1,12 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { flexRender, functionalUpdate, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
3
- import { useEffect, useState } from 'react';
4
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
5
  import { Button } from '../Button.js';
5
6
  import { PencilIcon } from '../PencilIcon.js';
7
+ import { PencilSurface } from '../PencilSurface.js';
8
+ import { PencilText } from '../PencilText.js';
6
9
  import { Input } from '../form/Input.js';
7
10
  import { Table } from '../content/index.js';
8
11
  import { Checkbox } from '../selection/Checkbox.js';
12
+ import { normalizeDataTablePagination } from './model.js';
9
13
  const sortIcon = [{ d: 'M 8 10 L 12 6 L 16 10 M 8 14 L 12 18 L 16 14' }];
14
+ const sortAscendingIcon = [{ d: 'M 7 15 L 12 10 L 17 15' }];
15
+ const sortDescendingIcon = [{ d: 'M 7 9 L 12 14 L 17 9' }];
10
16
  const previousIcon = [{ d: 'M 14 7 L 9 12 L 14 17' }];
11
17
  const nextIcon = [{ d: 'M 10 7 L 15 12 L 10 17' }];
12
18
  function classes(...values) {
@@ -15,27 +21,43 @@ function classes(...values) {
15
21
  function resolve(updater, current) {
16
22
  return functionalUpdate(updater, current);
17
23
  }
24
+ function pencilCopy(value, seed) {
25
+ return typeof value === 'string' || typeof value === 'number'
26
+ ? _jsx(PencilText, { seed: seed, children: String(value) })
27
+ : value;
28
+ }
18
29
  function SelectionCheckbox(props) {
19
30
  return _jsx(Checkbox, { className: "inkra-data-table__selection", checked: props.checked, indeterminate: props.indeterminate, disabled: props.disabled, label: _jsx("span", { className: "inkra-visually-hidden", children: props.label }), onCheckedChange: props.onChange });
20
31
  }
21
- export function DataTable({ data, columns, getRowId, caption, density = 'comfortable', layout = 'auto', striped = false, hoverable = true, stickyHeader = false, captionSide = 'top', filterable = false, filterLabel = 'Filter rows', filterPlaceholder = 'Filter rows', globalFilter, defaultGlobalFilter = '', onGlobalFilterChange, sorting, defaultSorting = [], onSortingChange, pagination = false, paginationState, defaultPagination = { pageIndex: 0, pageSize: 10 }, onPaginationChange, selectable = false, rowSelection, defaultRowSelection = {}, onRowSelectionChange, getRowCanSelect, loading = false, loadingMessage = 'Loading rows', emptyMessage = 'No rows', selectAllLabel = 'Select all rows on this page', selectRowLabel = row => `Select row ${row.id}`, previousPageLabel = 'Previous page', nextPageLabel = 'Next page', paginationLabel = 'Table pagination', pageLabel = (page, pageCount) => `Page ${page} of ${pageCount}`, sortStatusLabel = (columnId, direction) => direction === 'asc' ? `${columnId}: sorted ascending` : direction === 'desc' ? `${columnId}: sorted descending` : `${columnId}: not sorted`, className, ...props }) {
32
+ export function DataTable(inkraProps) {
33
+ const { t } = useInkraLocale();
34
+ const { data, columns, getRowId, caption, density = 'comfortable', layout = 'auto', striped = false, hoverable = true, stickyHeader = false, captionSide = 'top', filterable = false, filterLabel = t('table.filter'), filterPlaceholder = t('table.filter'), globalFilter, defaultGlobalFilter = '', onGlobalFilterChange, sorting, defaultSorting = [], onSortingChange, pagination = false, manualPagination = false, rowCount, manualSorting = false, manualFiltering = false, paginationState, defaultPagination = { pageIndex: 0, pageSize: 10 }, onPaginationChange, selectable = false, rowSelection, defaultRowSelection = {}, onRowSelectionChange, getRowCanSelect, loading = false, loadingMessage = t('table.loading'), emptyMessage = t('table.empty'), selectAllLabel = t('table.selectAll'), selectRowLabel = row => t('table.selectRow', { row: row.id }), previousPageLabel = t('common.previousPage'), nextPageLabel = t('common.nextPage'), paginationLabel = t('table.pagination'), pageLabel = (page, pageCount) => t('pagination.status', { page, total: pageCount }), sortStatusLabel = (columnId, direction) => t(direction === 'asc' ? 'table.ascending' : direction === 'desc' ? 'table.descending' : 'table.unsorted', { column: columnId }), className, ...props } = inkraProps;
22
35
  const [internalFilter, setInternalFilter] = useState(defaultGlobalFilter);
23
36
  const [internalSorting, setInternalSorting] = useState(defaultSorting);
24
37
  const [internalPagination, setInternalPagination] = useState(defaultPagination);
25
38
  const [internalSelection, setInternalSelection] = useState(defaultRowSelection);
39
+ const paginationCorrection = useRef('');
26
40
  const resolvedFilter = globalFilter ?? internalFilter;
27
41
  const resolvedSorting = sorting ?? internalSorting;
28
42
  const resolvedPagination = paginationState ?? internalPagination;
29
43
  const resolvedSelection = rowSelection ?? internalSelection;
44
+ const paginationRowCount = manualPagination && pagination && rowCount !== undefined
45
+ ? (Number.isFinite(rowCount) ? Math.max(0, Math.floor(rowCount)) : data.length)
46
+ : data.length;
47
+ const initialPagination = useMemo(() => normalizeDataTablePagination(resolvedPagination, paginationRowCount), [resolvedPagination.pageIndex, resolvedPagination.pageSize, paginationRowCount]);
30
48
  const handleSorting = updater => {
31
49
  if (sorting === undefined)
32
50
  setInternalSorting(current => resolve(updater, current));
33
51
  onSortingChange?.(updater);
34
52
  };
35
53
  const handlePagination = updater => {
54
+ const normalizedUpdater = current => {
55
+ const rowCount = manualPagination ? paginationRowCount : table.getPrePaginationRowModel().rows.length;
56
+ return normalizeDataTablePagination(resolve(updater, normalizeDataTablePagination(current, rowCount)), rowCount);
57
+ };
36
58
  if (paginationState === undefined)
37
- setInternalPagination(current => resolve(updater, current));
38
- onPaginationChange?.(updater);
59
+ setInternalPagination(normalizedUpdater);
60
+ onPaginationChange?.(normalizedUpdater);
39
61
  };
40
62
  const handleSelection = updater => {
41
63
  if (rowSelection === undefined)
@@ -53,39 +75,68 @@ export function DataTable({ data, columns, getRowId, caption, density = 'comfort
53
75
  data,
54
76
  columns,
55
77
  getRowId,
78
+ manualPagination: pagination && manualPagination,
79
+ rowCount: pagination && manualPagination ? paginationRowCount : undefined,
80
+ manualSorting,
81
+ manualFiltering,
56
82
  state: {
57
83
  globalFilter: resolvedFilter,
58
84
  sorting: resolvedSorting,
59
- pagination: resolvedPagination,
85
+ pagination: initialPagination,
60
86
  rowSelection: resolvedSelection,
61
87
  },
62
88
  onGlobalFilterChange: updater => handleFilter(resolve(updater, resolvedFilter)),
63
89
  onSortingChange: handleSorting,
64
90
  onPaginationChange: handlePagination,
65
91
  onRowSelectionChange: handleSelection,
92
+ defaultColumn: {
93
+ header: context => pencilCopy(context.column.id, `data-table-header-${context.header.id}`),
94
+ cell: context => pencilCopy(context.renderValue()?.toString?.() ?? null, `data-table-cell-${context.cell.id}`),
95
+ },
66
96
  enableRowSelection: selectable ? getRowCanSelect ?? true : false,
67
97
  getCoreRowModel: getCoreRowModel(),
68
98
  getFilteredRowModel: getFilteredRowModel(),
69
99
  getSortedRowModel: getSortedRowModel(),
70
100
  getPaginationRowModel: pagination ? getPaginationRowModel() : undefined,
71
101
  });
102
+ const filteredRowCount = pagination && manualPagination ? paginationRowCount : table.getPrePaginationRowModel().rows.length;
103
+ const effectivePagination = useMemo(() => normalizeDataTablePagination(initialPagination, filteredRowCount), [initialPagination, filteredRowCount]);
104
+ if (table.getState().pagination.pageIndex !== effectivePagination.pageIndex
105
+ || table.getState().pagination.pageSize !== effectivePagination.pageSize) {
106
+ table.setOptions(previous => ({
107
+ ...previous,
108
+ state: { ...previous.state, pagination: effectivePagination },
109
+ }));
110
+ }
72
111
  const rows = table.getRowModel().rows;
73
- const filteredRowCount = table.getPrePaginationRowModel().rows.length;
74
- const pageCount = Math.max(1, table.getPageCount());
112
+ const pageCount = filteredRowCount === 0 ? 0 : Math.ceil(filteredRowCount / effectivePagination.pageSize);
75
113
  const visibleColumnCount = Math.max(1, table.getVisibleLeafColumns().length + (selectable ? 1 : 0));
76
114
  const hasSelectableRows = rows.some(row => row.getCanSelect());
77
115
  useEffect(() => {
78
116
  if (!pagination)
79
117
  return;
80
- const lastPageIndex = Math.max(0, Math.ceil(filteredRowCount / resolvedPagination.pageSize) - 1);
81
- if (resolvedPagination.pageIndex <= lastPageIndex)
118
+ const matches = resolvedPagination.pageIndex === effectivePagination.pageIndex
119
+ && resolvedPagination.pageSize === effectivePagination.pageSize;
120
+ if (matches) {
121
+ paginationCorrection.current = '';
122
+ return;
123
+ }
124
+ const signature = `${filteredRowCount}:${resolvedPagination.pageIndex}:${resolvedPagination.pageSize}->${effectivePagination.pageIndex}:${effectivePagination.pageSize}`;
125
+ if (paginationCorrection.current === signature)
82
126
  return;
83
- const next = { ...resolvedPagination, pageIndex: lastPageIndex };
127
+ paginationCorrection.current = signature;
84
128
  if (paginationState === undefined)
85
- setInternalPagination(next);
86
- onPaginationChange?.(next);
87
- }, [filteredRowCount, onPaginationChange, pagination, paginationState, resolvedPagination]);
88
- return _jsxs("div", { ...props, className: classes('inkra-data-table', className), "data-loading": loading || undefined, children: [filterable && _jsx("div", { className: "inkra-data-table__toolbar", children: _jsxs("label", { className: "inkra-data-table__filter", children: [_jsx("span", { children: filterLabel }), _jsx(Input, { type: "search", value: resolvedFilter, placeholder: filterPlaceholder, onChange: event => handleFilter(event.currentTarget.value) })] }) }), _jsx(Table.Container, { className: "inkra-data-table__container", children: _jsxs(Table.Root, { density: density, layout: layout, striped: striped, hoverable: hoverable, stickyHeader: stickyHeader, captionSide: captionSide, children: [caption != null && _jsx(Table.Caption, { children: caption }), _jsx(Table.Header, { children: table.getHeaderGroups().map(headerGroup => _jsxs(Table.Row, { children: [selectable && _jsx(Table.Head, { className: "inkra-data-table__select-cell", children: _jsx(SelectionCheckbox, { checked: pagination ? table.getIsAllPageRowsSelected() : table.getIsAllRowsSelected(), indeterminate: pagination ? table.getIsSomePageRowsSelected() : table.getIsSomeRowsSelected(), label: selectAllLabel, disabled: loading || !hasSelectableRows, onChange: checked => pagination ? table.toggleAllPageRowsSelected(checked) : table.toggleAllRowsSelected(checked) }) }), headerGroup.headers.map(header => _jsx(Table.Head, { colSpan: header.colSpan, "aria-sort": header.column.getIsSorted() === 'asc' ? 'ascending' : header.column.getIsSorted() === 'desc' ? 'descending' : header.column.getCanSort() ? 'none' : undefined, children: header.isPlaceholder ? null : header.column.getCanSort() ? _jsxs("button", { type: "button", className: "inkra-data-table__sort", "data-sorted": header.column.getIsSorted() || undefined, onClick: header.column.getToggleSortingHandler(), children: [_jsx("span", { children: flexRender(header.column.columnDef.header, header.getContext()) }), _jsx(PencilIcon, { "aria-hidden": "true", paths: sortIcon, size: 18, sourceSize: 24, active: Boolean(header.column.getIsSorted()) }), _jsx("span", { className: "inkra-visually-hidden", children: sortStatusLabel(header.column.id, header.column.getIsSorted()) })] }) : flexRender(header.column.columnDef.header, header.getContext()) }, header.id))] }, headerGroup.id)) }), _jsx(Table.Body, { children: loading ? _jsx(Table.Row, { children: _jsx(Table.Cell, { colSpan: visibleColumnCount, children: _jsx("div", { className: "inkra-data-table__state", role: "status", children: loadingMessage }) }) })
89
- : rows.length === 0 ? _jsx(Table.Row, { children: _jsx(Table.Cell, { colSpan: visibleColumnCount, children: _jsx("div", { className: "inkra-data-table__state", children: emptyMessage }) }) })
90
- : rows.map(row => _jsxs(Table.Row, { selected: row.getIsSelected(), children: [selectable && _jsx(Table.Cell, { className: "inkra-data-table__select-cell", children: _jsx(SelectionCheckbox, { checked: row.getIsSelected(), label: selectRowLabel(row), disabled: !row.getCanSelect(), onChange: checked => row.toggleSelected(checked) }) }), row.getVisibleCells().map(cell => _jsx(Table.Cell, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))] }, row.id)) })] }) }), pagination && _jsxs("nav", { className: "inkra-data-table__pagination", "aria-label": paginationLabel, children: [_jsx(Button, { size: "icon-xs", variant: "ghost", "aria-label": previousPageLabel, disabled: loading || !table.getCanPreviousPage(), onClick: () => table.previousPage(), children: _jsx(PencilIcon, { paths: previousIcon, size: 18, sourceSize: 24 }) }), _jsx("span", { "aria-live": "polite", children: pageLabel(table.getState().pagination.pageIndex + 1, pageCount) }), _jsx(Button, { size: "icon-xs", variant: "ghost", "aria-label": nextPageLabel, disabled: loading || !table.getCanNextPage(), onClick: () => table.nextPage(), children: _jsx(PencilIcon, { paths: nextIcon, size: 18, sourceSize: 24 }) })] })] });
129
+ setInternalPagination(effectivePagination);
130
+ onPaginationChange?.(effectivePagination);
131
+ }, [effectivePagination.pageIndex, effectivePagination.pageSize, filteredRowCount, onPaginationChange, pagination,
132
+ paginationState, resolvedPagination.pageIndex, resolvedPagination.pageSize]);
133
+ return _jsxs("div", { ...props, className: classes('inkra-data-table', className), "data-loading": loading || undefined, children: [filterable && _jsx("div", { className: "inkra-data-table__toolbar", children: _jsxs("label", { className: "inkra-data-table__filter", children: [_jsx("span", { children: pencilCopy(filterLabel, 'data-table-filter-label') }), _jsx(Input, { type: "search", value: resolvedFilter, placeholder: filterPlaceholder, onChange: event => handleFilter(event.currentTarget.value) })] }) }), _jsx(Table.Container, { className: "inkra-data-table__container", children: _jsxs(Table.Root, { density: density, layout: layout, striped: striped, hoverable: hoverable, stickyHeader: stickyHeader, captionSide: captionSide, children: [caption != null && _jsx(Table.Caption, { children: caption }), _jsx(Table.Header, { children: table.getHeaderGroups().map(headerGroup => _jsxs(Table.Row, { children: [selectable && _jsx(Table.Head, { className: "inkra-data-table__select-cell", children: _jsx(SelectionCheckbox, { checked: pagination ? table.getIsAllPageRowsSelected() : table.getIsAllRowsSelected(), indeterminate: pagination ? table.getIsSomePageRowsSelected() : table.getIsSomeRowsSelected(), label: selectAllLabel, disabled: loading || !hasSelectableRows, onChange: checked => pagination ? table.toggleAllPageRowsSelected(checked) : table.toggleAllRowsSelected(checked) }) }), headerGroup.headers.map(header => _jsx(Table.Head, { colSpan: header.colSpan, "aria-sort": header.column.getIsSorted() === 'asc' ? 'ascending' : header.column.getIsSorted() === 'desc' ? 'descending' : header.column.getCanSort() ? 'none' : undefined, children: header.isPlaceholder ? null : (() => {
134
+ const rendered = flexRender(header.column.columnDef.header, header.getContext());
135
+ const direction = header.column.getIsSorted();
136
+ if (!header.column.getCanSort())
137
+ return pencilCopy(rendered, `data-table-header-${header.id}`);
138
+ return _jsxs("button", { type: "button", className: "inkra-data-table__sort", "data-sorted": direction || undefined, onClick: header.column.getToggleSortingHandler(), children: [_jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-data-table__sort-focus", variant: "outline", radius: 6, seed: `data-table-sort-${header.id}-focus` }), _jsx("span", { className: "inkra-data-table__sort-label", children: pencilCopy(rendered, `data-table-sort-${header.id}`) }), _jsx(PencilIcon, { "aria-hidden": "true", className: "inkra-data-table__sort-icon", "data-direction": direction || 'none', paths: direction === 'asc' ? sortAscendingIcon : direction === 'desc' ? sortDescendingIcon : sortIcon, size: 18, sourceSize: 24, active: Boolean(direction) }), _jsx("span", { className: "inkra-visually-hidden", children: sortStatusLabel(header.column.id, direction) })] });
139
+ })() }, header.id))] }, headerGroup.id)) }), _jsx(Table.Body, { children: loading ? _jsx(Table.Row, { children: _jsx(Table.Cell, { colSpan: visibleColumnCount, children: _jsx("div", { className: "inkra-data-table__state", role: "status", children: pencilCopy(loadingMessage, 'data-table-loading') }) }) })
140
+ : rows.length === 0 ? _jsx(Table.Row, { children: _jsx(Table.Cell, { colSpan: visibleColumnCount, children: _jsx("div", { className: "inkra-data-table__state", children: pencilCopy(emptyMessage, 'data-table-empty') }) }) })
141
+ : rows.map(row => _jsxs(Table.Row, { selected: row.getIsSelected(), children: [selectable && _jsx(Table.Cell, { className: "inkra-data-table__select-cell", children: _jsx(SelectionCheckbox, { checked: row.getIsSelected(), label: selectRowLabel(row), disabled: !row.getCanSelect(), onChange: checked => row.toggleSelected(checked) }) }), row.getVisibleCells().map(cell => _jsx(Table.Cell, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))] }, row.id)) })] }) }), pagination && _jsxs("nav", { className: "inkra-data-table__pagination", "aria-label": paginationLabel, children: [_jsx(Button, { size: "icon-xs", variant: "ghost", "aria-label": previousPageLabel, disabled: loading || pageCount === 0 || effectivePagination.pageIndex <= 0, onClick: () => table.previousPage(), children: _jsx(PencilIcon, { className: "inkra-data-table__page-arrow", paths: previousIcon, size: 18, sourceSize: 24 }) }), _jsx("span", { "aria-live": "polite", "aria-atomic": "true", children: pencilCopy(pageLabel(pageCount === 0 ? 0 : effectivePagination.pageIndex + 1, pageCount), 'data-table-page-label') }), _jsx(Button, { size: "icon-xs", variant: "ghost", "aria-label": nextPageLabel, disabled: loading || pageCount === 0 || effectivePagination.pageIndex >= pageCount - 1, onClick: () => table.nextPage(), children: _jsx(PencilIcon, { className: "inkra-data-table__page-arrow", paths: nextIcon, size: 18, sourceSize: 24 }) })] })] });
91
142
  }
@@ -0,0 +1,2 @@
1
+ import type { PaginationState } from '@tanstack/react-table';
2
+ export declare function normalizeDataTablePagination(state: PaginationState, rowCount: number): PaginationState;
@@ -0,0 +1,7 @@
1
+ export function normalizeDataTablePagination(state, rowCount) {
2
+ const pageSize = Number.isFinite(state.pageSize) ? Math.max(1, Math.floor(state.pageSize)) : 10;
3
+ const count = Number.isFinite(rowCount) ? Math.max(0, Math.floor(rowCount)) : 0;
4
+ const pageCount = count === 0 ? 0 : Math.ceil(count / pageSize);
5
+ const requestedIndex = Number.isFinite(state.pageIndex) ? Math.max(0, Math.floor(state.pageIndex)) : 0;
6
+ return { pageIndex: pageCount === 0 ? 0 : Math.min(requestedIndex, pageCount - 1), pageSize };
7
+ }
@@ -1,6 +1,7 @@
1
1
  import type { AriaAttributes, CSSProperties, FocusEventHandler } from 'react';
2
2
  import type { DateRange } from 'react-day-picker';
3
3
  import type { CalendarProps } from '../calendar/index.js';
4
+ import type { FieldControlSize } from '../form/types.js';
4
5
  export type DatePickerMode = 'single' | 'range';
5
6
  export type DatePickerRange = DateRange;
6
7
  export type DatePickerValue = Date | DatePickerRange | undefined;
@@ -23,6 +24,7 @@ export interface DatePickerProps {
23
24
  onBlur?: FocusEventHandler<HTMLButtonElement>;
24
25
  onFocus?: FocusEventHandler<HTMLButtonElement>;
25
26
  required?: boolean;
27
+ size?: FieldControlSize;
26
28
  style?: CSSProperties;
27
29
  mode?: DatePickerMode;
28
30
  value?: DatePickerValue;