@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
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef } from 'react';
3
- import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable, effectiveDpr } from '../internal/cache.js';
3
+ import { BoundedCache, PENCIL_RENDERER_VERSION, boundedCanvasSize, canvasByteCost, canvasIsUsable, effectiveDpr } from '../internal/cache.js';
4
4
  import { observeCanvasRecovery } from '../internal/recovery.js';
5
5
  import { seedToNumber } from '../internal/random.js';
6
6
  import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from '../internal/renderTokens.js';
@@ -84,6 +84,7 @@ export function ProgressRing({ diameter, thickness, percent, seed }) {
84
84
  const weight = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
85
85
  const roughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
86
86
  const dpr = effectiveDpr(2);
87
+ const size = boundedCanvasSize(width, width, dpr);
87
88
  const key = JSON.stringify([PENCIL_RENDERER_VERSION, diameter, thickness, width, dpr, style.color, weight, roughness, seed]);
88
89
  if (key === lastPaint && canvasIsUsable(canvas))
89
90
  return;
@@ -94,7 +95,9 @@ export function ProgressRing({ diameter, thickness, percent, seed }) {
94
95
  }
95
96
  if (!image) {
96
97
  image = document.createElement('canvas');
97
- image.width = image.height = Math.ceil(width * dpr);
98
+ image.width = size.width;
99
+ image.height = size.height;
100
+ image.addEventListener('contextlost', () => ringCache.delete(key), { once: true });
98
101
  const context = image.getContext('2d');
99
102
  if (!context)
100
103
  throw new Error('Canvas unavailable');
@@ -116,6 +119,7 @@ export function ProgressRing({ diameter, thickness, percent, seed }) {
116
119
  lastPaint = key;
117
120
  }
118
121
  catch {
122
+ canvas.width = canvas.height = 0;
119
123
  delete canvas.dataset.ready;
120
124
  lastPaint = '';
121
125
  }
@@ -131,7 +135,7 @@ export function ProgressRing({ diameter, thickness, percent, seed }) {
131
135
  const scheme = matchMedia('(prefers-color-scheme: dark)');
132
136
  scheme.addEventListener('change', paint);
133
137
  window.addEventListener('resize', paint);
134
- const stopRecovery = observeCanvasRecovery(paint);
138
+ const stopRecovery = observeCanvasRecovery(() => { lastPaint = ''; paint(); });
135
139
  paint();
136
140
  return () => {
137
141
  observer.disconnect();
@@ -1,4 +1,4 @@
1
- import type { HTMLAttributes, ReactNode } from 'react';
1
+ import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
2
2
  import { type ProgressPartialSegment, type ProgressThreshold } from '../segmented-progress/SegmentedProgress.js';
3
3
  export type { ProgressPartialSegment, ProgressThreshold } from '../segmented-progress/SegmentedProgress.js';
4
4
  export type BadgeVariant = 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'info';
@@ -22,6 +22,9 @@ export declare const Separator: import("react").ForwardRefExoticComponent<Separa
22
22
  export interface SkeletonProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
23
23
  seed?: string | number;
24
24
  radius?: number;
25
+ width?: CSSProperties['width'];
26
+ height?: CSSProperties['height'];
27
+ animation?: 'none' | 'pulse';
25
28
  }
26
29
  export declare const Skeleton: import("react").ForwardRefExoticComponent<SkeletonProps & import("react").RefAttributes<HTMLDivElement>>;
27
30
  export interface ProgressRenderState {
@@ -30,8 +30,9 @@ export const Separator = forwardRef(function Separator({ className, decorative =
30
30
  : { 'aria-orientation': orientation, role: 'separator' };
31
31
  return (_jsx("div", { ...props, ...semanticProps, className: ['inkra-separator', className].filter(Boolean).join(' '), "data-orientation": orientation, ref: ref, children: _jsx(PencilLine, { className: "inkra-separator__line", orientation: orientation, seed: seed ?? `inkra-separator-${orientation}`, weight: Number.isFinite(weight) ? Math.max(0.3, Math.min(2, weight)) : 0.9 }) }));
32
32
  });
33
- export const Skeleton = forwardRef(function Skeleton({ className, seed = 'inkra-skeleton', radius, ...props }, ref) {
34
- return (_jsx(PencilSurface, { ...props, "aria-hidden": "true", as: "div", className: ['inkra-skeleton', className].filter(Boolean).join(' '), ref: ref, seed: seed, radius: radius, variant: "quiet" }));
33
+ export const Skeleton = forwardRef(function Skeleton({ className, seed = 'inkra-skeleton', radius = 7, width, height, animation = 'none', style, ...props }, ref) {
34
+ const resolvedRadius = Number.isFinite(radius) ? Math.max(0, Math.min(radius, 8)) : 7;
35
+ return (_jsx(PencilSurface, { ...props, "aria-hidden": "true", as: "div", className: ['inkra-skeleton', className].filter(Boolean).join(' '), "data-animation": animation, style: { width, height, borderRadius: resolvedRadius, ...style }, ref: ref, seed: seed, radius: resolvedRadius, variant: "selection" }));
35
36
  });
36
37
  function normalizeMax(max) {
37
38
  return typeof max === 'number' && Number.isFinite(max) && max > 0 ? max : 100;
@@ -43,7 +43,7 @@ export const DockablePanelWorkspace = forwardRef(function DockablePanelWorkspace
43
43
  return () => observer.disconnect();
44
44
  }, []);
45
45
  const activate = useCallback((id) => {
46
- setOrder(current => promoteDockablePanel(current, id));
46
+ setOrder(current => current[current.length - 1] === id ? current : promoteDockablePanel(current, id));
47
47
  if (activeId === undefined)
48
48
  setUncontrolledActiveId(id);
49
49
  if (resolvedActiveId !== id)
@@ -51,7 +51,7 @@ export const DockablePanelWorkspace = forwardRef(function DockablePanelWorkspace
51
51
  }, [activeId, onActiveChange, resolvedActiveId]);
52
52
  useEffect(() => {
53
53
  if (activeId != null)
54
- setOrder(current => promoteDockablePanel(current, activeId));
54
+ setOrder(current => current[current.length - 1] === activeId ? current : promoteDockablePanel(current, activeId));
55
55
  }, [activeId]);
56
56
  const register = useCallback((id) => {
57
57
  setOrder(current => current.includes(id) ? current : [...current, id]);
@@ -72,18 +72,18 @@ export const DockablePanelWorkspace = forwardRef(function DockablePanelWorkspace
72
72
  titleBarVisibility,
73
73
  width: size.width,
74
74
  }), [boundaryPadding, size.height, size.width, titleBarVisibility]);
75
+ const layers = useMemo(() => new Map(order.map((id, index) => [id, index])), [order]);
75
76
  const context = useMemo(() => ({
76
77
  activeId: resolvedActiveId,
77
78
  activate,
78
79
  bounds,
79
80
  commitDrag,
80
81
  layer(id) {
81
- const index = order.indexOf(id);
82
- return baseZIndex + (resolvedActiveId === id ? order.length : Math.max(0, index));
82
+ return baseZIndex + (resolvedActiveId === id ? order.length : (layers.get(id) ?? 0));
83
83
  },
84
84
  register,
85
85
  snapThreshold,
86
- }), [activate, baseZIndex, bounds, commitDrag, order, register, resolvedActiveId, snapThreshold]);
86
+ }), [activate, baseZIndex, bounds, commitDrag, layers, order.length, register, resolvedActiveId, snapThreshold]);
87
87
  function handleDragEnd(event) {
88
88
  const id = String(event.active.id);
89
89
  dragCallbacks.current.get(id)?.({ x: event.delta.x, y: event.delta.y });
@@ -8,7 +8,7 @@ export declare function DrawerRoot(props: DrawerRootProps): import("react").JSX.
8
8
  export type DrawerTriggerProps = DialogPrimitive.DialogTriggerProps;
9
9
  export declare const DrawerTrigger: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
10
10
  export type DrawerPortalProps = DialogPrimitive.DialogPortalProps;
11
- export declare function DrawerPortal({ children, ...props }: DrawerPortalProps): import("react").JSX.Element;
11
+ export declare function DrawerPortal({ children, ...props }: DrawerPortalProps): ReactNode;
12
12
  export type DrawerBackdropProps = DialogPrimitive.DialogOverlayProps;
13
13
  export declare const DrawerBackdrop: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogOverlayProps & import("react").RefAttributes<HTMLDivElement>>;
14
14
  export interface DrawerContentProps extends DialogPrimitive.DialogContentProps {
@@ -8,7 +8,7 @@ import { PencilLine } from '../PencilLine.js';
8
8
  import { PencilSurface } from '../PencilSurface.js';
9
9
  import { PencilText } from '../PencilText.js';
10
10
  import { Tooltip } from '../tooltip/Tooltip.js';
11
- import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef, } from '../overlay/themeBridge.js';
11
+ import { mapPortalChildren, PortalThemeProvider, usePortalThemeProps, usePortalTriggerRef, } from '../overlay/themeBridge.js';
12
12
  const classes = (...values) => values.filter(Boolean).join(' ');
13
13
  const closePaths = closeIcon.node.map(([, attributes]) => ({ d: attributes.d }));
14
14
  function drawerSize(size) {
@@ -29,16 +29,18 @@ export const DrawerTrigger = forwardRef(function DrawerTrigger(props, forwardedR
29
29
  return _jsx(DialogPrimitive.Trigger, { ...props, ref: usePortalTriggerRef(forwardedRef) });
30
30
  });
31
31
  export function DrawerPortal({ children, ...props }) {
32
- return _jsx(DialogPrimitive.Portal, { ...props, children: _jsx(PortalThemeBridge, { children: children }) });
32
+ return mapPortalChildren(children, child => _jsx(DialogPrimitive.Portal, { ...props, children: child }));
33
33
  }
34
34
  export const DrawerBackdrop = forwardRef(function DrawerBackdrop({ className, ...props }, ref) {
35
+ props = usePortalThemeProps(props);
35
36
  return _jsx(DialogPrimitive.Overlay, { ...props, className: classes('inkra-drawer__backdrop', className), ref: ref });
36
37
  });
37
38
  export const DrawerContent = forwardRef(function DrawerContent({ children, className, direction = 'right', seed, size = 'md', style, ...props }, ref) {
38
39
  const id = useId();
39
- const mergedStyle = { ...style, '--inkra-drawer-size': drawerSize(size) };
40
+ const themedProps = usePortalThemeProps({ ...props, style });
41
+ const mergedStyle = { ...themedProps.style, '--inkra-drawer-size': drawerSize(size) };
40
42
  const edgeOrientation = direction === 'left' || direction === 'right' ? 'vertical' : 'horizontal';
41
- return (_jsx(DialogPrimitive.Content, { ...props, className: classes('inkra-drawer__content', className), "data-direction": direction, ref: ref, style: mergedStyle, children: _jsxs(PencilSurface, { className: "inkra-drawer__surface", radius: 0, seed: seed ?? `inkra-drawer-${id}`, variant: "full", children: [_jsx(PencilLine, { className: "inkra-drawer__edge", orientation: edgeOrientation, seed: `drawer-edge-${id}` }), _jsx("div", { className: "inkra-drawer__layout", children: children })] }) }));
43
+ return (_jsxs(DialogPrimitive.Content, { ...themedProps, className: classes('inkra-drawer__content', className), "data-direction": direction, ref: ref, style: mergedStyle, children: [_jsx(PencilSurface, { "aria-hidden": "true", className: "inkra-drawer__focus-ring", radius: 0, seed: `drawer-focus-${id}`, variant: "outline" }), _jsxs(PencilSurface, { className: "inkra-drawer__surface", radius: 0, seed: seed ?? `inkra-drawer-${id}`, variant: "full", children: [_jsx(PencilLine, { className: "inkra-drawer__edge", orientation: edgeOrientation, seed: `drawer-edge-${id}` }), _jsx("div", { className: "inkra-drawer__layout", children: children })] })] }));
42
44
  });
43
45
  export const DrawerTitle = forwardRef(function DrawerTitle({ children, className, seed, ...props }, ref) {
44
46
  const id = useId();
@@ -61,7 +63,8 @@ export const DrawerCloseButton = forwardRef(function DrawerCloseButton({ classNa
61
63
  });
62
64
  export const DrawerHeader = forwardRef(function DrawerHeader({ action, children, className, closeLabel = 'Close', divided = true, showClose = true, ...props }, ref) {
63
65
  const id = useId();
64
- return _jsxs("div", { ...props, className: classes('inkra-drawer__header', className), "data-divided": divided || undefined, ref: ref, children: [_jsx("div", { className: "inkra-drawer__heading", children: children }), (action || showClose) ? _jsxs("div", { className: "inkra-drawer__header-actions", children: [action, showClose ? _jsx(DrawerCloseButton, { label: closeLabel }) : null] }) : null, divided ? _jsx(PencilLine, { className: "inkra-drawer__divider", seed: `drawer-header-${id}` }) : null] });
66
+ const hasActions = Boolean(action || showClose);
67
+ return _jsxs("div", { ...props, className: classes('inkra-drawer__header', className), "data-actions": hasActions || undefined, "data-custom-action": action ? true : undefined, "data-divided": divided || undefined, ref: ref, children: [_jsx("div", { className: "inkra-drawer__heading", children: children }), hasActions ? _jsxs("div", { className: "inkra-drawer__header-actions", children: [action, showClose ? _jsx(DrawerCloseButton, { label: closeLabel }) : null] }) : null, divided ? _jsx(PencilLine, { className: "inkra-drawer__divider", seed: `drawer-header-${id}` }) : null] });
65
68
  });
66
69
  export const DrawerBody = forwardRef(function DrawerBody({ className, ...props }, ref) {
67
70
  return _jsx("div", { ...props, className: classes('inkra-drawer__body', className), ref: ref });
@@ -70,7 +73,7 @@ export const DrawerFooter = forwardRef(function DrawerFooter({ align = 'end', ch
70
73
  const id = useId();
71
74
  return _jsxs("div", { ...props, className: classes('inkra-drawer__footer', className), "data-align": align, "data-divided": divided || undefined, ref: ref, children: [divided ? _jsx(PencilLine, { className: "inkra-drawer__divider", seed: `drawer-footer-${id}` }) : null, children] });
72
75
  });
73
- export const Drawer = Object.freeze({
76
+ export const Drawer = /* @__PURE__ */ Object.freeze({
74
77
  Root: DrawerRoot,
75
78
  Trigger: DrawerTrigger,
76
79
  Portal: DrawerPortal,
@@ -4,7 +4,7 @@ export type DropdownMenuProps = Primitive.DropdownMenuProps;
4
4
  declare function Root(props: DropdownMenuProps): import("react").JSX.Element;
5
5
  export type DropdownMenuTriggerProps = Primitive.DropdownMenuTriggerProps;
6
6
  export type DropdownMenuPortalProps = Primitive.DropdownMenuPortalProps;
7
- declare function Portal({ children, ...props }: DropdownMenuPortalProps): import("react").JSX.Element;
7
+ declare function Portal({ children, ...props }: DropdownMenuPortalProps): ReactNode;
8
8
  export interface DropdownMenuContentProps extends Omit<Primitive.DropdownMenuContentProps, 'asChild'> {
9
9
  seed?: string | number;
10
10
  }
@@ -5,16 +5,17 @@ import { PencilSurface } from '../PencilSurface.js';
5
5
  import { PencilText } from '../PencilText.js';
6
6
  import { PencilIcon } from '../PencilIcon.js';
7
7
  import { PencilLine } from '../PencilLine.js';
8
- import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef } from '../overlay/themeBridge.js';
8
+ import { mapPortalChildren, PortalThemeProvider, usePortalThemeProps, usePortalTriggerRef } from '../overlay/themeBridge.js';
9
9
  const classes = (...values) => values.filter(Boolean).join(' ');
10
10
  const copy = (children) => typeof children === 'string' ? _jsx(PencilText, { children: children }) : children;
11
11
  function Root(props) { return _jsx(PortalThemeProvider, { children: _jsx(Primitive.Root, { ...props }) }); }
12
12
  const Trigger = forwardRef(function Trigger(props, ref) {
13
13
  return _jsx(Primitive.Trigger, { ...props, ref: usePortalTriggerRef(ref) });
14
14
  });
15
- function Portal({ children, ...props }) { return _jsx(Primitive.Portal, { ...props, children: _jsx(PortalThemeBridge, { children: children }) }); }
15
+ function Portal({ children, ...props }) { return mapPortalChildren(children, child => _jsx(Primitive.Portal, { ...props, children: child })); }
16
16
  const Content = forwardRef(function Content({ className, children, seed, sideOffset = 6, collisionPadding = 8, ...props }, ref) {
17
17
  const id = useId();
18
+ props = usePortalThemeProps(props);
18
19
  return _jsx(Primitive.Content, { ...props, ref: ref, sideOffset: sideOffset, collisionPadding: collisionPadding, className: classes('inkra-menu', className), children: _jsx(PencilSurface, { variant: "paper", radius: 8, seed: seed ?? `menu-${id}`, className: "inkra-menu__paper", children: children }) });
19
20
  });
20
21
  function ItemBody({ children, icon }) {
@@ -1,7 +1,23 @@
1
- import type { HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
1
+ import type { FocusEvent, HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
2
+ import type { SearchableSelectOption } from '../select/index.js';
2
3
  import type { InputProps } from '../form/Input.js';
3
4
  import type { TextareaProps } from '../form/Textarea.js';
4
5
  export type EditableBlurBehavior = 'ignore' | 'save' | 'cancel';
6
+ export type EditableEditorType = 'input' | 'textarea' | 'select';
7
+ export interface EditableSelectProps {
8
+ items: readonly SearchableSelectOption[];
9
+ mode?: 'dropdown' | 'searchable' | 'stepper';
10
+ size?: 'sm' | 'md' | 'lg';
11
+ dir?: 'ltr' | 'rtl';
12
+ searchPlaceholder?: string;
13
+ emptyContent?: ReactNode;
14
+ clearable?: boolean;
15
+ clearLabel?: string;
16
+ openLabel?: string;
17
+ previousLabel?: string;
18
+ nextLabel?: string;
19
+ loop?: boolean;
20
+ }
5
21
  export type EditableSaveResult = boolean | string | void | null | undefined;
6
22
  export type EditableSaveHandler = (value: string, previousValue: string) => EditableSaveResult | Promise<EditableSaveResult>;
7
23
  export type EditableValidateHandler = (value: string, previousValue: string) => EditableSaveResult | Promise<EditableSaveResult>;
@@ -9,12 +25,16 @@ export interface EditableEditorProps {
9
25
  value: string;
10
26
  onValueChange: (value: string) => void;
11
27
  onKeyDown: (event: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
12
- onBlur: () => void;
28
+ onBlur: (event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
13
29
  disabled?: boolean;
30
+ readOnly?: boolean;
14
31
  invalid?: boolean;
15
32
  error?: string;
16
33
  autoFocus?: boolean;
17
34
  'aria-label'?: string;
35
+ 'aria-describedby'?: string;
36
+ 'aria-errormessage'?: string;
37
+ 'aria-invalid'?: boolean;
18
38
  }
19
39
  export type EditableEditor = ReactNode | ((props: EditableEditorProps) => ReactNode);
20
40
  export interface EditableProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
@@ -29,6 +49,8 @@ export interface EditableProps extends Omit<HTMLAttributes<HTMLDivElement>, 'chi
29
49
  onCancel?: (value: string) => void;
30
50
  display?: ReactNode | ((value: string) => ReactNode);
31
51
  editor?: EditableEditor;
52
+ editorType?: EditableEditorType;
53
+ selectProps?: EditableSelectProps;
32
54
  multiline?: boolean;
33
55
  inputProps?: Omit<InputProps, 'value' | 'defaultValue' | 'onValueChange' | 'onChange'>;
34
56
  textareaProps?: Omit<TextareaProps, 'value' | 'defaultValue' | 'onChange'>;
@@ -1,10 +1,20 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef, useState, } from 'react';
3
3
  import { Button } from '../Button.js';
4
+ import { PencilIcon } from '../PencilIcon.js';
5
+ import { PencilSurface } from '../PencilSurface.js';
6
+ import { PencilText } from '../PencilText.js';
4
7
  import { Input } from '../form/Input.js';
5
8
  import { Textarea } from '../form/Textarea.js';
9
+ import { Tooltip } from '../tooltip/Tooltip.js';
10
+ import { Select } from '../select/index.js';
6
11
  const classes = (...values) => values.filter(Boolean).join(' ');
7
12
  const interactiveSelector = 'button, a, input, textarea, select, option, [contenteditable="true"], [data-editable-ignore]';
13
+ const checkIcon = [{ d: 'm20 6-11 11-5-5' }];
14
+ const cancelIcon = [{ d: 'M18 6 6 18' }, { d: 'm6 6 12 12' }];
15
+ function mergeIds(...ids) {
16
+ return ids.filter(Boolean).join(' ') || undefined;
17
+ }
8
18
  function resultMessage(result, fallback) {
9
19
  if (typeof result === 'string')
10
20
  return result || fallback;
@@ -15,11 +25,15 @@ function resultMessage(result, fallback) {
15
25
  function isComposing(event) {
16
26
  return event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
17
27
  }
18
- export const Editable = forwardRef(function Editable({ 'aria-label': ariaLabel, blurBehavior = 'ignore', className, defaultEditing = false, defaultValue = '', disabled = false, display, editor, editing, id, inputProps, invalid = false, multiline = false, onCancel, onEditingChange, onSave, onValueChange, placeholder = 'Edit value', readOnly = false, saveLabel = 'Save', cancelLabel = 'Cancel', startEditingLabel = 'Edit value', seed, submitOnEnter = !multiline, textareaProps, validate, value, ...props }, forwardedRef) {
28
+ export const Editable = forwardRef(function Editable({ 'aria-label': ariaLabel, blurBehavior = 'ignore', className, defaultEditing = false, defaultValue = '', disabled = false, display, editor, editorType, editing, id, inputProps, invalid = false, multiline = false, onBlur: onRootBlur, onCancel, onEditingChange, onSave, onValueChange, placeholder = 'Edit value', readOnly = false, saveLabel = 'Save', cancelLabel = 'Cancel', startEditingLabel = 'Edit value', seed, selectProps, submitOnEnter = true, textareaProps, validate, value, ...props }, forwardedRef) {
19
29
  const generatedId = useId();
20
30
  const rootRef = useRef(null);
21
31
  const editorRef = useRef(null);
22
- const displayRef = useRef(null);
32
+ const editButtonRef = useRef(null);
33
+ const blurFrameRef = useRef(undefined);
34
+ const savingRef = useRef(false);
35
+ const popupOpenRef = useRef(false);
36
+ const popupFrameRef = useRef(undefined);
23
37
  const controlledValue = value !== undefined;
24
38
  const controlledEditing = editing !== undefined;
25
39
  const [localValue, setLocalValue] = useState(String(value ?? defaultValue));
@@ -31,6 +45,11 @@ export const Editable = forwardRef(function Editable({ 'aria-label': ariaLabel,
31
45
  const isEditing = controlledEditing ? Boolean(editing) : localEditing;
32
46
  const resolvedSeed = seed ?? `inkra-editable-${id ?? generatedId}`;
33
47
  const resolvedId = id ?? `inkra-editable-${generatedId}`;
48
+ const resolvedEditorType = editorType ?? (multiline ? 'textarea' : 'input');
49
+ const isMultiline = resolvedEditorType === 'textarea';
50
+ const resolvedSize = resolvedEditorType === 'select' ? (selectProps?.size ?? 'md') : isMultiline ? (textareaProps?.size ?? 'md') : (inputProps?.size ?? 'md');
51
+ const actionSize = resolvedSize === 'sm' ? 'icon-sm' : resolvedSize === 'lg' ? 'icon-lg' : 'icon';
52
+ const actionIconSize = resolvedSize === 'sm' ? 18 : resolvedSize === 'lg' ? 24 : 20;
34
53
  useEffect(() => {
35
54
  if (controlledValue) {
36
55
  const next = String(value ?? '');
@@ -43,9 +62,18 @@ export const Editable = forwardRef(function Editable({ 'aria-label': ariaLabel,
43
62
  useEffect(() => {
44
63
  if (!isEditing || disabled || readOnly)
45
64
  return;
46
- const frame = requestAnimationFrame(() => editorRef.current?.focus({ preventScroll: true }));
65
+ const frame = requestAnimationFrame(() => {
66
+ const control = editorRef.current ?? rootRef.current?.querySelector('[data-editable-editor] input:not([type="hidden"]), [data-editable-editor] textarea, [data-editable-editor] [role="combobox"], [data-editable-editor] [role="spinbutton"]');
67
+ control?.focus({ preventScroll: true });
68
+ });
47
69
  return () => cancelAnimationFrame(frame);
48
70
  }, [disabled, isEditing, readOnly]);
71
+ useEffect(() => () => {
72
+ if (blurFrameRef.current !== undefined)
73
+ cancelAnimationFrame(blurFrameRef.current);
74
+ if (popupFrameRef.current !== undefined)
75
+ cancelAnimationFrame(popupFrameRef.current);
76
+ }, []);
49
77
  const setEditing = useCallback((next) => {
50
78
  if (!controlledEditing)
51
79
  setLocalEditing(next);
@@ -57,31 +85,41 @@ export const Editable = forwardRef(function Editable({ 'aria-label': ariaLabel,
57
85
  onValueChange?.(next);
58
86
  }, [controlledValue, onValueChange]);
59
87
  const startEditing = useCallback(() => {
60
- if (disabled || readOnly || saving || isEditing)
88
+ if (disabled || readOnly || savingRef.current || isEditing)
61
89
  return;
62
90
  setError(undefined);
63
91
  previousValueRef.current = currentValue;
64
92
  setEditing(true);
65
- }, [currentValue, disabled, isEditing, readOnly, saving, setEditing]);
93
+ }, [currentValue, disabled, isEditing, readOnly, setEditing]);
66
94
  const finish = useCallback((restoreFocus = true) => {
95
+ popupOpenRef.current = false;
67
96
  setEditing(false);
68
97
  if (restoreFocus)
69
- requestAnimationFrame(() => displayRef.current?.focus({ preventScroll: true }));
98
+ requestAnimationFrame(() => editButtonRef.current?.focus({ preventScroll: true }));
70
99
  }, [setEditing]);
71
- const cancel = useCallback(() => {
72
- if (saving)
100
+ const cancel = useCallback((restoreFocus = true) => {
101
+ if (savingRef.current)
73
102
  return;
103
+ if (blurFrameRef.current !== undefined) {
104
+ cancelAnimationFrame(blurFrameRef.current);
105
+ blurFrameRef.current = undefined;
106
+ }
74
107
  const previous = previousValueRef.current;
75
108
  updateValue(previous);
76
109
  setError(undefined);
77
110
  onCancel?.(previous);
78
- finish();
79
- }, [finish, onCancel, saving, updateValue]);
80
- const save = useCallback(async () => {
81
- if (saving || disabled || readOnly)
111
+ finish(restoreFocus);
112
+ }, [finish, onCancel, updateValue]);
113
+ const save = useCallback(async (restoreFocus = true) => {
114
+ if (savingRef.current || disabled || readOnly)
82
115
  return;
116
+ if (blurFrameRef.current !== undefined) {
117
+ cancelAnimationFrame(blurFrameRef.current);
118
+ blurFrameRef.current = undefined;
119
+ }
83
120
  const next = currentValue;
84
121
  const previous = previousValueRef.current;
122
+ savingRef.current = true;
85
123
  setSaving(true);
86
124
  setError(undefined);
87
125
  try {
@@ -99,64 +137,131 @@ export const Editable = forwardRef(function Editable({ 'aria-label': ariaLabel,
99
137
  }
100
138
  previousValueRef.current = next;
101
139
  updateValue(next);
102
- finish();
140
+ finish(restoreFocus);
103
141
  }
104
142
  catch (caught) {
105
143
  setError(caught instanceof Error ? caught.message : 'Could not save value');
106
144
  }
107
145
  finally {
146
+ savingRef.current = false;
108
147
  setSaving(false);
109
148
  }
110
- }, [currentValue, disabled, finish, onSave, readOnly, saving, updateValue, validate]);
111
- useImperativeHandle(forwardedRef, () => ({ focus: () => displayRef.current?.focus(), startEditing, save, cancel }), [cancel, save, startEditing]);
149
+ }, [currentValue, disabled, finish, onSave, readOnly, updateValue, validate]);
150
+ useImperativeHandle(forwardedRef, () => ({ focus: () => editButtonRef.current?.focus(), startEditing, save, cancel }), [cancel, save, startEditing]);
112
151
  const handleDisplayClick = (event) => {
113
152
  if (event.target.closest(interactiveSelector))
114
153
  return;
115
154
  startEditing();
116
155
  };
117
- const handleKeyDown = (event) => {
118
- if (event.key === 'Enter' || event.key === ' ') {
119
- event.preventDefault();
120
- startEditing();
121
- }
122
- };
123
156
  const handleEditorKeyDown = (event) => {
124
- if (isComposing(event))
157
+ if (event.defaultPrevented || isComposing(event))
125
158
  return;
126
159
  if (event.key === 'Escape') {
127
160
  event.preventDefault();
128
161
  cancel();
129
162
  return;
130
163
  }
131
- if (event.key === 'Enter' && submitOnEnter && (!multiline || event.ctrlKey || event.metaKey)) {
164
+ if (event.key === 'Enter' && submitOnEnter && (!isMultiline || event.ctrlKey || event.metaKey)) {
132
165
  event.preventDefault();
133
166
  void save();
134
167
  }
135
168
  };
136
- const handleEditorBlur = () => {
137
- if (saving || blurBehavior === 'ignore')
169
+ const handleEditorBlur = (event) => {
170
+ if (event.defaultPrevented || popupOpenRef.current || rootRef.current?.contains(event.relatedTarget))
138
171
  return;
139
- if (blurBehavior === 'cancel')
140
- cancel();
141
- else
142
- void save();
172
+ if (blurFrameRef.current !== undefined)
173
+ cancelAnimationFrame(blurFrameRef.current);
174
+ blurFrameRef.current = requestAnimationFrame(() => {
175
+ blurFrameRef.current = undefined;
176
+ if (popupOpenRef.current || rootRef.current?.contains(document.activeElement) || savingRef.current || blurBehavior === 'ignore')
177
+ return;
178
+ if (blurBehavior === 'cancel')
179
+ cancel(false);
180
+ else
181
+ void save(false);
182
+ });
143
183
  };
144
- const displayContent = typeof display === 'function'
145
- ? display(currentValue)
146
- : (display ?? currentValue) || placeholder;
184
+ const displayValue = resolvedEditorType === 'select' ? (selectProps?.items.find(item => item.value === currentValue)?.label ?? currentValue) : currentValue;
185
+ const displayContent = display == null
186
+ ? _jsx(PencilText, { seed: `${resolvedSeed}-display-text`, children: displayValue || placeholder })
187
+ : typeof display === 'function' ? display(currentValue) : display;
147
188
  const editorProps = {
148
189
  value: currentValue,
149
190
  onValueChange: updateValue,
150
191
  onKeyDown: handleEditorKeyDown,
151
192
  onBlur: handleEditorBlur,
152
193
  disabled: disabled || saving,
194
+ readOnly,
153
195
  invalid: invalid || Boolean(error),
154
196
  error,
155
197
  autoFocus: true,
156
198
  'aria-label': ariaLabel,
199
+ 'aria-describedby': error ? `${resolvedId}-error` : undefined,
200
+ 'aria-errormessage': error ? `${resolvedId}-error` : undefined,
201
+ 'aria-invalid': invalid || Boolean(error) || undefined,
202
+ };
203
+ const inputDescription = mergeIds(inputProps?.['aria-describedby'], error ? `${resolvedId}-error` : undefined);
204
+ const textareaDescription = mergeIds(textareaProps?.['aria-describedby'], error ? `${resolvedId}-error` : undefined);
205
+ const actions = _jsx(Tooltip.Provider, { children: _jsxs("span", { "aria-label": `${saveLabel}, ${cancelLabel}`, className: "inkra-editable__actions", role: "group", children: [_jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: _jsx(Button, { "aria-label": saveLabel, className: "inkra-editable__action", disabled: disabled || readOnly, loading: saving, onClick: () => void save(), size: actionSize, title: saveLabel, type: "button", variant: "ghost", children: _jsx(PencilIcon, { decorative: true, paths: checkIcon, seed: `${resolvedSeed}-save-icon`, size: actionIconSize, sourceSize: 24 }) }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Content, { children: saveLabel }) })] }), _jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: _jsx(Button, { "aria-label": cancelLabel, className: "inkra-editable__action", disabled: disabled || saving, onClick: () => cancel(), size: actionSize, title: cancelLabel, type: "button", variant: "ghost", children: _jsx(PencilIcon, { decorative: true, paths: cancelIcon, seed: `${resolvedSeed}-cancel-icon`, size: actionIconSize, sourceSize: 24 }) }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Content, { children: cancelLabel }) })] })] }) });
206
+ const changePopupOpen = (open) => {
207
+ if (popupFrameRef.current !== undefined)
208
+ cancelAnimationFrame(popupFrameRef.current);
209
+ if (open)
210
+ popupOpenRef.current = true;
211
+ else
212
+ popupFrameRef.current = requestAnimationFrame(() => {
213
+ popupOpenRef.current = false;
214
+ if (!rootRef.current || rootRef.current.contains(document.activeElement) || document.activeElement === document.body || savingRef.current)
215
+ return;
216
+ if (blurBehavior === 'cancel')
217
+ cancel(false);
218
+ else if (blurBehavior === 'save')
219
+ void save(false);
220
+ });
157
221
  };
158
- const defaultEditor = multiline
159
- ? _jsx(Textarea, { ...textareaProps, "aria-label": ariaLabel, autoFocus: true, ref: editorRef, value: currentValue, onChange: event => updateValue(event.currentTarget.value), onKeyDown: handleEditorKeyDown, onBlur: handleEditorBlur, disabled: disabled || saving, invalid: invalid || Boolean(error), placeholder: placeholder, seed: `${resolvedSeed}-textarea` })
160
- : _jsx(Input, { ...inputProps, "aria-label": ariaLabel, autoFocus: true, ref: editorRef, value: currentValue, onValueChange: updateValue, onKeyDown: handleEditorKeyDown, onBlur: handleEditorBlur, disabled: disabled || saving, invalid: invalid || Boolean(error), placeholder: placeholder, seed: `${resolvedSeed}-input` });
161
- return _jsx("div", { ...props, className: classes('inkra-editable', className), "data-editing": isEditing || undefined, "data-saving": saving || undefined, "data-invalid": Boolean(error) || invalid || undefined, "data-multiline": multiline || undefined, ref: rootRef, children: isEditing ? _jsxs("div", { className: "inkra-editable__editor", "data-editable-editor": "true", children: [editor == null ? defaultEditor : typeof editor === 'function' ? editor(editorProps) : editor, _jsxs("div", { className: "inkra-editable__actions", children: [_jsx(Button, { type: "button", size: "sm", variant: "primary", loading: saving, onClick: () => void save(), "aria-label": saveLabel, children: saveLabel }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", disabled: saving, onClick: cancel, "aria-label": cancelLabel, children: cancelLabel })] }), error ? _jsx("div", { className: "inkra-editable__error", role: "alert", id: `${resolvedId}-error`, children: error }) : null] }) : _jsx("div", { "aria-label": ariaLabel ?? startEditingLabel, className: "inkra-editable__display", "data-editable-display": "true", onClick: handleDisplayClick, onKeyDown: handleKeyDown, ref: displayRef, role: "button", tabIndex: disabled || readOnly ? -1 : 0, "aria-disabled": disabled || readOnly || undefined, children: displayContent }) });
222
+ const items = selectProps?.items ?? [];
223
+ const selectState = {
224
+ value: currentValue,
225
+ onValueChange: (next) => updateValue(next ?? ''),
226
+ disabled: disabled || saving,
227
+ readOnly,
228
+ size: resolvedSize,
229
+ dir: selectProps?.dir,
230
+ placeholder,
231
+ 'aria-label': ariaLabel ?? startEditingLabel,
232
+ 'aria-describedby': error ? `${resolvedId}-error` : undefined,
233
+ 'aria-errormessage': error ? `${resolvedId}-error` : undefined,
234
+ invalid: invalid || Boolean(error),
235
+ };
236
+ const selectEditor = _jsxs("div", { className: "inkra-editable__select-row", onKeyDown: event => {
237
+ if (event.defaultPrevented || popupOpenRef.current || event.target !== event.currentTarget.ownerDocument.activeElement)
238
+ return;
239
+ if (event.key === 'Escape' && !event.nativeEvent.isComposing) {
240
+ event.preventDefault();
241
+ cancel();
242
+ }
243
+ }, children: [selectProps?.mode === 'searchable' ? _jsx(Select.Searchable, { ...selectState, items: items, searchPlaceholder: selectProps.searchPlaceholder, emptyContent: selectProps.emptyContent, clearable: selectProps.clearable ?? false, clearLabel: selectProps.clearLabel, openLabel: selectProps.openLabel, onOpenChange: changePopupOpen })
244
+ : selectProps?.mode === 'stepper' ? _jsx(Select.Stepper, { ...selectState, items: items, loop: selectProps.loop, previousLabel: selectProps.previousLabel, nextLabel: selectProps.nextLabel })
245
+ : _jsxs(Select.Root, { value: currentValue, onValueChange: updateValue, disabled: disabled || saving, readOnly: readOnly, dir: selectProps?.dir, onOpenChange: changePopupOpen, children: [_jsx(Select.Trigger, { "aria-label": ariaLabel ?? startEditingLabel, "aria-describedby": selectState['aria-describedby'], "aria-errormessage": selectState['aria-errormessage'], invalid: selectState.invalid, size: resolvedSize, children: _jsx(Select.Value, { placeholder: placeholder }) }), _jsx(Select.Portal, { children: _jsx(Select.Content, { children: _jsx(Select.Viewport, { children: items.filter(item => item.value !== '').map(item => _jsxs(Select.Item, { value: item.value, disabled: item.disabled, children: [_jsx(Select.ItemText, { children: item.label }), _jsx(Select.ItemIndicator, {})] }, item.value)) }) }) })] }), actions] });
246
+ const defaultEditor = resolvedEditorType === 'select' ? selectEditor : isMultiline
247
+ ? _jsx(Textarea, { variant: "underline", ...textareaProps, endAction: _jsxs(_Fragment, { children: [textareaProps?.endAction, actions] }), "aria-describedby": textareaDescription, "aria-errormessage": error ? `${resolvedId}-error` : textareaProps?.['aria-errormessage'], "aria-invalid": invalid || Boolean(error) || textareaProps?.['aria-invalid'], "aria-label": ariaLabel, autoFocus: true, disabled: disabled || saving, invalid: invalid || Boolean(error), onBlur: event => {
248
+ textareaProps?.onBlur?.(event);
249
+ handleEditorBlur(event);
250
+ }, onChange: event => updateValue(event.currentTarget.value), onKeyDown: event => {
251
+ textareaProps?.onKeyDown?.(event);
252
+ handleEditorKeyDown(event);
253
+ }, placeholder: placeholder, readOnly: readOnly, ref: editorRef, seed: `${resolvedSeed}-textarea`, value: currentValue })
254
+ : _jsx(Input, { variant: "underline", ...inputProps, endAction: _jsxs(_Fragment, { children: [inputProps?.endAction, actions] }), "aria-describedby": inputDescription, "aria-errormessage": error ? `${resolvedId}-error` : inputProps?.['aria-errormessage'], "aria-invalid": invalid || Boolean(error) || inputProps?.['aria-invalid'], "aria-label": ariaLabel, autoFocus: true, disabled: disabled || saving, invalid: invalid || Boolean(error), onBlur: event => {
255
+ inputProps?.onBlur?.(event);
256
+ handleEditorBlur(event);
257
+ }, onKeyDown: event => {
258
+ inputProps?.onKeyDown?.(event);
259
+ handleEditorKeyDown(event);
260
+ }, onValueChange: updateValue, placeholder: placeholder, readOnly: readOnly, ref: editorRef, seed: `${resolvedSeed}-input`, value: currentValue });
261
+ return _jsx("div", { ...props, className: classes('inkra-editable', className), "data-disabled": disabled || undefined, "data-editing": isEditing || undefined, "data-saving": saving || undefined, "data-invalid": Boolean(error) || invalid || undefined, "data-multiline": isMultiline || undefined, "data-editor-type": resolvedEditorType, "data-readonly": readOnly || undefined, "data-size": resolvedSize, onBlur: event => {
262
+ onRootBlur?.(event);
263
+ handleEditorBlur(event);
264
+ }, ref: rootRef, children: _jsx("div", { className: "inkra-editable__frame", children: isEditing ? _jsxs("div", { className: "inkra-editable__editor", "data-editable-editor": "true", children: [editor == null ? defaultEditor : _jsxs("div", { className: "inkra-editable__custom-row", children: [typeof editor === 'function' ? editor(editorProps) : editor, actions] }), error ? _jsx(PencilText, { as: "div", className: "inkra-editable__error", role: "alert", id: `${resolvedId}-error`, seed: `${resolvedSeed}-error`, children: error }) : null] }) : _jsxs("div", { className: "inkra-editable__display-shell", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-editable__focus-ring", seed: `${resolvedSeed}-focus`, variant: "outline" }), display == null
265
+ ? _jsx("button", { "aria-label": ariaLabel ?? startEditingLabel, className: "inkra-editable__display", "data-editable-display": "true", disabled: disabled || readOnly, onClick: startEditing, ref: editButtonRef, type: "button", children: displayContent })
266
+ : _jsxs(_Fragment, { children: [_jsx("div", { className: "inkra-editable__display", "data-editable-display": "true", onClick: handleDisplayClick, children: displayContent }), _jsx("button", { "aria-label": ariaLabel ?? startEditingLabel, className: "inkra-editable__custom-trigger", disabled: disabled || readOnly, onClick: startEditing, ref: editButtonRef, type: "button", children: startEditingLabel })] })] }) }) });
162
267
  });
@@ -0,0 +1,8 @@
1
+ import type { PencilIconPath } from '../PencilIcon.js';
2
+ export type EmptyIllustration = 'inbox' | 'search' | 'folder' | 'documents' | 'messages' | 'image' | 'offline' | 'favorites';
3
+ type Illustration = {
4
+ ink: readonly PencilIconPath[];
5
+ accent: readonly PencilIconPath[];
6
+ };
7
+ export declare const emptyIllustrations: Record<EmptyIllustration, Illustration>;
8
+ export {};