@inkra/react 0.1.0 → 0.2.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 (160) hide show
  1. package/README.md +104 -1
  2. package/dist/PencilIcon.js +14 -16
  3. package/dist/PencilLine.js +18 -12
  4. package/dist/PencilText.js +192 -59
  5. package/dist/alert-dialog/index.d.ts +64 -0
  6. package/dist/alert-dialog/index.js +109 -0
  7. package/dist/audio-player/index.d.ts +60 -0
  8. package/dist/audio-player/index.js +101 -0
  9. package/dist/carousel/index.d.ts +51 -0
  10. package/dist/carousel/index.js +290 -0
  11. package/dist/choice-grid/index.d.ts +38 -0
  12. package/dist/choice-grid/index.js +85 -0
  13. package/dist/color-picker/color.d.ts +15 -0
  14. package/dist/color-picker/color.js +55 -0
  15. package/dist/color-picker/index.d.ts +31 -0
  16. package/dist/color-picker/index.js +69 -0
  17. package/dist/command/index.d.ts +23 -0
  18. package/dist/command/index.js +39 -0
  19. package/dist/context-menu/index.d.ts +76 -0
  20. package/dist/context-menu/index.js +240 -0
  21. package/dist/data-table/index.d.ts +47 -0
  22. package/dist/data-table/index.js +91 -0
  23. package/dist/date-picker/index.d.ts +50 -0
  24. package/dist/date-picker/index.js +180 -0
  25. package/dist/display/ProgressRing.d.ts +6 -0
  26. package/dist/display/ProgressRing.js +146 -0
  27. package/dist/display/index.d.ts +27 -0
  28. package/dist/display/index.js +18 -2
  29. package/dist/dockable-panel/index.d.ts +41 -0
  30. package/dist/dockable-panel/index.js +253 -0
  31. package/dist/dockable-panel/model.d.ts +21 -0
  32. package/dist/dockable-panel/model.js +61 -0
  33. package/dist/drawer/index.d.ts +63 -0
  34. package/dist/drawer/index.js +86 -0
  35. package/dist/editable/index.d.ts +53 -0
  36. package/dist/editable/index.js +162 -0
  37. package/dist/empty/index.d.ts +21 -0
  38. package/dist/empty/index.js +23 -0
  39. package/dist/flip/index.d.ts +49 -0
  40. package/dist/flip/index.js +138 -0
  41. package/dist/form/EditablePencilText.js +157 -146
  42. package/dist/form/Textarea.d.ts +12 -0
  43. package/dist/form/Textarea.js +83 -4
  44. package/dist/form/index.d.ts +1 -1
  45. package/dist/heatmap/index.d.ts +59 -0
  46. package/dist/heatmap/index.js +248 -0
  47. package/dist/hotkey/index.d.ts +37 -0
  48. package/dist/hotkey/index.js +130 -0
  49. package/dist/hotkey/model.d.ts +27 -0
  50. package/dist/hotkey/model.js +97 -0
  51. package/dist/hover-card/index.d.ts +33 -0
  52. package/dist/hover-card/index.js +141 -0
  53. package/dist/image-cropper/index.d.ts +82 -0
  54. package/dist/image-cropper/index.js +261 -0
  55. package/dist/image-cropper/model.d.ts +42 -0
  56. package/dist/image-cropper/model.js +94 -0
  57. package/dist/image-viewer/index.d.ts +79 -0
  58. package/dist/image-viewer/index.js +220 -0
  59. package/dist/image-viewer/model.d.ts +9 -0
  60. package/dist/image-viewer/model.js +18 -0
  61. package/dist/index.d.ts +47 -0
  62. package/dist/index.js +47 -0
  63. package/dist/internal/cache.d.ts +1 -1
  64. package/dist/internal/cache.js +1 -1
  65. package/dist/internal/renderTokens.d.ts +10 -0
  66. package/dist/internal/renderTokens.js +13 -0
  67. package/dist/internal/surfaceRenderer.d.ts +14 -6
  68. package/dist/internal/surfaceRenderer.js +15 -12
  69. package/dist/internal/textRenderer.d.ts +1 -0
  70. package/dist/internal/textRenderer.js +33 -7
  71. package/dist/internal/usePencilSurface.js +16 -8
  72. package/dist/internal/useSelectionDrawing.js +15 -11
  73. package/dist/journey-map/MapScale.js +3 -1
  74. package/dist/journey-map/index.js +3 -1
  75. package/dist/keypad/index.d.ts +23 -0
  76. package/dist/keypad/index.js +63 -0
  77. package/dist/keypad/model.d.ts +20 -0
  78. package/dist/keypad/model.js +33 -0
  79. package/dist/layout/index.d.ts +63 -0
  80. package/dist/layout/index.js +62 -0
  81. package/dist/log/index.d.ts +45 -0
  82. package/dist/log/index.js +159 -0
  83. package/dist/log/model.d.ts +16 -0
  84. package/dist/log/model.js +13 -0
  85. package/dist/node-graph/index.d.ts +123 -0
  86. package/dist/node-graph/index.js +304 -0
  87. package/dist/number-ticker/index.d.ts +29 -0
  88. package/dist/number-ticker/index.js +132 -0
  89. package/dist/number-ticker/model.d.ts +8 -0
  90. package/dist/number-ticker/model.js +34 -0
  91. package/dist/overflow-list/index.d.ts +39 -0
  92. package/dist/overflow-list/index.js +230 -0
  93. package/dist/overflow-list/layout.d.ts +19 -0
  94. package/dist/overflow-list/layout.js +44 -0
  95. package/dist/pagination/index.d.ts +23 -0
  96. package/dist/pagination/index.js +41 -0
  97. package/dist/pagination/model.d.ts +4 -0
  98. package/dist/pagination/model.js +42 -0
  99. package/dist/pan-zoom/index.d.ts +71 -0
  100. package/dist/pan-zoom/index.js +175 -0
  101. package/dist/radar/index.d.ts +54 -0
  102. package/dist/radar/index.js +220 -0
  103. package/dist/range-slider/index.d.ts +16 -0
  104. package/dist/range-slider/index.js +22 -0
  105. package/dist/rating/index.d.ts +28 -0
  106. package/dist/rating/index.js +127 -0
  107. package/dist/resizable/index.d.ts +32 -0
  108. package/dist/resizable/index.js +50 -0
  109. package/dist/scroll-area/index.d.ts +36 -0
  110. package/dist/scroll-area/index.js +44 -0
  111. package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
  112. package/dist/segmented-progress/SegmentedProgress.js +42 -0
  113. package/dist/slider/index.js +1 -1
  114. package/dist/slot/index.d.ts +24 -0
  115. package/dist/slot/index.js +29 -0
  116. package/dist/sortable/index.d.ts +50 -0
  117. package/dist/sortable/index.js +278 -0
  118. package/dist/sortable/model.d.ts +30 -0
  119. package/dist/sortable/model.js +64 -0
  120. package/dist/sparkline/index.d.ts +19 -0
  121. package/dist/sparkline/index.js +240 -0
  122. package/dist/sparkline/model.d.ts +16 -0
  123. package/dist/sparkline/model.js +28 -0
  124. package/dist/steps/index.d.ts +16 -0
  125. package/dist/steps/index.js +37 -0
  126. package/dist/styles.css +4850 -287
  127. package/dist/tag-input/index.d.ts +69 -0
  128. package/dist/tag-input/index.js +325 -0
  129. package/dist/tag-input/model.d.ts +14 -0
  130. package/dist/tag-input/model.js +38 -0
  131. package/dist/time-picker/index.d.ts +15 -0
  132. package/dist/time-picker/index.js +168 -0
  133. package/dist/timeline/index.d.ts +14 -0
  134. package/dist/timeline/index.js +16 -0
  135. package/dist/toast/index.d.ts +27 -0
  136. package/dist/toast/index.js +36 -0
  137. package/dist/toggle-group/index.d.ts +17 -0
  138. package/dist/toggle-group/index.js +26 -0
  139. package/dist/tokens.css +10 -9
  140. package/dist/transfer/index.d.ts +64 -0
  141. package/dist/transfer/index.js +174 -0
  142. package/dist/transfer/model.d.ts +12 -0
  143. package/dist/transfer/model.js +33 -0
  144. package/dist/tree/index.d.ts +39 -0
  145. package/dist/tree/index.js +36 -0
  146. package/dist/tree-select/index.d.ts +59 -0
  147. package/dist/tree-select/index.js +142 -0
  148. package/dist/tree-select/model.d.ts +31 -0
  149. package/dist/tree-select/model.js +135 -0
  150. package/dist/typewriter/index.d.ts +21 -0
  151. package/dist/typewriter/index.js +101 -0
  152. package/dist/typewriter/model.d.ts +7 -0
  153. package/dist/typewriter/model.js +45 -0
  154. package/dist/upload/index.d.ts +59 -0
  155. package/dist/upload/index.js +230 -0
  156. package/dist/virtual-list/index.d.ts +44 -0
  157. package/dist/virtual-list/index.js +128 -0
  158. package/dist/virtual-list/model.d.ts +7 -0
  159. package/dist/virtual-list/model.js +27 -0
  160. package/package.json +22 -3
@@ -0,0 +1,79 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { PanZoomWheelMode } from '../pan-zoom/index.js';
3
+ import type { OverlayContentProps, OverlayRootProps, OverlayTriggerProps } from '../overlay/index.js';
4
+ export interface ImageViewerItem {
5
+ id: string;
6
+ src: string;
7
+ alt: string;
8
+ thumbnailSrc?: string;
9
+ thumbnailAlt?: string;
10
+ width?: number;
11
+ height?: number;
12
+ }
13
+ export type ImageViewerChangeReason = 'previous' | 'next' | 'thumbnail' | 'home' | 'end' | 'items-change';
14
+ export interface ImageViewerChangeDetails {
15
+ item: ImageViewerItem | null;
16
+ reason: ImageViewerChangeReason;
17
+ }
18
+ export interface ImageViewerRootProps extends Omit<OverlayRootProps, 'open' | 'defaultOpen' | 'onOpenChange'> {
19
+ items: readonly ImageViewerItem[];
20
+ activeId?: string | null;
21
+ defaultActiveId?: string | null;
22
+ onActiveIdChange?: (activeId: string | null, details: ImageViewerChangeDetails) => void;
23
+ open?: boolean;
24
+ defaultOpen?: boolean;
25
+ onOpenChange?: (open: boolean) => void;
26
+ loop?: boolean;
27
+ }
28
+ export declare function ImageViewerRoot({ activeId, defaultActiveId, defaultOpen, items, loop, onActiveIdChange, onOpenChange, open, ...props }: ImageViewerRootProps): import("react").JSX.Element;
29
+ export type ImageViewerTriggerProps = OverlayTriggerProps;
30
+ export declare const ImageViewerTrigger: import("react").ForwardRefExoticComponent<import("@radix-ui/react-dialog").DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
31
+ export interface ImageViewerLabels {
32
+ title: string;
33
+ description: string;
34
+ close: string;
35
+ previous: string;
36
+ next: string;
37
+ thumbnails: string;
38
+ imagePosition: (current: number, total: number) => string;
39
+ thumbnail: (item: ImageViewerItem, current: number, total: number) => string;
40
+ failedTitle: string;
41
+ failedDescription: (item: ImageViewerItem) => string;
42
+ retry: string;
43
+ zoomIn: string;
44
+ zoomOut: string;
45
+ resetZoom: string;
46
+ rotate: string;
47
+ panZoom: (item: ImageViewerItem) => string;
48
+ emptyTitle: string;
49
+ emptyDescription: string;
50
+ }
51
+ export interface ImageViewerImageRenderProps {
52
+ alt: string;
53
+ className: string;
54
+ height?: number;
55
+ key: string;
56
+ onError: () => void;
57
+ onLoad: () => void;
58
+ src: string;
59
+ width?: number;
60
+ }
61
+ export interface ImageViewerContentProps extends Omit<OverlayContentProps, 'children' | 'layout'> {
62
+ labels?: Partial<ImageViewerLabels>;
63
+ showThumbnails?: boolean;
64
+ renderImage?: (item: ImageViewerItem, props: ImageViewerImageRenderProps) => ReactNode;
65
+ emptyState?: ReactNode;
66
+ zoomable?: boolean;
67
+ rotatable?: boolean;
68
+ minScale?: number;
69
+ maxScale?: number;
70
+ wheel?: PanZoomWheelMode;
71
+ }
72
+ export declare const ImageViewerContent: import("react").ForwardRefExoticComponent<ImageViewerContentProps & import("react").RefAttributes<HTMLDivElement>>;
73
+ export declare const ImageViewer: Readonly<{
74
+ Root: typeof ImageViewerRoot;
75
+ Trigger: import("react").ForwardRefExoticComponent<import("@radix-ui/react-dialog").DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
76
+ Content: import("react").ForwardRefExoticComponent<ImageViewerContentProps & import("react").RefAttributes<HTMLDivElement>>;
77
+ }>;
78
+ export { nextImageViewerIndex, resolveImageViewerSelection } from './model.js';
79
+ export type { ImageViewerIdentifiable, ImageViewerSelection } from './model.js';
@@ -0,0 +1,220 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { __iconData as chevronLeftIcon } from 'lucide-react/dist/esm/icons/chevron-left.mjs';
3
+ import { __iconData as chevronRightIcon } from 'lucide-react/dist/esm/icons/chevron-right.mjs';
4
+ import { __iconData as imageOffIcon } from 'lucide-react/dist/esm/icons/image-off.mjs';
5
+ import { __iconData as rotateIcon } from 'lucide-react/dist/esm/icons/rotate-cw.mjs';
6
+ import { __iconData as zoomInIcon } from 'lucide-react/dist/esm/icons/zoom-in.mjs';
7
+ import { __iconData as zoomOutIcon } from 'lucide-react/dist/esm/icons/zoom-out.mjs';
8
+ import { createContext, forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
9
+ import { Button } from '../Button.js';
10
+ import { PencilIcon } from '../PencilIcon.js';
11
+ import { PencilSurface } from '../PencilSurface.js';
12
+ import { PencilText } from '../PencilText.js';
13
+ import { PanZoom } from '../pan-zoom/index.js';
14
+ import { OverlayBackdrop, OverlayContent, OverlayDescription, OverlayHeader, OverlayPortal, OverlayRoot, OverlayTitle, OverlayTrigger, } from '../overlay/index.js';
15
+ import { nextImageViewerIndex, resolveImageViewerSelection } from './model.js';
16
+ const chevronLeftPaths = chevronLeftIcon.node.map(([, attributes]) => ({ d: attributes.d }));
17
+ const chevronRightPaths = chevronRightIcon.node.map(([, attributes]) => ({ d: attributes.d }));
18
+ const imageOffPaths = imageOffIcon.node.map(([, attributes]) => ({ d: attributes.d }));
19
+ const rotatePaths = rotateIcon.node.map(([, attributes]) => ({ d: attributes.d }));
20
+ const zoomInPaths = zoomInIcon.node.map(([, attributes]) => ({ d: attributes.d }));
21
+ const zoomOutPaths = zoomOutIcon.node.map(([, attributes]) => ({ d: attributes.d }));
22
+ const ImageViewerContext = createContext(null);
23
+ function useImageViewerContext() {
24
+ const context = useContext(ImageViewerContext);
25
+ if (!context)
26
+ throw new Error('ImageViewer components must be used inside ImageViewer.Root.');
27
+ return context;
28
+ }
29
+ export function ImageViewerRoot({ activeId, defaultActiveId, defaultOpen = false, items, loop = true, onActiveIdChange, onOpenChange, open, ...props }) {
30
+ const activeControlled = activeId !== undefined;
31
+ const openControlled = open !== undefined;
32
+ const [internalActiveId, setInternalActiveId] = useState(defaultActiveId ?? items[0]?.id ?? null);
33
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
34
+ const previousIndexRef = useRef(0);
35
+ const repairRef = useRef('');
36
+ const requestedId = activeControlled ? activeId : internalActiveId;
37
+ const selection = resolveImageViewerSelection(items, requestedId, previousIndexRef.current);
38
+ if (selection.index >= 0)
39
+ previousIndexRef.current = selection.index;
40
+ const commitActiveId = useCallback((nextId, reason) => {
41
+ if (!activeControlled)
42
+ setInternalActiveId(nextId);
43
+ onActiveIdChange?.(nextId, {
44
+ item: items.find(item => item.id === nextId) ?? null,
45
+ reason,
46
+ });
47
+ }, [activeControlled, items, onActiveIdChange]);
48
+ useEffect(() => {
49
+ if (requestedId === selection.activeId) {
50
+ repairRef.current = '';
51
+ return;
52
+ }
53
+ const repairKey = `${String(requestedId)}:${String(selection.activeId)}:${items.map(item => item.id).join('|')}`;
54
+ if (repairRef.current === repairKey)
55
+ return;
56
+ repairRef.current = repairKey;
57
+ commitActiveId(selection.activeId, 'items-change');
58
+ }, [commitActiveId, items, requestedId, selection.activeId]);
59
+ const selectIndex = useCallback((index, reason) => {
60
+ if (items.length === 0)
61
+ return;
62
+ const nextIndex = Math.min(Math.max(0, index), items.length - 1);
63
+ if (items[nextIndex].id !== selection.activeId)
64
+ commitActiveId(items[nextIndex].id, reason);
65
+ }, [commitActiveId, items, selection.activeId]);
66
+ const step = useCallback((direction) => {
67
+ const nextIndex = nextImageViewerIndex(items.length, selection.index, direction, loop);
68
+ if (nextIndex !== selection.index)
69
+ selectIndex(nextIndex, direction < 0 ? 'previous' : 'next');
70
+ }, [items.length, loop, selectIndex, selection.index]);
71
+ const context = useMemo(() => ({
72
+ items,
73
+ activeItem: selection.index < 0 ? null : items[selection.index],
74
+ activeIndex: selection.index,
75
+ loop,
76
+ selectIndex,
77
+ step,
78
+ }), [items, loop, selectIndex, selection.index, step]);
79
+ return (_jsx(ImageViewerContext.Provider, { value: context, children: _jsx(OverlayRoot, { ...props, open: openControlled ? open : internalOpen, onOpenChange: (nextOpen) => {
80
+ if (!openControlled)
81
+ setInternalOpen(nextOpen);
82
+ onOpenChange?.(nextOpen);
83
+ } }) }));
84
+ }
85
+ export const ImageViewerTrigger = forwardRef(function ImageViewerTrigger(props, ref) {
86
+ return _jsx(OverlayTrigger, { ...props, ref: ref });
87
+ });
88
+ const defaultLabels = {
89
+ title: 'Image viewer',
90
+ description: 'Use the previous and next controls or arrow keys to browse images.',
91
+ close: 'Close image viewer',
92
+ previous: 'Previous image',
93
+ next: 'Next image',
94
+ thumbnails: 'Image thumbnails',
95
+ imagePosition: (current, total) => `${current} of ${total}`,
96
+ thumbnail: (item, current, total) => `Show ${item.alt}, ${current} of ${total}`,
97
+ failedTitle: 'Image unavailable',
98
+ failedDescription: item => `${item.alt} could not be loaded.`,
99
+ retry: 'Retry',
100
+ zoomIn: 'Zoom in',
101
+ zoomOut: 'Zoom out',
102
+ resetZoom: 'Reset zoom',
103
+ rotate: 'Rotate clockwise',
104
+ panZoom: item => `Pan and zoom ${item.alt}`,
105
+ emptyTitle: 'No images',
106
+ emptyDescription: 'There are no images to show.',
107
+ };
108
+ function directionFor(node) {
109
+ return getComputedStyle(node).direction === 'rtl' ? 'rtl' : 'ltr';
110
+ }
111
+ export const ImageViewerContent = forwardRef(function ImageViewerContent({ className, emptyState, labels: labelOverrides, onOpenAutoFocus, renderImage, rotatable = false, showThumbnails = true, zoomable = true, minScale = 0.5, maxScale = 6, wheel = 'modifier', ...props }, forwardedRef) {
112
+ const { activeIndex, activeItem, items, loop, selectIndex, step } = useImageViewerContext();
113
+ const labels = { ...defaultLabels, ...labelOverrides };
114
+ const stageRef = useRef(null);
115
+ const thumbnailRefs = useRef(new Map());
116
+ const panZoomRef = useRef(null);
117
+ const [failedIds, setFailedIds] = useState(() => new Set());
118
+ const [attempts, setAttempts] = useState({});
119
+ const [rotation, setRotation] = useState(0);
120
+ const total = items.length;
121
+ const atStart = activeIndex <= 0;
122
+ const atEnd = activeIndex >= total - 1;
123
+ useEffect(() => {
124
+ if (activeItem)
125
+ thumbnailRefs.current.get(activeItem.id)?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
126
+ setRotation(0);
127
+ }, [activeItem]);
128
+ const handleStageKeyDown = (event) => {
129
+ if (event.target !== event.currentTarget)
130
+ return;
131
+ const rtl = directionFor(event.currentTarget) === 'rtl';
132
+ if (event.key === 'ArrowLeft') {
133
+ event.preventDefault();
134
+ step(rtl ? 1 : -1);
135
+ }
136
+ else if (event.key === 'ArrowRight') {
137
+ event.preventDefault();
138
+ step(rtl ? -1 : 1);
139
+ }
140
+ else if (event.key === 'Home') {
141
+ event.preventDefault();
142
+ selectIndex(0, 'home');
143
+ }
144
+ else if (event.key === 'End') {
145
+ event.preventDefault();
146
+ selectIndex(total - 1, 'end');
147
+ }
148
+ };
149
+ const selectThumbnail = (index, reason = 'thumbnail') => {
150
+ selectIndex(index, reason);
151
+ const item = items[index];
152
+ if (item)
153
+ requestAnimationFrame(() => thumbnailRefs.current.get(item.id)?.focus());
154
+ };
155
+ const renderDefaultImage = (item, { key, ...imageProps }) => (_jsx("img", { ...imageProps, draggable: false }, key));
156
+ const failed = activeItem ? failedIds.has(activeItem.id) : false;
157
+ const imageProps = activeItem ? {
158
+ alt: activeItem.alt,
159
+ className: 'inkra-image-viewer__image',
160
+ height: activeItem.height,
161
+ key: `${activeItem.id}:${attempts[activeItem.id] ?? 0}`,
162
+ onError: () => setFailedIds(current => new Set(current).add(activeItem.id)),
163
+ onLoad: () => setFailedIds(current => {
164
+ if (!current.has(activeItem.id))
165
+ return current;
166
+ const next = new Set(current);
167
+ next.delete(activeItem.id);
168
+ return next;
169
+ }),
170
+ src: activeItem.src,
171
+ width: activeItem.width,
172
+ } : null;
173
+ return (_jsxs(OverlayPortal, { children: [_jsx(OverlayBackdrop, {}), _jsxs(OverlayContent, { ...props, className: ['inkra-image-viewer', className].filter(Boolean).join(' '), layout: "sections", onOpenAutoFocus: (event) => {
174
+ onOpenAutoFocus?.(event);
175
+ if (event.defaultPrevented)
176
+ return;
177
+ event.preventDefault();
178
+ stageRef.current?.focus();
179
+ }, ref: forwardedRef, surfaceRadius: 8, children: [_jsxs(OverlayHeader, { closeLabel: labels.close, children: [_jsx(OverlayTitle, { children: labels.title }), _jsx(OverlayDescription, { className: "inkra-image-viewer__description", children: labels.description }), total > 0 ? _jsx(PencilText, { as: "span", className: "inkra-image-viewer__counter", children: labels.imagePosition(activeIndex + 1, total) }) : null] }), _jsx("div", { className: "inkra-image-viewer__body", children: activeItem && imageProps ? _jsx(_Fragment, { children: _jsxs("div", { "aria-label": activeItem.alt, className: "inkra-image-viewer__stage", onKeyDown: handleStageKeyDown, ref: stageRef, tabIndex: 0, children: [failed ? _jsxs("div", { className: "inkra-image-viewer__state", role: "alert", children: [_jsx(PencilIcon, { decorative: true, paths: imageOffPaths, size: 48, sourceSize: 24 }), _jsx(PencilText, { as: "h3", children: labels.failedTitle }), _jsx(PencilText, { as: "p", children: labels.failedDescription(activeItem) }), _jsx(Button, { variant: "outline", onClick: () => {
180
+ const failedId = activeItem.id;
181
+ setFailedIds(current => {
182
+ const next = new Set(current);
183
+ next.delete(failedId);
184
+ return next;
185
+ });
186
+ setAttempts(current => ({ ...current, [failedId]: (current[failedId] ?? 0) + 1 }));
187
+ }, children: labels.retry })] }) : _jsx(PanZoom, { "aria-label": labels.panZoom(activeItem), centerOnInit: true, className: "inkra-image-viewer__pan-zoom", fitOnInit: "contain", keyboard: zoomable, maxScale: maxScale, minScale: minScale, pan: zoomable, pinch: zoomable, readOnly: !zoomable, ref: panZoomRef, wheel: zoomable ? wheel : 'disabled', children: _jsx("div", { className: "inkra-image-viewer__image-transform", style: { transform: `rotate(${rotation}deg)` }, children: (renderImage ?? renderDefaultImage)(activeItem, imageProps) }) }, activeItem.id), !failed && (zoomable || rotatable) ? _jsxs("div", { className: "inkra-image-viewer__tools", role: "group", "aria-label": labels.panZoom(activeItem), "data-pan-zoom-no-drag": true, children: [zoomable ? _jsxs(_Fragment, { children: [_jsx(Button, { "aria-label": labels.zoomOut, onClick: () => void panZoomRef.current?.zoomOut(), size: "icon-sm", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: zoomOutPaths, size: 24, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels.resetZoom, onClick: () => void panZoomRef.current?.reset(), size: "sm", variant: "outline", children: "100%" }), _jsx(Button, { "aria-label": labels.zoomIn, onClick: () => void panZoomRef.current?.zoomIn(), size: "icon-sm", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: zoomInPaths, size: 24, sourceSize: 24 }) })] }) : null, rotatable ? _jsx(Button, { "aria-label": labels.rotate, onClick: () => {
188
+ setRotation(current => (current + 90) % 360);
189
+ requestAnimationFrame(() => void panZoomRef.current?.fit({ mode: 'contain' }));
190
+ }, size: "icon-sm", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: rotatePaths, size: 24, sourceSize: 24 }) }) : null] }) : null, total > 1 ? _jsxs("div", { className: "inkra-image-viewer__navigation", children: [_jsx(Button, { "aria-label": labels.previous, className: "inkra-image-viewer__previous", disabled: !loop && atStart, onClick: () => step(-1), size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: chevronLeftPaths, size: 30, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels.next, className: "inkra-image-viewer__next", disabled: !loop && atEnd, onClick: () => step(1), size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: chevronRightPaths, size: 30, sourceSize: 24 }) })] }) : null] }) }) : _jsx("div", { className: "inkra-image-viewer__state", ref: stageRef, tabIndex: 0, children: emptyState ?? _jsxs(_Fragment, { children: [_jsx(PencilIcon, { decorative: true, paths: imageOffPaths, size: 48, sourceSize: 24 }), _jsx(PencilText, { as: "h3", children: labels.emptyTitle }), _jsx(PencilText, { as: "p", children: labels.emptyDescription })] }) }) }), showThumbnails && total > 1 ? _jsx("div", { "aria-label": labels.thumbnails, className: "inkra-image-viewer__thumbnails", role: "listbox", children: items.map((item, index) => {
191
+ const selected = index === activeIndex;
192
+ return _jsxs("button", { "aria-label": labels.thumbnail(item, index + 1, total), "aria-selected": selected, className: "inkra-image-viewer__thumbnail", onClick: () => selectIndex(index, 'thumbnail'), onKeyDown: (event) => {
193
+ const rtl = directionFor(event.currentTarget) === 'rtl';
194
+ let nextIndex = index;
195
+ if (event.key === 'ArrowLeft')
196
+ nextIndex = nextImageViewerIndex(total, index, rtl ? 1 : -1, loop);
197
+ else if (event.key === 'ArrowRight')
198
+ nextIndex = nextImageViewerIndex(total, index, rtl ? -1 : 1, loop);
199
+ else if (event.key === 'Home')
200
+ nextIndex = 0;
201
+ else if (event.key === 'End')
202
+ nextIndex = total - 1;
203
+ else
204
+ return;
205
+ event.preventDefault();
206
+ selectThumbnail(nextIndex, event.key === 'Home' ? 'home' : event.key === 'End' ? 'end' : 'thumbnail');
207
+ }, ref: (node) => {
208
+ if (node)
209
+ thumbnailRefs.current.set(item.id, node);
210
+ else
211
+ thumbnailRefs.current.delete(item.id);
212
+ }, role: "option", tabIndex: selected ? 0 : -1, type: "button", children: [_jsx(PencilSurface, { as: "span", className: "inkra-image-viewer__thumbnail-surface", radius: 6, variant: selected ? 'selection' : 'outline', children: _jsx("img", { alt: "", className: "inkra-image-viewer__thumbnail-image", draggable: false, src: item.thumbnailSrc ?? item.src }) }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-image-viewer__thumbnail-focus", radius: 6, variant: "outline" })] }, item.id);
213
+ }) }) : null] })] }));
214
+ });
215
+ export const ImageViewer = Object.freeze({
216
+ Root: ImageViewerRoot,
217
+ Trigger: ImageViewerTrigger,
218
+ Content: ImageViewerContent,
219
+ });
220
+ export { nextImageViewerIndex, resolveImageViewerSelection } from './model.js';
@@ -0,0 +1,9 @@
1
+ export interface ImageViewerIdentifiable {
2
+ id: string;
3
+ }
4
+ export interface ImageViewerSelection {
5
+ activeId: string | null;
6
+ index: number;
7
+ }
8
+ export declare function resolveImageViewerSelection(items: readonly ImageViewerIdentifiable[], requestedId: string | null | undefined, previousIndex?: number): ImageViewerSelection;
9
+ export declare function nextImageViewerIndex(length: number, index: number, direction: -1 | 1, loop: boolean): number;
@@ -0,0 +1,18 @@
1
+ export function resolveImageViewerSelection(items, requestedId, previousIndex = 0) {
2
+ if (items.length === 0)
3
+ return { activeId: null, index: -1 };
4
+ const requestedIndex = requestedId == null ? -1 : items.findIndex(item => item.id === requestedId);
5
+ const index = requestedIndex >= 0
6
+ ? requestedIndex
7
+ : Math.min(Math.max(0, previousIndex), items.length - 1);
8
+ return { activeId: items[index].id, index };
9
+ }
10
+ export function nextImageViewerIndex(length, index, direction, loop) {
11
+ if (length <= 0)
12
+ return -1;
13
+ const current = Math.min(Math.max(0, index), length - 1);
14
+ const next = current + direction;
15
+ if (loop)
16
+ return (next + length) % length;
17
+ return Math.min(Math.max(0, next), length - 1);
18
+ }
package/dist/index.d.ts CHANGED
@@ -15,6 +15,53 @@ export * from './dropdown-menu/index.js';
15
15
  export * from './calendar/index.js';
16
16
  export * from './day-agenda/index.js';
17
17
  export * from './journey-map/index.js';
18
+ export * from './toast/index.js';
19
+ export * from './empty/index.js';
20
+ export * from './toggle-group/index.js';
21
+ export * from './alert-dialog/index.js';
22
+ export * from './drawer/index.js';
23
+ export * from './date-picker/index.js';
24
+ export * from './time-picker/index.js';
25
+ export * from './pagination/index.js';
26
+ export * from './command/index.js';
27
+ export * from './data-table/index.js';
28
+ export * from './tree/index.js';
29
+ export * from './upload/index.js';
30
+ export * from './steps/index.js';
31
+ export * from './timeline/index.js';
32
+ export * from './layout/index.js';
33
+ export * from './slot/index.js';
34
+ export * from './log/index.js';
35
+ export * from './hotkey/index.js';
36
+ export * from './number-ticker/index.js';
37
+ export * from './audio-player/index.js';
38
+ export * from './carousel/index.js';
39
+ export * from './choice-grid/index.js';
40
+ export * from './color-picker/index.js';
41
+ export * from './context-menu/index.js';
42
+ export * from './dockable-panel/index.js';
43
+ export * from './editable/index.js';
44
+ export * from './flip/index.js';
45
+ export * from './heatmap/index.js';
46
+ export * from './hover-card/index.js';
47
+ export * from './image-cropper/index.js';
48
+ export * from './image-viewer/index.js';
49
+ export * from './keypad/index.js';
50
+ export * from './node-graph/index.js';
51
+ export * from './overflow-list/index.js';
52
+ export * from './pan-zoom/index.js';
53
+ export * from './radar/index.js';
54
+ export * from './range-slider/index.js';
55
+ export * from './rating/index.js';
56
+ export * from './resizable/index.js';
57
+ export * from './scroll-area/index.js';
58
+ export * from './sortable/index.js';
59
+ export * from './sparkline/index.js';
60
+ export * from './tag-input/index.js';
61
+ export * from './transfer/index.js';
62
+ export * from './tree-select/index.js';
63
+ export * from './typewriter/index.js';
64
+ export * from './virtual-list/index.js';
18
65
  export type { ButtonGroupOrientation, ButtonAlign, ButtonLoadingPosition, ButtonGroupProps, ButtonLinkProps, ButtonProps, ButtonSize, ButtonVariant, } from './Button.js';
19
66
  export { PencilLine } from './PencilLine.js';
20
67
  export type { PencilLineOrientation, PencilLineProps } from './PencilLine.js';
package/dist/index.js CHANGED
@@ -15,6 +15,53 @@ export * from './dropdown-menu/index.js';
15
15
  export * from './calendar/index.js';
16
16
  export * from './day-agenda/index.js';
17
17
  export * from './journey-map/index.js';
18
+ export * from './toast/index.js';
19
+ export * from './empty/index.js';
20
+ export * from './toggle-group/index.js';
21
+ export * from './alert-dialog/index.js';
22
+ export * from './drawer/index.js';
23
+ export * from './date-picker/index.js';
24
+ export * from './time-picker/index.js';
25
+ export * from './pagination/index.js';
26
+ export * from './command/index.js';
27
+ export * from './data-table/index.js';
28
+ export * from './tree/index.js';
29
+ export * from './upload/index.js';
30
+ export * from './steps/index.js';
31
+ export * from './timeline/index.js';
32
+ export * from './layout/index.js';
33
+ export * from './slot/index.js';
34
+ export * from './log/index.js';
35
+ export * from './hotkey/index.js';
36
+ export * from './number-ticker/index.js';
37
+ export * from './audio-player/index.js';
38
+ export * from './carousel/index.js';
39
+ export * from './choice-grid/index.js';
40
+ export * from './color-picker/index.js';
41
+ export * from './context-menu/index.js';
42
+ export * from './dockable-panel/index.js';
43
+ export * from './editable/index.js';
44
+ export * from './flip/index.js';
45
+ export * from './heatmap/index.js';
46
+ export * from './hover-card/index.js';
47
+ export * from './image-cropper/index.js';
48
+ export * from './image-viewer/index.js';
49
+ export * from './keypad/index.js';
50
+ export * from './node-graph/index.js';
51
+ export * from './overflow-list/index.js';
52
+ export * from './pan-zoom/index.js';
53
+ export * from './radar/index.js';
54
+ export * from './range-slider/index.js';
55
+ export * from './rating/index.js';
56
+ export * from './resizable/index.js';
57
+ export * from './scroll-area/index.js';
58
+ export * from './sortable/index.js';
59
+ export * from './sparkline/index.js';
60
+ export * from './tag-input/index.js';
61
+ export * from './transfer/index.js';
62
+ export * from './tree-select/index.js';
63
+ export * from './typewriter/index.js';
64
+ export * from './virtual-list/index.js';
18
65
  export { PencilLine } from './PencilLine.js';
19
66
  export { PencilSurface } from './PencilSurface.js';
20
67
  export { PencilText } from './PencilText.js';
@@ -1,4 +1,4 @@
1
- export declare const PENCIL_RENDERER_VERSION = "inkra-pencil-v4";
1
+ export declare const PENCIL_RENDERER_VERSION = "inkra-pencil-v5";
2
2
  export declare class BoundedCache<Value> {
3
3
  private readonly maxEntries;
4
4
  private readonly maxCost;
@@ -1,4 +1,4 @@
1
- export const PENCIL_RENDERER_VERSION = 'inkra-pencil-v4';
1
+ export const PENCIL_RENDERER_VERSION = 'inkra-pencil-v5';
2
2
  export class BoundedCache {
3
3
  constructor(maxEntries, maxCost) {
4
4
  this.maxEntries = maxEntries;
@@ -0,0 +1,10 @@
1
+ export declare const pencilRenderDefaults: Readonly<{
2
+ strokeWidth: 2;
3
+ strokeRoughness: 1.5;
4
+ washStrength: 0.8;
5
+ washSpacing: 2;
6
+ washRoughness: 3;
7
+ washAngle: -45;
8
+ }>;
9
+ export declare const maxStrokeRoughness = 4;
10
+ export declare function readRenderToken(style: Pick<CSSStyleDeclaration, 'getPropertyValue'>, name: string, fallback: number, min: number, max: number): number;
@@ -0,0 +1,13 @@
1
+ export const pencilRenderDefaults = Object.freeze({
2
+ strokeWidth: 2,
3
+ strokeRoughness: 1.5,
4
+ washStrength: 0.8,
5
+ washSpacing: 2,
6
+ washRoughness: 3,
7
+ washAngle: -45,
8
+ });
9
+ export const maxStrokeRoughness = 4;
10
+ export function readRenderToken(style, name, fallback, min, max) {
11
+ const value = Number.parseFloat(style.getPropertyValue(name));
12
+ return Number.isFinite(value) ? Math.max(min, Math.min(max, value)) : fallback;
13
+ }
@@ -4,6 +4,14 @@ export interface PencilSurfaceTheme {
4
4
  dark: boolean;
5
5
  ink: string;
6
6
  wash: string;
7
+ strokeWidth?: number;
8
+ strokeRoughness?: number;
9
+ washSettings?: {
10
+ strength: number;
11
+ spacing: number;
12
+ roughness: number;
13
+ angle: number;
14
+ };
7
15
  }
8
16
  export declare const PENCIL_ACTION_WASH: Readonly<{
9
17
  lightStrength: 0.2;
@@ -14,9 +22,9 @@ export declare const PENCIL_QUIET_WASH: Readonly<{
14
22
  darkStrength: 0.08;
15
23
  }>;
16
24
  export declare const PENCIL_FILL_WASH: Readonly<{
17
- angle: -35;
18
- spacing: 1.8;
19
- roughness: 3.5;
25
+ angle: -45;
26
+ spacing: 2;
27
+ roughness: 3;
20
28
  inset: 2;
21
29
  edgeReach: 0;
22
30
  }>;
@@ -24,9 +32,9 @@ export declare const PENCIL_SELECTION_WASH: Readonly<{
24
32
  lightStrength: 0.2;
25
33
  darkStrength: 0.12;
26
34
  radius: 8;
27
- angle: -35;
28
- spacing: 1.8;
29
- roughness: 3.5;
35
+ angle: -45;
36
+ spacing: 2;
37
+ roughness: 3;
30
38
  inset: 2;
31
39
  edgeReach: 0;
32
40
  }>;
@@ -1,6 +1,7 @@
1
1
  import { pencilPaper } from './paper.js';
2
2
  import { pencilStroke } from './stroke.js';
3
3
  import { drawPencilWash } from './wash.js';
4
+ import { pencilRenderDefaults } from './renderTokens.js';
4
5
  export const PENCIL_ACTION_WASH = Object.freeze({
5
6
  lightStrength: 0.2,
6
7
  darkStrength: 0.12,
@@ -28,9 +29,9 @@ const surfaceStyles = {
28
29
  danger: { radius: 7, width: 0.75, passes: 2, strength: 0.18 },
29
30
  };
30
31
  export const PENCIL_FILL_WASH = Object.freeze({
31
- angle: -35,
32
- spacing: 1.8,
33
- roughness: 3.5,
32
+ angle: pencilRenderDefaults.washAngle,
33
+ spacing: pencilRenderDefaults.washSpacing,
34
+ roughness: pencilRenderDefaults.washRoughness,
34
35
  inset: 2,
35
36
  edgeReach: 0,
36
37
  });
@@ -85,9 +86,10 @@ export function drawPencilSurface(canvas, borderCanvas, width, height, variant,
85
86
  }
86
87
  context.translate(bleed, bleed);
87
88
  drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
88
- strength: 1,
89
- spacing: 1.8,
90
- roughness: 3.5,
89
+ strength: theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48,
90
+ spacing: theme.washSettings?.spacing ?? pencilRenderDefaults.washSpacing,
91
+ roughness: theme.washSettings?.roughness ?? pencilRenderDefaults.washRoughness,
92
+ angle: (theme.washSettings?.angle ?? pencilRenderDefaults.washAngle) + 35,
91
93
  inset: 2,
92
94
  radius: 8,
93
95
  edgeReach: 8,
@@ -101,13 +103,13 @@ export function drawPencilSurface(canvas, borderCanvas, width, height, variant,
101
103
  borderCanvas.height = canvas.height;
102
104
  }
103
105
  drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
104
- strength: theme.dark ? PENCIL_SELECTION_WASH.darkStrength : PENCIL_SELECTION_WASH.lightStrength,
105
- spacing: PENCIL_SELECTION_WASH.spacing,
106
- roughness: PENCIL_SELECTION_WASH.roughness,
106
+ strength: (theme.dark ? PENCIL_SELECTION_WASH.darkStrength : PENCIL_SELECTION_WASH.lightStrength) * (theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48),
107
+ spacing: theme.washSettings?.spacing ?? PENCIL_SELECTION_WASH.spacing,
108
+ roughness: theme.washSettings?.roughness ?? PENCIL_SELECTION_WASH.roughness,
107
109
  inset: PENCIL_SELECTION_WASH.inset,
108
110
  radius: PENCIL_SELECTION_WASH.radius,
109
111
  edgeReach: PENCIL_SELECTION_WASH.edgeReach,
110
- angle: PENCIL_SELECTION_WASH.angle,
112
+ angle: theme.washSettings?.angle ?? PENCIL_SELECTION_WASH.angle,
111
113
  dpr,
112
114
  });
113
115
  return undefined;
@@ -147,7 +149,8 @@ export function drawPencilSurface(canvas, borderCanvas, width, height, variant,
147
149
  if (strength) {
148
150
  drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
149
151
  ...PENCIL_FILL_WASH,
150
- strength,
152
+ ...theme.washSettings,
153
+ strength: strength * (theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48),
151
154
  radius: resolvedRadius,
152
155
  dpr,
153
156
  });
@@ -164,7 +167,7 @@ export function drawPencilSurface(canvas, borderCanvas, width, height, variant,
164
167
  borderContext = separateContext;
165
168
  }
166
169
  }
167
- pencilStroke(borderContext, points, theme.ink, style.width, seed, 0.6, style.passes, true, {
170
+ pencilStroke(borderContext, points, theme.ink, style.width * ((theme.strokeWidth ?? pencilRenderDefaults.strokeWidth) / 1.2), seed, theme.strokeRoughness ?? pencilRenderDefaults.strokeRoughness, style.passes, true, {
168
171
  variation: 0.82,
169
172
  breaks: variant === 'paper' ? 0.2 : 0.25,
170
173
  grain: 0.7,
@@ -24,4 +24,5 @@ export declare function renderPencilText(text: string, options: {
24
24
  dpr: number;
25
25
  settings?: PencilLetteringSettings;
26
26
  cache?: boolean;
27
+ glyphSeed?: number;
27
28
  }): PencilTextSprite;