@inkra/react 0.0.0-stage → 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 (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +316 -2
  3. package/THIRD_PARTY_NOTICES.md +39 -0
  4. package/Yozai-OFL.txt +94 -0
  5. package/dist/Button.d.ts +36 -0
  6. package/dist/Button.js +89 -0
  7. package/dist/PencilIcon.d.ts +22 -0
  8. package/dist/PencilIcon.js +210 -0
  9. package/dist/PencilLine.d.ts +10 -0
  10. package/dist/PencilLine.js +91 -0
  11. package/dist/PencilSurface.d.ts +19 -0
  12. package/dist/PencilSurface.js +19 -0
  13. package/dist/PencilText.d.ts +16 -0
  14. package/dist/PencilText.js +338 -0
  15. package/dist/accordion/Accordion.d.ts +27 -0
  16. package/dist/accordion/Accordion.js +40 -0
  17. package/dist/accordion/index.d.ts +2 -0
  18. package/dist/accordion/index.js +1 -0
  19. package/dist/alert-dialog/index.d.ts +64 -0
  20. package/dist/alert-dialog/index.js +109 -0
  21. package/dist/assets/ui-hand-yozai.woff2 +0 -0
  22. package/dist/audio-player/index.d.ts +60 -0
  23. package/dist/audio-player/index.js +101 -0
  24. package/dist/breadcrumb/index.d.ts +16 -0
  25. package/dist/breadcrumb/index.js +25 -0
  26. package/dist/calendar/index.d.ts +21 -0
  27. package/dist/calendar/index.js +41 -0
  28. package/dist/carousel/index.d.ts +51 -0
  29. package/dist/carousel/index.js +290 -0
  30. package/dist/choice-grid/index.d.ts +38 -0
  31. package/dist/choice-grid/index.js +85 -0
  32. package/dist/color-picker/color.d.ts +15 -0
  33. package/dist/color-picker/color.js +55 -0
  34. package/dist/color-picker/index.d.ts +31 -0
  35. package/dist/color-picker/index.js +69 -0
  36. package/dist/command/index.d.ts +23 -0
  37. package/dist/command/index.js +39 -0
  38. package/dist/content/index.d.ts +106 -0
  39. package/dist/content/index.js +80 -0
  40. package/dist/context-menu/index.d.ts +76 -0
  41. package/dist/context-menu/index.js +240 -0
  42. package/dist/data-table/index.d.ts +47 -0
  43. package/dist/data-table/index.js +91 -0
  44. package/dist/date-picker/index.d.ts +50 -0
  45. package/dist/date-picker/index.js +180 -0
  46. package/dist/day-agenda/index.d.ts +43 -0
  47. package/dist/day-agenda/index.js +47 -0
  48. package/dist/day-agenda/layout.d.ts +20 -0
  49. package/dist/day-agenda/layout.js +60 -0
  50. package/dist/display/ProgressRing.d.ts +6 -0
  51. package/dist/display/ProgressRing.js +146 -0
  52. package/dist/display/index.d.ts +59 -0
  53. package/dist/display/index.js +75 -0
  54. package/dist/dockable-panel/index.d.ts +41 -0
  55. package/dist/dockable-panel/index.js +253 -0
  56. package/dist/dockable-panel/model.d.ts +21 -0
  57. package/dist/dockable-panel/model.js +61 -0
  58. package/dist/drawer/index.d.ts +63 -0
  59. package/dist/drawer/index.js +86 -0
  60. package/dist/dropdown-menu/index.d.ts +34 -0
  61. package/dist/dropdown-menu/index.js +40 -0
  62. package/dist/editable/index.d.ts +53 -0
  63. package/dist/editable/index.js +162 -0
  64. package/dist/empty/index.d.ts +21 -0
  65. package/dist/empty/index.js +23 -0
  66. package/dist/flip/index.d.ts +49 -0
  67. package/dist/flip/index.js +138 -0
  68. package/dist/form/EditablePencilText.d.ts +13 -0
  69. package/dist/form/EditablePencilText.js +323 -0
  70. package/dist/form/Field.d.ts +13 -0
  71. package/dist/form/Field.js +26 -0
  72. package/dist/form/Input.d.ts +27 -0
  73. package/dist/form/Input.js +97 -0
  74. package/dist/form/InputAction.d.ts +7 -0
  75. package/dist/form/InputAction.js +24 -0
  76. package/dist/form/Textarea.d.ts +17 -0
  77. package/dist/form/Textarea.js +102 -0
  78. package/dist/form/context.d.ts +12 -0
  79. package/dist/form/context.js +10 -0
  80. package/dist/form/controlProps.d.ts +23 -0
  81. package/dist/form/controlProps.js +20 -0
  82. package/dist/form/index.d.ts +7 -0
  83. package/dist/form/index.js +3 -0
  84. package/dist/form/types.d.ts +6 -0
  85. package/dist/form/types.js +1 -0
  86. package/dist/heatmap/index.d.ts +59 -0
  87. package/dist/heatmap/index.js +248 -0
  88. package/dist/hotkey/index.d.ts +37 -0
  89. package/dist/hotkey/index.js +130 -0
  90. package/dist/hotkey/model.d.ts +27 -0
  91. package/dist/hotkey/model.js +97 -0
  92. package/dist/hover-card/index.d.ts +33 -0
  93. package/dist/hover-card/index.js +141 -0
  94. package/dist/image-cropper/index.d.ts +82 -0
  95. package/dist/image-cropper/index.js +261 -0
  96. package/dist/image-cropper/model.d.ts +42 -0
  97. package/dist/image-cropper/model.js +94 -0
  98. package/dist/image-viewer/index.d.ts +79 -0
  99. package/dist/image-viewer/index.js +220 -0
  100. package/dist/image-viewer/model.d.ts +9 -0
  101. package/dist/image-viewer/model.js +18 -0
  102. package/dist/images/layers-2x.png +0 -0
  103. package/dist/images/layers.png +0 -0
  104. package/dist/images/marker-icon.png +0 -0
  105. package/dist/index.d.ts +75 -0
  106. package/dist/index.js +69 -0
  107. package/dist/input-number/index.d.ts +16 -0
  108. package/dist/input-number/index.js +77 -0
  109. package/dist/internal/InputActionContext.d.ts +1 -0
  110. package/dist/internal/InputActionContext.js +3 -0
  111. package/dist/internal/cache.d.ts +14 -0
  112. package/dist/internal/cache.js +54 -0
  113. package/dist/internal/line.d.ts +4 -0
  114. package/dist/internal/line.js +21 -0
  115. package/dist/internal/paper.d.ts +1 -0
  116. package/dist/internal/paper.js +65 -0
  117. package/dist/internal/random.d.ts +3 -0
  118. package/dist/internal/random.js +30 -0
  119. package/dist/internal/recovery.d.ts +1 -0
  120. package/dist/internal/recovery.js +52 -0
  121. package/dist/internal/renderTokens.d.ts +10 -0
  122. package/dist/internal/renderTokens.js +13 -0
  123. package/dist/internal/stroke.d.ts +24 -0
  124. package/dist/internal/stroke.js +144 -0
  125. package/dist/internal/surfaceRenderer.d.ts +41 -0
  126. package/dist/internal/surfaceRenderer.js +178 -0
  127. package/dist/internal/textQueue.d.ts +1 -0
  128. package/dist/internal/textQueue.js +37 -0
  129. package/dist/internal/textRenderer.d.ts +28 -0
  130. package/dist/internal/textRenderer.js +239 -0
  131. package/dist/internal/travelIcons.d.ts +24 -0
  132. package/dist/internal/travelIcons.js +14 -0
  133. package/dist/internal/useBrowserLayoutEffect.d.ts +2 -0
  134. package/dist/internal/useBrowserLayoutEffect.js +3 -0
  135. package/dist/internal/usePencilSurface.d.ts +3 -0
  136. package/dist/internal/usePencilSurface.js +116 -0
  137. package/dist/internal/useSelectionDrawing.d.ts +2 -0
  138. package/dist/internal/useSelectionDrawing.js +90 -0
  139. package/dist/internal/wash.d.ts +16 -0
  140. package/dist/internal/wash.js +149 -0
  141. package/dist/journey-map/MapScale.d.ts +4 -0
  142. package/dist/journey-map/MapScale.js +54 -0
  143. package/dist/journey-map/index.d.ts +39 -0
  144. package/dist/journey-map/index.js +197 -0
  145. package/dist/journey-map/terrain.d.ts +8 -0
  146. package/dist/journey-map/terrain.js +127 -0
  147. package/dist/keypad/index.d.ts +23 -0
  148. package/dist/keypad/index.js +63 -0
  149. package/dist/keypad/model.d.ts +20 -0
  150. package/dist/keypad/model.js +33 -0
  151. package/dist/layout/index.d.ts +63 -0
  152. package/dist/layout/index.js +62 -0
  153. package/dist/log/index.d.ts +45 -0
  154. package/dist/log/index.js +159 -0
  155. package/dist/log/model.d.ts +16 -0
  156. package/dist/log/model.js +13 -0
  157. package/dist/node-graph/index.d.ts +123 -0
  158. package/dist/node-graph/index.js +304 -0
  159. package/dist/number-ticker/index.d.ts +29 -0
  160. package/dist/number-ticker/index.js +132 -0
  161. package/dist/number-ticker/model.d.ts +8 -0
  162. package/dist/number-ticker/model.js +34 -0
  163. package/dist/overflow-list/index.d.ts +39 -0
  164. package/dist/overflow-list/index.js +230 -0
  165. package/dist/overflow-list/layout.d.ts +19 -0
  166. package/dist/overflow-list/layout.js +44 -0
  167. package/dist/overlay/Overlay.d.ts +65 -0
  168. package/dist/overlay/Overlay.js +77 -0
  169. package/dist/overlay/Popover.d.ts +43 -0
  170. package/dist/overlay/Popover.js +45 -0
  171. package/dist/overlay/index.d.ts +4 -0
  172. package/dist/overlay/index.js +2 -0
  173. package/dist/overlay/themeBridge.d.ts +8 -0
  174. package/dist/overlay/themeBridge.js +98 -0
  175. package/dist/pagination/index.d.ts +23 -0
  176. package/dist/pagination/index.js +41 -0
  177. package/dist/pagination/model.d.ts +4 -0
  178. package/dist/pagination/model.js +42 -0
  179. package/dist/pan-zoom/index.d.ts +71 -0
  180. package/dist/pan-zoom/index.js +175 -0
  181. package/dist/pencil.d.ts +10 -0
  182. package/dist/pencil.js +39 -0
  183. package/dist/radar/index.d.ts +54 -0
  184. package/dist/radar/index.js +220 -0
  185. package/dist/range-slider/index.d.ts +16 -0
  186. package/dist/range-slider/index.js +22 -0
  187. package/dist/rating/index.d.ts +28 -0
  188. package/dist/rating/index.js +127 -0
  189. package/dist/resizable/index.d.ts +32 -0
  190. package/dist/resizable/index.js +50 -0
  191. package/dist/scroll-area/index.d.ts +36 -0
  192. package/dist/scroll-area/index.js +44 -0
  193. package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
  194. package/dist/segmented-progress/SegmentedProgress.js +42 -0
  195. package/dist/select/MultipleSelect.d.ts +44 -0
  196. package/dist/select/MultipleSelect.js +124 -0
  197. package/dist/select/SearchableSelect.d.ts +45 -0
  198. package/dist/select/SearchableSelect.js +50 -0
  199. package/dist/select/Select.d.ts +109 -0
  200. package/dist/select/Select.js +205 -0
  201. package/dist/select/index.d.ts +4 -0
  202. package/dist/select/index.js +2 -0
  203. package/dist/selection/Checkbox.d.ts +9 -0
  204. package/dist/selection/Checkbox.js +37 -0
  205. package/dist/selection/RadioGroup.d.ts +23 -0
  206. package/dist/selection/RadioGroup.js +82 -0
  207. package/dist/selection/Switch.d.ts +8 -0
  208. package/dist/selection/Switch.js +36 -0
  209. package/dist/selection/index.d.ts +6 -0
  210. package/dist/selection/index.js +3 -0
  211. package/dist/selection/useNativeChecked.d.ts +13 -0
  212. package/dist/selection/useNativeChecked.js +27 -0
  213. package/dist/slider/index.d.ts +11 -0
  214. package/dist/slider/index.js +45 -0
  215. package/dist/slot/index.d.ts +24 -0
  216. package/dist/slot/index.js +29 -0
  217. package/dist/sortable/index.d.ts +50 -0
  218. package/dist/sortable/index.js +278 -0
  219. package/dist/sortable/model.d.ts +30 -0
  220. package/dist/sortable/model.js +64 -0
  221. package/dist/sparkline/index.d.ts +19 -0
  222. package/dist/sparkline/index.js +240 -0
  223. package/dist/sparkline/model.d.ts +16 -0
  224. package/dist/sparkline/model.js +28 -0
  225. package/dist/steps/index.d.ts +16 -0
  226. package/dist/steps/index.js +37 -0
  227. package/dist/styles.css +8226 -0
  228. package/dist/tabs/index.d.ts +22 -0
  229. package/dist/tabs/index.js +42 -0
  230. package/dist/tag-input/index.d.ts +69 -0
  231. package/dist/tag-input/index.js +325 -0
  232. package/dist/tag-input/model.d.ts +14 -0
  233. package/dist/tag-input/model.js +38 -0
  234. package/dist/time-picker/index.d.ts +15 -0
  235. package/dist/time-picker/index.js +168 -0
  236. package/dist/timeline/index.d.ts +14 -0
  237. package/dist/timeline/index.js +16 -0
  238. package/dist/toast/index.d.ts +27 -0
  239. package/dist/toast/index.js +36 -0
  240. package/dist/toggle-group/index.d.ts +17 -0
  241. package/dist/toggle-group/index.js +26 -0
  242. package/dist/tokens.css +356 -0
  243. package/dist/tooltip/Tooltip.d.ts +24 -0
  244. package/dist/tooltip/Tooltip.js +32 -0
  245. package/dist/tooltip/index.d.ts +2 -0
  246. package/dist/tooltip/index.js +1 -0
  247. package/dist/transfer/index.d.ts +64 -0
  248. package/dist/transfer/index.js +174 -0
  249. package/dist/transfer/model.d.ts +12 -0
  250. package/dist/transfer/model.js +33 -0
  251. package/dist/tree/index.d.ts +39 -0
  252. package/dist/tree/index.js +36 -0
  253. package/dist/tree-select/index.d.ts +59 -0
  254. package/dist/tree-select/index.js +142 -0
  255. package/dist/tree-select/model.d.ts +31 -0
  256. package/dist/tree-select/model.js +135 -0
  257. package/dist/typewriter/index.d.ts +21 -0
  258. package/dist/typewriter/index.js +101 -0
  259. package/dist/typewriter/model.d.ts +7 -0
  260. package/dist/typewriter/model.js +45 -0
  261. package/dist/upload/index.d.ts +59 -0
  262. package/dist/upload/index.js +230 -0
  263. package/dist/virtual-list/index.d.ts +44 -0
  264. package/dist/virtual-list/index.js +128 -0
  265. package/dist/virtual-list/model.d.ts +7 -0
  266. package/dist/virtual-list/model.js +27 -0
  267. package/package.json +119 -4
@@ -0,0 +1,98 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, forwardRef, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
3
+ const emptySnapshot = {
4
+ direction: 'ltr',
5
+ style: {},
6
+ };
7
+ const PortalThemeContext = createContext(null);
8
+ const PortalLayerDepthContext = createContext(-1);
9
+ function assignRef(ref, value) {
10
+ if (typeof ref === 'function') {
11
+ ref(value);
12
+ return;
13
+ }
14
+ if (ref)
15
+ ref.current = value;
16
+ }
17
+ function closestAttribute(node, attribute) {
18
+ return node.closest(`[${attribute}]`)?.getAttribute(attribute) ?? undefined;
19
+ }
20
+ function isForcedColorsActive() {
21
+ return typeof window !== 'undefined'
22
+ && typeof window.matchMedia === 'function'
23
+ && window.matchMedia('(forced-colors: active)').matches;
24
+ }
25
+ function readThemeSnapshot(node, forcedColors = isForcedColorsActive()) {
26
+ if (!node || typeof window === 'undefined')
27
+ return emptySnapshot;
28
+ const computed = window.getComputedStyle(node);
29
+ const style = {};
30
+ if (!forcedColors) {
31
+ for (let index = 0; index < computed.length; index += 1) {
32
+ const property = computed.item(index);
33
+ if (property.startsWith('--inkra-')) {
34
+ style[property] = computed.getPropertyValue(property).trim();
35
+ }
36
+ }
37
+ }
38
+ style.colorScheme = computed.colorScheme;
39
+ style.fontFamily = computed.fontFamily;
40
+ return {
41
+ theme: closestAttribute(node, 'data-theme'),
42
+ inkraTheme: closestAttribute(node, 'data-inkra-theme'),
43
+ direction: computed.direction === 'rtl' ? 'rtl' : 'ltr',
44
+ language: node.closest('[lang]')?.lang || undefined,
45
+ style,
46
+ };
47
+ }
48
+ export function PortalThemeProvider({ children }) {
49
+ const parentDepth = useContext(PortalLayerDepthContext);
50
+ const depth = parentDepth + 1;
51
+ const [trigger, setTrigger] = useState(null);
52
+ const [snapshot, setSnapshot] = useState(emptySnapshot);
53
+ const captureTrigger = useCallback((node) => {
54
+ setTrigger(node);
55
+ setSnapshot(readThemeSnapshot(node));
56
+ }, []);
57
+ useEffect(() => {
58
+ if (!trigger || typeof MutationObserver === 'undefined')
59
+ return undefined;
60
+ const refresh = () => setSnapshot(readThemeSnapshot(trigger));
61
+ const observer = new MutationObserver(refresh);
62
+ for (let target = trigger; target; target = target.parentElement) {
63
+ observer.observe(target, {
64
+ attributes: true,
65
+ attributeFilter: ['class', 'data-theme', 'data-inkra-theme', 'dir', 'lang', 'style'],
66
+ });
67
+ }
68
+ const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
69
+ const forcedColors = window.matchMedia('(forced-colors: active)');
70
+ colorScheme.addEventListener('change', refresh);
71
+ forcedColors.addEventListener('change', refresh);
72
+ refresh();
73
+ return () => {
74
+ observer.disconnect();
75
+ colorScheme.removeEventListener('change', refresh);
76
+ forcedColors.removeEventListener('change', refresh);
77
+ };
78
+ }, [trigger]);
79
+ const value = useMemo(() => ({ captureTrigger, depth, snapshot }), [captureTrigger, depth, snapshot]);
80
+ return (_jsx(PortalLayerDepthContext.Provider, { value: depth, children: _jsx(PortalThemeContext.Provider, { value: value, children: children }) }));
81
+ }
82
+ export function usePortalTriggerRef(forwardedRef) {
83
+ const captureTrigger = useContext(PortalThemeContext)?.captureTrigger;
84
+ return useCallback((node) => {
85
+ assignRef(forwardedRef, node);
86
+ captureTrigger?.(node);
87
+ }, [captureTrigger, forwardedRef]);
88
+ }
89
+ export const PortalThemeBridge = forwardRef(function PortalThemeBridge({ children }, ref) {
90
+ const context = useContext(PortalThemeContext);
91
+ const depth = context?.depth ?? 0;
92
+ const snapshot = context?.snapshot ?? emptySnapshot;
93
+ const style = useMemo(() => ({
94
+ ...snapshot.style,
95
+ '--inkra-floating-layer-offset': `${depth * 20}`,
96
+ }), [depth, snapshot.style]);
97
+ return (_jsx("div", { className: "inkra-portal-theme-bridge", ref: ref, "data-inkra-theme": snapshot.inkraTheme, "data-theme": snapshot.theme, dir: snapshot.direction, lang: snapshot.language, style: style, children: children }));
98
+ });
@@ -0,0 +1,23 @@
1
+ import type { HTMLAttributes } from 'react';
2
+ export interface PaginationLabels {
3
+ nav: string;
4
+ first: string;
5
+ previous: string;
6
+ next: string;
7
+ last: string;
8
+ page: (page: number) => string;
9
+ ellipsis: string;
10
+ }
11
+ export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'children' | 'onChange'> {
12
+ totalPages: number;
13
+ page?: number;
14
+ defaultPage?: number;
15
+ onPageChange?: (page: number) => void;
16
+ siblingCount?: number;
17
+ boundaryCount?: number;
18
+ showFirstLast?: boolean;
19
+ disabled?: boolean;
20
+ size?: 'sm' | 'md' | 'lg';
21
+ labels?: Partial<PaginationLabels>;
22
+ }
23
+ export declare const Pagination: import("react").ForwardRefExoticComponent<PaginationProps & import("react").RefAttributes<HTMLElement>>;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useState } from 'react';
3
+ import { Button } from '../Button.js';
4
+ import { PencilIcon } from '../PencilIcon.js';
5
+ import { PencilSurface } from '../PencilSurface.js';
6
+ import { PencilText } from '../PencilText.js';
7
+ import { clampPage, getPaginationEntries, normalizeTotal } from './model.js';
8
+ const defaultLabels = {
9
+ nav: 'Pagination', first: 'First page', previous: 'Previous page', next: 'Next page', last: 'Last page',
10
+ page: page => `Page ${page}`, ellipsis: 'More pages',
11
+ };
12
+ export const Pagination = forwardRef(function Pagination({ totalPages, page, defaultPage = 1, onPageChange, siblingCount = 1, boundaryCount = 1, showFirstLast = false, disabled = false, size = 'md', labels, className, ...props }, ref) {
13
+ const total = normalizeTotal(totalPages);
14
+ const [internalPage, setInternalPage] = useState(() => clampPage(defaultPage, total));
15
+ const current = clampPage(page ?? internalPage, total);
16
+ const text = { ...defaultLabels, ...labels };
17
+ useEffect(() => {
18
+ if (page === undefined)
19
+ setInternalPage(value => clampPage(value, total));
20
+ }, [page, total]);
21
+ function select(next) {
22
+ const target = clampPage(next, total);
23
+ if (disabled || !total || target === current)
24
+ return;
25
+ if (page === undefined)
26
+ setInternalPage(target);
27
+ onPageChange?.(target);
28
+ }
29
+ const arrow = (kind, target, unavailable) => {
30
+ const backwards = kind === 'first' || kind === 'previous';
31
+ const paths = [{ d: backwards ? 'm15 18-6-6 6-6' : 'm9 18 6-6-6-6' }];
32
+ if (kind === 'first')
33
+ paths.push({ d: 'M5 6v12' });
34
+ if (kind === 'last')
35
+ paths.push({ d: 'M19 6v12' });
36
+ return _jsx("li", { children: _jsx(Button, { variant: "ghost", size: size === 'md' ? 'icon' : `icon-${size}`, "aria-label": text[kind], title: text[kind], disabled: disabled || unavailable, onClick: () => select(target), children: _jsx(PencilIcon, { className: "inkra-pagination__arrow", paths: paths, size: 20, sourceSize: 24 }) }) }, kind);
37
+ };
38
+ return _jsx("nav", { "aria-label": text.nav, ...props, ref: ref, className: ['inkra-pagination', className].filter(Boolean).join(' '), "data-empty": !total || undefined, children: _jsxs("ul", { className: "inkra-pagination__list", children: [showFirstLast && arrow('first', 1, current <= 1), arrow('previous', current - 1, current <= 1), getPaginationEntries(total, current, siblingCount, boundaryCount).map(entry => typeof entry === 'number'
39
+ ? _jsx("li", { children: _jsxs(Button, { size: size, variant: "ghost", className: "inkra-pagination__page", "aria-label": text.page(entry), "aria-current": entry === current ? 'page' : undefined, disabled: disabled, onClick: () => select(entry), children: [entry === current && _jsx(PencilSurface, { as: "span", "aria-hidden": "true", variant: "selection", className: "inkra-pagination__selection", seed: `page-${entry}` }), _jsx(PencilText, { children: String(entry) })] }) }, entry)
40
+ : _jsxs("li", { className: "inkra-pagination__ellipsis", children: [_jsx("span", { className: "inkra-pagination__sr-only", children: text.ellipsis }), _jsx(PencilText, { "aria-hidden": "true", children: "..." })] }, entry)), arrow('next', current + 1, !total || current >= total), showFirstLast && arrow('last', total, !total || current >= total)] }) });
41
+ });
@@ -0,0 +1,4 @@
1
+ export type PaginationEntry = number | 'ellipsis-start' | 'ellipsis-end';
2
+ export declare function normalizeTotal(total: number): number;
3
+ export declare function clampPage(page: number, total: number): number;
4
+ export declare function getPaginationEntries(total: number, page: number, siblings?: number, boundaries?: number): PaginationEntry[];
@@ -0,0 +1,42 @@
1
+ export function normalizeTotal(total) {
2
+ return Number.isFinite(total) ? Math.max(0, Math.min(Number.MAX_SAFE_INTEGER, Math.floor(total))) : 0;
3
+ }
4
+ export function clampPage(page, total) {
5
+ if (total === 0)
6
+ return 0;
7
+ return Math.min(total, Math.max(1, Number.isFinite(page) ? Math.floor(page) : 1));
8
+ }
9
+ function windowCount(value) {
10
+ return Number.isFinite(value) ? Math.max(0, Math.min(3, Math.floor(value))) : 1;
11
+ }
12
+ export function getPaginationEntries(total, page, siblings = 1, boundaries = 1) {
13
+ total = normalizeTotal(total);
14
+ if (!total)
15
+ return [];
16
+ page = clampPage(page, total);
17
+ siblings = windowCount(siblings);
18
+ boundaries = windowCount(boundaries);
19
+ const visible = new Set([page]);
20
+ for (let offset = 1; offset <= boundaries; offset += 1) {
21
+ visible.add(offset);
22
+ visible.add(total - offset + 1);
23
+ }
24
+ for (let offset = -siblings; offset <= siblings; offset += 1)
25
+ visible.add(page + offset);
26
+ const pages = [...visible].filter(value => value >= 1 && value <= total).sort((a, b) => a - b);
27
+ const entries = [];
28
+ let previous = 0;
29
+ for (const value of pages) {
30
+ if (previous && value - previous === 2)
31
+ entries.push(previous + 1);
32
+ else if (previous && value - previous > 2)
33
+ entries.push(value <= page ? 'ellipsis-start' : 'ellipsis-end');
34
+ else if (!previous && value > 1)
35
+ entries.push('ellipsis-start');
36
+ entries.push(value);
37
+ previous = value;
38
+ }
39
+ if (previous < total)
40
+ entries.push('ellipsis-end');
41
+ return entries;
42
+ }
@@ -0,0 +1,71 @@
1
+ import type { FitToViewMode, ZoomToElementOptions, ZoomToElementTarget } from 'react-zoom-pan-pinch';
2
+ import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
3
+ export interface PanZoomTransform {
4
+ x: number;
5
+ y: number;
6
+ scale: number;
7
+ }
8
+ export interface PanZoomChangeDetails {
9
+ reason: 'gesture' | 'programmatic';
10
+ }
11
+ export interface PanZoomPoint {
12
+ x: number;
13
+ y: number;
14
+ }
15
+ export interface PanZoomAnimationOptions {
16
+ duration?: number;
17
+ }
18
+ export interface PanZoomFitOptions extends PanZoomAnimationOptions {
19
+ mode?: FitToViewMode;
20
+ minScale?: number;
21
+ maxScale?: number;
22
+ }
23
+ export interface PanZoomZoomToOptions extends PanZoomAnimationOptions {
24
+ anchor?: PanZoomPoint;
25
+ }
26
+ export type PanZoomZoomToElementOptions = ZoomToElementOptions;
27
+ export interface PanZoomRef {
28
+ reset: (options?: PanZoomAnimationOptions) => Promise<void>;
29
+ fit: (options?: PanZoomFitOptions) => Promise<void>;
30
+ setTransform: (transform: PanZoomTransform, options?: PanZoomAnimationOptions) => Promise<void>;
31
+ zoomIn: (step?: number, options?: PanZoomAnimationOptions) => Promise<void>;
32
+ zoomOut: (step?: number, options?: PanZoomAnimationOptions) => Promise<void>;
33
+ zoomTo: (scale: number, options?: PanZoomZoomToOptions) => Promise<void>;
34
+ zoomToElement: (target: ZoomToElementTarget | ZoomToElementTarget[], options?: PanZoomZoomToElementOptions) => Promise<void>;
35
+ panBy: (deltaX: number, deltaY: number, options?: PanZoomAnimationOptions) => Promise<void>;
36
+ clientToContent: (clientX: number, clientY: number) => PanZoomPoint;
37
+ contentToClient: (x: number, y: number) => PanZoomPoint;
38
+ getTransform: () => PanZoomTransform;
39
+ getViewportElement: () => HTMLDivElement | null;
40
+ getContentElement: () => HTMLDivElement | null;
41
+ }
42
+ export type PanZoomWheelMode = 'modifier' | 'always' | 'disabled';
43
+ export interface PanZoomProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'defaultValue' | 'onChange'> {
44
+ children: ReactNode;
45
+ value?: PanZoomTransform;
46
+ defaultValue?: PanZoomTransform;
47
+ onTransformChange?: (transform: PanZoomTransform, details: PanZoomChangeDetails) => void;
48
+ minScale?: number;
49
+ maxScale?: number;
50
+ wheel?: PanZoomWheelMode;
51
+ pinch?: boolean;
52
+ pan?: boolean;
53
+ keyboard?: boolean;
54
+ keyboardPanStep?: number;
55
+ keyboardZoomStep?: number;
56
+ doubleClickZoom?: boolean;
57
+ disabled?: boolean;
58
+ readOnly?: boolean;
59
+ limitToBounds?: boolean;
60
+ centerZoomedOut?: boolean;
61
+ centerOnInit?: boolean;
62
+ fitOnInit?: boolean | FitToViewMode;
63
+ viewportClassName?: string;
64
+ contentClassName?: string;
65
+ viewportStyle?: CSSProperties;
66
+ contentStyle?: CSSProperties;
67
+ viewportProps?: Omit<HTMLAttributes<HTMLDivElement>, 'className' | 'style' | 'children'>;
68
+ contentProps?: Omit<HTMLAttributes<HTMLDivElement>, 'className' | 'style' | 'children'>;
69
+ instructions?: string;
70
+ }
71
+ export declare const PanZoom: import("react").ForwardRefExoticComponent<PanZoomProps & import("react").RefAttributes<PanZoomRef>>;
@@ -0,0 +1,175 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
3
+ import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef, useState, } from 'react';
4
+ import { PencilSurface } from '../PencilSurface.js';
5
+ const defaultTransform = { x: 0, y: 0, scale: 1 };
6
+ const interactionExclusions = [
7
+ 'button',
8
+ 'a',
9
+ 'input',
10
+ 'select',
11
+ 'textarea',
12
+ ];
13
+ function clampScale(value, minScale, maxScale) {
14
+ return Math.min(maxScale, Math.max(minScale, value));
15
+ }
16
+ function normalizedTransform(value, minScale, maxScale) {
17
+ return {
18
+ x: Number.isFinite(value.x) ? value.x : 0,
19
+ y: Number.isFinite(value.y) ? value.y : 0,
20
+ scale: clampScale(Number.isFinite(value.scale) ? value.scale : 1, minScale, maxScale),
21
+ };
22
+ }
23
+ function transformsEqual(left, right) {
24
+ return left.x === right.x && left.y === right.y && left.scale === right.scale;
25
+ }
26
+ function prefersReducedMotion(element) {
27
+ return element?.ownerDocument.defaultView?.matchMedia('(prefers-reduced-motion: reduce)').matches ?? false;
28
+ }
29
+ function PanZoomRoot({ children, value, defaultValue = defaultTransform, onTransformChange, minScale = 0.25, maxScale = 8, wheel = 'modifier', pinch = true, pan = true, keyboard = true, keyboardPanStep = 48, keyboardZoomStep = 0.25, doubleClickZoom = false, disabled = false, readOnly = false, limitToBounds = true, centerZoomedOut = true, centerOnInit = false, fitOnInit = false, className, viewportClassName, contentClassName, style, viewportStyle, contentStyle, viewportProps, contentProps, instructions, 'aria-label': ariaLabel = 'Pan and zoom content', ...props }, forwardedRef) {
30
+ const resolvedMinScale = Math.max(Number.isFinite(minScale) ? minScale : 0.25, 0.0001);
31
+ const resolvedMaxScale = Math.max(Number.isFinite(maxScale) ? maxScale : 8, resolvedMinScale);
32
+ const controlled = value !== undefined;
33
+ const initialTransform = normalizedTransform(value ?? defaultValue, resolvedMinScale, resolvedMaxScale);
34
+ const [uncontrolledTransform, setUncontrolledTransform] = useState(initialTransform);
35
+ const visibleTransform = normalizedTransform(value ?? uncontrolledTransform, resolvedMinScale, resolvedMaxScale);
36
+ const engineRef = useRef(null);
37
+ const instructionsId = useId();
38
+ const committedRef = useRef(initialTransform);
39
+ const programmaticRef = useRef(false);
40
+ const pointerBlocksPanRef = useRef(false);
41
+ const wheelTargetBlocksZoomRef = useRef(false);
42
+ const animationDuration = useCallback((duration, fallback) => {
43
+ if (duration !== undefined)
44
+ return duration;
45
+ return prefersReducedMotion(engineRef.current?.instance.wrapperComponent ?? null) ? 0 : fallback;
46
+ }, []);
47
+ const syncExternalTransform = useCallback((next, duration = 0) => {
48
+ const normalized = normalizedTransform(next, resolvedMinScale, resolvedMaxScale);
49
+ committedRef.current = normalized;
50
+ programmaticRef.current = true;
51
+ const engine = engineRef.current;
52
+ if (!engine)
53
+ return;
54
+ void engine.setTransform(normalized.x, normalized.y, normalized.scale, duration)
55
+ .finally(() => {
56
+ programmaticRef.current = false;
57
+ });
58
+ }, [resolvedMaxScale, resolvedMinScale]);
59
+ useEffect(() => {
60
+ if (!controlled || transformsEqual(visibleTransform, committedRef.current))
61
+ return;
62
+ syncExternalTransform(visibleTransform);
63
+ }, [controlled, syncExternalTransform, visibleTransform.x, visibleTransform.y, visibleTransform.scale]);
64
+ useEffect(() => {
65
+ const clamped = normalizedTransform(committedRef.current, resolvedMinScale, resolvedMaxScale);
66
+ if (!transformsEqual(clamped, committedRef.current))
67
+ syncExternalTransform(clamped);
68
+ }, [resolvedMaxScale, resolvedMinScale, syncExternalTransform]);
69
+ const runProgrammatic = useCallback(async (action) => {
70
+ const engine = engineRef.current;
71
+ if (!engine || disabled)
72
+ return;
73
+ programmaticRef.current = true;
74
+ try {
75
+ await action(engine);
76
+ }
77
+ finally {
78
+ programmaticRef.current = false;
79
+ }
80
+ }, [disabled]);
81
+ const reconcileControlled = useCallback(() => {
82
+ if (controlled && !transformsEqual(visibleTransform, committedRef.current)) {
83
+ syncExternalTransform(visibleTransform, animationDuration(undefined, 100));
84
+ }
85
+ }, [animationDuration, controlled, syncExternalTransform, visibleTransform.x, visibleTransform.y, visibleTransform.scale]);
86
+ useImperativeHandle(forwardedRef, () => ({
87
+ reset: options => runProgrammatic(engine => engine.resetTransform(animationDuration(options?.duration, 200))),
88
+ fit: options => runProgrammatic(engine => engine.fitToView({
89
+ mode: options?.mode,
90
+ minScale: options?.minScale,
91
+ maxScale: options?.maxScale,
92
+ animationTime: animationDuration(options?.duration, 200),
93
+ })),
94
+ setTransform: (transform, options) => runProgrammatic(engine => {
95
+ const next = normalizedTransform(transform, resolvedMinScale, resolvedMaxScale);
96
+ return engine.setTransform(next.x, next.y, next.scale, animationDuration(options?.duration, 200));
97
+ }),
98
+ zoomIn: (step = keyboardZoomStep, options) => runProgrammatic(engine => engine.zoomIn(step, animationDuration(options?.duration, 200))),
99
+ zoomOut: (step = keyboardZoomStep, options) => runProgrammatic(engine => engine.zoomOut(step, animationDuration(options?.duration, 200))),
100
+ zoomTo: (scale, options) => runProgrammatic((engine) => {
101
+ const nextScale = clampScale(scale, resolvedMinScale, resolvedMaxScale);
102
+ if (options?.anchor) {
103
+ return engine.zoomToPoint(nextScale, options.anchor.x, options.anchor.y, animationDuration(options.duration, 200));
104
+ }
105
+ return engine.centerView(nextScale, animationDuration(options?.duration, 200));
106
+ }),
107
+ zoomToElement: (target, options) => runProgrammatic(engine => engine.zoomToElement(target, {
108
+ ...options,
109
+ animationTime: animationDuration(options?.animationTime, 300),
110
+ })),
111
+ panBy: (deltaX, deltaY, options) => runProgrammatic(engine => engine.panBy(deltaX, deltaY, animationDuration(options?.duration, 160))),
112
+ clientToContent: (clientX, clientY) => engineRef.current?.clientToContent(clientX, clientY) ?? { x: clientX, y: clientY },
113
+ contentToClient: (x, y) => engineRef.current?.contentToClient(x, y) ?? { x, y },
114
+ getTransform: () => committedRef.current,
115
+ getViewportElement: () => engineRef.current?.instance.wrapperComponent ?? null,
116
+ getContentElement: () => engineRef.current?.instance.contentComponent ?? null,
117
+ }), [animationDuration, keyboardZoomStep, resolvedMaxScale, resolvedMinScale, runProgrammatic]);
118
+ const userInteractionDisabled = disabled || readOnly;
119
+ const wheelActivation = wheel === 'modifier'
120
+ ? (keys) => !wheelTargetBlocksZoomRef.current && (keys.includes('Control') || keys.includes('Meta'))
121
+ : () => !wheelTargetBlocksZoomRef.current;
122
+ const defaultInstructions = wheel === 'modifier'
123
+ ? 'Use Control or Command with the mouse wheel to zoom. Use arrow keys to pan, plus or minus to zoom, and 0 to reset.'
124
+ : 'Use arrow keys to pan, plus or minus to zoom, and 0 to reset.';
125
+ const wheelHint = instructions ?? defaultInstructions;
126
+ const mergedViewportLabel = viewportProps?.['aria-label'] ?? ariaLabel;
127
+ const describedBy = [viewportProps?.['aria-describedby'], keyboard && !disabled ? instructionsId : null]
128
+ .filter(Boolean)
129
+ .join(' ') || undefined;
130
+ return _jsxs("div", { ...props, className: ['inkra-pan-zoom', className].filter(Boolean).join(' '), "data-disabled": disabled || undefined, "data-gestures": !userInteractionDisabled && (pan || pinch) || undefined, "data-read-only": readOnly || undefined, "data-wheel": wheel, style: style, children: [_jsx(PencilSurface, { "aria-hidden": "true", className: "inkra-pan-zoom__surface", radius: 8, variant: "paper" }), _jsx("div", { className: "inkra-pan-zoom__clip", children: _jsx(TransformWrapper, { ref: engineRef, initialScale: initialTransform.scale, initialPositionX: initialTransform.x, initialPositionY: initialTransform.y, minScale: resolvedMinScale, maxScale: resolvedMaxScale, limitToBounds: limitToBounds, centerZoomedOut: centerZoomedOut, centerOnInit: centerOnInit, fitOnInit: fitOnInit, smooth: true, panning: {
131
+ disabled: userInteractionDisabled || !pan,
132
+ excluded: interactionExclusions,
133
+ activationKeys: () => !pointerBlocksPanRef.current,
134
+ }, pinch: { disabled: userInteractionDisabled || !pinch, excluded: interactionExclusions }, wheel: {
135
+ disabled: userInteractionDisabled || wheel === 'disabled',
136
+ activationKeys: wheelActivation,
137
+ excluded: interactionExclusions,
138
+ }, trackPadPanning: { disabled: true }, keyboard: {
139
+ disabled: userInteractionDisabled || !keyboard,
140
+ panStep: keyboardPanStep,
141
+ zoomStep: keyboardZoomStep,
142
+ animationTime: 100,
143
+ excluded: interactionExclusions,
144
+ }, doubleClick: { disabled: userInteractionDisabled || !doubleClickZoom, mode: 'zoomIn', excluded: interactionExclusions }, zoomAnimation: { disabled: false, animationTime: 200 }, velocityAnimation: { disabled: prefersReducedMotion(engineRef.current?.instance.wrapperComponent ?? null) }, onPanningStop: reconcileControlled, onPinchStop: reconcileControlled, onWheelStop: reconcileControlled, onZoomStop: reconcileControlled, onTransform: (_, state) => {
145
+ const next = normalizedTransform({ x: state.positionX, y: state.positionY, scale: state.scale }, resolvedMinScale, resolvedMaxScale);
146
+ committedRef.current = next;
147
+ if (!controlled)
148
+ setUncontrolledTransform(next);
149
+ onTransformChange?.(next, { reason: programmaticRef.current ? 'programmatic' : 'gesture' });
150
+ }, children: _jsx(TransformComponent, { wrapperClass: ['inkra-pan-zoom__viewport', viewportClassName].filter(Boolean).join(' '), contentClass: ['inkra-pan-zoom__content', contentClassName].filter(Boolean).join(' '), wrapperStyle: viewportStyle, contentStyle: contentStyle, wrapperProps: {
151
+ ...viewportProps,
152
+ role: viewportProps?.role ?? 'region',
153
+ 'aria-label': mergedViewportLabel,
154
+ 'aria-disabled': disabled || undefined,
155
+ 'aria-readonly': readOnly || undefined,
156
+ 'aria-describedby': describedBy,
157
+ onPointerDownCapture: event => {
158
+ pointerBlocksPanRef.current = Boolean(event.target.closest?.('[data-pan-zoom-no-drag]'));
159
+ viewportProps?.onPointerDownCapture?.(event);
160
+ },
161
+ onPointerUpCapture: event => {
162
+ pointerBlocksPanRef.current = false;
163
+ viewportProps?.onPointerUpCapture?.(event);
164
+ },
165
+ onPointerCancelCapture: event => {
166
+ pointerBlocksPanRef.current = false;
167
+ viewportProps?.onPointerCancelCapture?.(event);
168
+ },
169
+ onWheelCapture: event => {
170
+ wheelTargetBlocksZoomRef.current = Boolean(event.target.closest?.('[contenteditable], [data-pan-zoom-no-drag]'));
171
+ viewportProps?.onWheelCapture?.(event);
172
+ },
173
+ }, contentProps: contentProps, children: children }) }) }), _jsx(PencilSurface, { "aria-hidden": "true", className: "inkra-pan-zoom__focus-ring", radius: 8, seed: "pan-zoom-focus", variant: "outline" }), keyboard && !disabled && _jsx("span", { className: "inkra-pan-zoom__instructions visually-hidden", id: instructionsId, children: wheelHint })] });
174
+ }
175
+ export const PanZoom = forwardRef(PanZoomRoot);
@@ -0,0 +1,10 @@
1
+ export type PencilPoint = readonly [x: number, y: number];
2
+ export interface PencilPathOptions {
3
+ seed: string | number;
4
+ width?: number;
5
+ height?: number;
6
+ points?: number;
7
+ roughness?: number;
8
+ }
9
+ export declare function createPencilPoints({ seed, width, height, points, roughness, }: PencilPathOptions): PencilPoint[];
10
+ export declare function createPencilPath(options: PencilPathOptions): string;
package/dist/pencil.js ADDED
@@ -0,0 +1,39 @@
1
+ function hashSeed(seed) {
2
+ const value = String(seed);
3
+ let hash = 2166136261;
4
+ for (let index = 0; index < value.length; index += 1) {
5
+ hash ^= value.charCodeAt(index);
6
+ hash = Math.imul(hash, 16777619);
7
+ }
8
+ return hash >>> 0;
9
+ }
10
+ function createRandom(seed) {
11
+ let state = hashSeed(seed) || 0x6d2b79f5;
12
+ return () => {
13
+ state += 0x6d2b79f5;
14
+ let value = state;
15
+ value = Math.imul(value ^ (value >>> 15), value | 1);
16
+ value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
17
+ return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
18
+ };
19
+ }
20
+ export function createPencilPoints({ seed, width = 100, height = 8, points = 7, roughness = 1, }) {
21
+ const random = createRandom(seed);
22
+ const safeWidth = Math.max(1, width);
23
+ const safeHeight = Math.max(1, height);
24
+ const pointCount = Math.max(2, Math.floor(points));
25
+ const center = safeHeight / 2;
26
+ const amplitude = Math.min(safeHeight * 0.32, Math.max(0, roughness));
27
+ return Array.from({ length: pointCount }, (_, index) => {
28
+ const progress = index / (pointCount - 1);
29
+ const xJitter = index === 0 || index === pointCount - 1 ? 0 : (random() - 0.5) * 1.4;
30
+ const yJitter = (random() - 0.5) * 2 * amplitude;
31
+ return [progress * safeWidth + xJitter, center + yJitter];
32
+ });
33
+ }
34
+ export function createPencilPath(options) {
35
+ const points = createPencilPoints(options);
36
+ return points
37
+ .map(([x, y], index) => `${index === 0 ? 'M' : 'L'} ${x.toFixed(2)} ${y.toFixed(2)}`)
38
+ .join(' ');
39
+ }
@@ -0,0 +1,54 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ export type RadarKey = string | number;
3
+ export interface RadarAxis {
4
+ id: RadarKey;
5
+ label: ReactNode;
6
+ min?: number;
7
+ max?: number;
8
+ ariaLabel?: string;
9
+ formatValue?: (value: number) => string;
10
+ }
11
+ export interface RadarPoint {
12
+ axisId: RadarKey;
13
+ value?: number | null;
14
+ label?: ReactNode;
15
+ }
16
+ export interface RadarSeries {
17
+ id: RadarKey;
18
+ label: ReactNode;
19
+ data: readonly RadarPoint[];
20
+ color?: string;
21
+ pattern?: 'solid' | 'dash' | 'dot';
22
+ hidden?: boolean;
23
+ }
24
+ export interface RadarValueContext {
25
+ axis: RadarAxis;
26
+ series: RadarSeries;
27
+ point: RadarPoint;
28
+ value: number | null;
29
+ missing: boolean;
30
+ }
31
+ export interface RadarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
32
+ axes: readonly RadarAxis[];
33
+ series: readonly RadarSeries[];
34
+ label: ReactNode;
35
+ description?: ReactNode;
36
+ size?: number | string;
37
+ maxRadius?: number;
38
+ levels?: number;
39
+ showAxes?: boolean;
40
+ showLabels?: boolean;
41
+ showLegend?: boolean;
42
+ showValues?: boolean;
43
+ showTable?: boolean;
44
+ legendLabel?: ReactNode;
45
+ missingLabel?: string;
46
+ emptyMessage?: ReactNode;
47
+ selectedSeries?: RadarKey | null;
48
+ defaultSelectedSeries?: RadarKey | null;
49
+ onSelectedSeriesChange?: (id: RadarKey | null, series: RadarSeries | null) => void;
50
+ renderAxisLabel?: (axis: RadarAxis) => ReactNode;
51
+ renderValueLabel?: (context: RadarValueContext) => ReactNode;
52
+ getValueLabel?: (value: number, axis: RadarAxis) => string;
53
+ }
54
+ export declare const Radar: import("react").ForwardRefExoticComponent<RadarProps & import("react").RefAttributes<HTMLDivElement>>;