@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,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,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>>;
@@ -0,0 +1,220 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { scaleLinear } from 'd3-scale';
3
+ import { curveLinearClosed, lineRadial } from 'd3-shape';
4
+ import { forwardRef, useEffect, useId, useMemo, useRef, useState } from 'react';
5
+ import { PencilSurface } from '../PencilSurface.js';
6
+ import { PencilText } from '../PencilText.js';
7
+ import { Button } from '../Button.js';
8
+ import { Table } from '../content/index.js';
9
+ import { observeCanvasRecovery } from '../internal/recovery.js';
10
+ import { seedToNumber } from '../internal/random.js';
11
+ import { pencilStroke } from '../internal/stroke.js';
12
+ import { drawPencilWash } from '../internal/wash.js';
13
+ import { effectiveDpr } from '../internal/cache.js';
14
+ import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from '../internal/renderTokens.js';
15
+ function RadarInk({ axisAngles, axes, levels, radius, center, seriesPoints, }) {
16
+ const canvasRef = useRef(null);
17
+ const [ready, setReady] = useState(false);
18
+ useEffect(() => {
19
+ const canvas = canvasRef.current;
20
+ if (!canvas)
21
+ return undefined;
22
+ let disposed = false;
23
+ let lastPaint = '';
24
+ const paint = () => {
25
+ const width = canvas.clientWidth;
26
+ const height = canvas.clientHeight;
27
+ if (!width || !height) {
28
+ lastPaint = '';
29
+ delete canvas.dataset.ready;
30
+ setReady(false);
31
+ return;
32
+ }
33
+ try {
34
+ const dpr = effectiveDpr(2);
35
+ const context = canvas.getContext('2d');
36
+ if (!context || context.isContextLost?.()) {
37
+ setReady(current => current ? false : current);
38
+ return;
39
+ }
40
+ const style = getComputedStyle(canvas);
41
+ const strokeWidth = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
42
+ const roughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
43
+ const washStrength = readRenderToken(style, '--inkra-wash-strength', pencilRenderDefaults.washStrength, 0, 1)
44
+ * (0.18 / pencilRenderDefaults.washStrength);
45
+ const washSpacing = readRenderToken(style, '--inkra-wash-spacing', pencilRenderDefaults.washSpacing, 1, 6);
46
+ const washRoughness = readRenderToken(style, '--inkra-wash-roughness', pencilRenderDefaults.washRoughness, 0, 6);
47
+ const washAngle = readRenderToken(style, '--inkra-wash-angle', pencilRenderDefaults.washAngle, -85, 85);
48
+ const washInset = readRenderToken(style, '--inkra-wash-inset', 2, 0, 24);
49
+ const resolveColor = (token, fallback) => style.getPropertyValue(token).trim() || fallback;
50
+ const resolvePaint = (value, fallback) => {
51
+ const probe = document.createElement('span');
52
+ probe.style.color = value;
53
+ probe.style.position = 'absolute';
54
+ probe.style.inlineSize = '0';
55
+ probe.style.blockSize = '0';
56
+ canvas.parentElement?.appendChild(probe);
57
+ const resolved = getComputedStyle(probe).color;
58
+ probe.remove();
59
+ return resolved && resolved !== 'rgba(0, 0, 0, 0)' ? resolved : fallback;
60
+ };
61
+ const borderColor = resolveColor('--inkra-border', '#99aa9b');
62
+ const paperColor = resolveColor('--inkra-paper', '#f4f0e3');
63
+ const resolvedSeriesColors = seriesPoints.map(entry => resolvePaint(entry.color, borderColor));
64
+ const key = JSON.stringify([width, height, dpr, borderColor, paperColor, strokeWidth, roughness, washStrength, washSpacing, washRoughness, washAngle, washInset, levels, radius, axisAngles, resolvedSeriesColors, seriesPoints.map(entry => [entry.item.id, entry.points.map(point => point.value)])]);
65
+ if (key === lastPaint && canvas.dataset.ready === 'true')
66
+ return;
67
+ canvas.width = Math.ceil(width * dpr);
68
+ canvas.height = Math.ceil(height * dpr);
69
+ context.setTransform(dpr, 0, 0, dpr, 0, 0);
70
+ context.clearRect(0, 0, width, height);
71
+ const scaleX = width / 1000;
72
+ const scaleY = height / 1000;
73
+ const line = (points, color, weight, seed, closed = false) => {
74
+ if (points.length < 2)
75
+ return;
76
+ const path = closed ? [...points, points[0]] : points;
77
+ pencilStroke(context, path, color, weight, seedToNumber(seed), roughness, 2, closed, { variation: 0.82, breaks: 0.2, grain: 0.72, gain: 2.1, step: 0.7 });
78
+ };
79
+ const coordinate = (angle, r) => [(center + Math.sin(angle) * r) * scaleX, (center - Math.cos(angle) * r) * scaleY];
80
+ const gridColor = borderColor;
81
+ for (let level = 1; level <= levels; level += 1) {
82
+ line(axisAngles.map(angle => coordinate(angle, radius * level / levels)), gridColor, strokeWidth * 0.55, `radar-grid-${level}`, true);
83
+ }
84
+ axes.forEach((_, index) => line([coordinate(0, 0), coordinate(axisAngles[index], radius)], gridColor, strokeWidth * 0.5, `radar-axis-${index}`));
85
+ seriesPoints.forEach(({ item, points, index }, seriesIndex) => {
86
+ const resolvedColor = resolvedSeriesColors[seriesIndex];
87
+ const valid = points.filter(point => point.value != null).map(point => coordinate(point.angle, point.radius));
88
+ if (valid.length < 2)
89
+ return;
90
+ const minX = Math.min(...valid.map(point => point[0]));
91
+ const maxX = Math.max(...valid.map(point => point[0]));
92
+ const minY = Math.min(...valid.map(point => point[1]));
93
+ const maxY = Math.max(...valid.map(point => point[1]));
94
+ context.save();
95
+ context.beginPath();
96
+ valid.forEach(([x, y], pointIndex) => pointIndex ? context.lineTo(x, y) : context.moveTo(x, y));
97
+ context.closePath();
98
+ context.clip();
99
+ drawPencilWash(context, { x: minX, y: minY, width: Math.max(1, maxX - minX), height: Math.max(1, maxY - minY) }, resolvedColor, seedToNumber(`radar-wash-${String(item.id)}`), { strength: washStrength, spacing: washSpacing, roughness: washRoughness, inset: washInset, radius: 8, edgeReach: 4, angle: washAngle, dpr });
100
+ context.restore();
101
+ line(valid, resolvedColor, strokeWidth * (0.68 + (index === 0 ? 0.12 : 0)), `radar-series-${String(item.id)}`, true);
102
+ points.forEach((point, pointIndex) => {
103
+ if (point.value == null)
104
+ return;
105
+ const [x, y] = coordinate(point.angle, point.radius);
106
+ const marker = index % 3 === 1
107
+ ? [[x - 5, y - 5], [x + 5, y - 5], [x + 5, y + 5], [x - 5, y + 5], [x - 5, y - 5]]
108
+ : index % 3 === 2
109
+ ? [[x, y - 6], [x + 6, y + 5], [x - 6, y + 5], [x, y - 6]]
110
+ : Array.from({ length: 25 }, (_, markerIndex) => { const angle = markerIndex / 24 * Math.PI * 2; return [x + Math.cos(angle) * 5, y + Math.sin(angle) * 5]; });
111
+ pencilStroke(context, marker, resolvedColor, strokeWidth * 0.9, seedToNumber(`radar-marker-${String(item.id)}-${pointIndex}`), roughness, 2, true, { variation: 0.78, breaks: 0.24, grain: 0.7, gain: 2.1, step: 0.55 });
112
+ void pointIndex;
113
+ });
114
+ });
115
+ lastPaint = key;
116
+ canvas.dataset.ready = 'true';
117
+ if (!disposed)
118
+ setReady(current => current ? current : true);
119
+ }
120
+ catch {
121
+ lastPaint = '';
122
+ delete canvas.dataset.ready;
123
+ setReady(false);
124
+ canvas.width = 1;
125
+ canvas.height = 1;
126
+ canvas.getContext('2d')?.clearRect(0, 0, 1, 1);
127
+ }
128
+ };
129
+ const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(paint);
130
+ const inheritedStyle = new MutationObserver(paint);
131
+ for (let node = canvas; node; node = node.parentElement)
132
+ inheritedStyle.observe(node, { attributes: true, attributeFilter: ['style', 'class', 'dir', 'data-theme', 'data-inkra-theme'] });
133
+ const scheme = window.matchMedia('(prefers-color-scheme: dark)');
134
+ scheme.addEventListener('change', paint);
135
+ const stopRecovery = observeCanvasRecovery(() => { lastPaint = ''; delete canvas.dataset.ready; paint(); });
136
+ observer?.observe(canvas);
137
+ paint();
138
+ return () => { disposed = true; observer?.disconnect(); inheritedStyle.disconnect(); scheme.removeEventListener('change', paint); stopRecovery(); };
139
+ }, [axes, axisAngles, center, levels, radius, seriesPoints]);
140
+ return _jsx("canvas", { "aria-hidden": "true", className: "inkra-radar__ink", "data-ready": ready || undefined, ref: canvasRef });
141
+ }
142
+ const palette = ['#4b96aa', '#994f3f', '#629568', '#8d6b9c', '#c58b47'];
143
+ function keyPart(value) { return JSON.stringify([typeof value, value]); }
144
+ function sameKey(a, b) { return a != null && b != null && a === b; }
145
+ function axisText(axis) { return axis.ariaLabel ?? (typeof axis.label === 'string' || typeof axis.label === 'number' ? String(axis.label) : String(axis.id)); }
146
+ function cssSize(value, fallback) {
147
+ if (typeof value === 'number')
148
+ return Number.isFinite(value) ? `${Math.max(220, value)}px` : fallback;
149
+ return value || fallback;
150
+ }
151
+ function finite(value) { return typeof value === 'number' && Number.isFinite(value); }
152
+ function formatNumber(value, axis, getValueLabel) {
153
+ return axis.formatValue?.(value) ?? getValueLabel?.(value, axis) ?? String(value);
154
+ }
155
+ function textNode(value, seed) {
156
+ return typeof value === 'string' || typeof value === 'number' ? _jsx(PencilText, { seed: seed, children: String(value) }) : value;
157
+ }
158
+ function domainForAxis(axis, values) {
159
+ let min = finite(axis.min) ? axis.min : values.length ? Math.min(...values) : 0;
160
+ let max = finite(axis.max) ? axis.max : values.length ? Math.max(...values) : 1;
161
+ if (min > max)
162
+ [min, max] = [max, min];
163
+ if (min === max) {
164
+ const pad = Math.abs(min) * 0.5 || 1;
165
+ return [min - pad, max + pad];
166
+ }
167
+ return [min, max];
168
+ }
169
+ function paintStyle(color) {
170
+ return { '--inkra-accent': color, '--inkra-accent-soft': color, '--inkra-accent-strong': color };
171
+ }
172
+ export const Radar = forwardRef(function Radar({ axes, series, label, description, size = 'min(100%, 34rem)', maxRadius, levels = 4, showAxes = true, showLabels = true, showLegend = true, showValues = true, showTable = true, legendLabel = 'Series', missingLabel = 'Missing', emptyMessage = 'No radar data', selectedSeries, defaultSelectedSeries = null, onSelectedSeriesChange, renderAxisLabel, renderValueLabel, getValueLabel, className, style, ...props }, forwardedRef) {
173
+ const id = useId();
174
+ const labelId = `inkra-radar-${id}-label`;
175
+ const descriptionId = description == null ? undefined : `inkra-radar-${id}-description`;
176
+ const [internalSelected, setInternalSelected] = useState(defaultSelectedSeries);
177
+ const selected = selectedSeries === undefined ? internalSelected : selectedSeries ?? null;
178
+ const visibleSeries = series.filter(item => !item.hidden);
179
+ const domains = useMemo(() => new Map(axes.map(axis => {
180
+ const values = visibleSeries.flatMap(item => item.data.filter(point => point.axisId === axis.id && finite(point.value)).map(point => point.value));
181
+ return [keyPart(axis.id), domainForAxis(axis, values)];
182
+ })), [axes, visibleSeries]);
183
+ const side = 1000;
184
+ const radius = Math.max(120, Math.min(maxRadius ?? 390, 470));
185
+ const axisCount = axes.length;
186
+ const angles = axes.map((_, index) => (index * Math.PI * 2) / Math.max(1, axisCount));
187
+ const center = side / 2;
188
+ const valuePoints = (item) => axes.map((axis, index) => {
189
+ const point = item.data.find(candidate => candidate.axisId === axis.id) ?? { axisId: axis.id, value: null };
190
+ const domain = domains.get(keyPart(axis.id)) ?? [0, 1];
191
+ const value = finite(point.value) ? point.value : null;
192
+ const scale = scaleLinear().domain(domain).range([0, radius]).clamp(true);
193
+ return { angle: angles[index], radius: value == null ? 0 : scale(value), value, point, axis };
194
+ });
195
+ const line = useMemo(() => lineRadial().angle(point => point.angle).radius(point => point.radius).curve(curveLinearClosed), []);
196
+ const rootStyle = { ...style, '--inkra-radar-size': cssSize(size, 'min(100%, 34rem)'), '--inkra-radar-levels': Math.max(2, Math.min(8, Math.round(levels))) };
197
+ const selectSeries = (item) => {
198
+ const next = sameKey(selected, item.id) ? null : item.id;
199
+ if (selectedSeries === undefined)
200
+ setInternalSelected(next);
201
+ onSelectedSeriesChange?.(next, next == null ? null : item);
202
+ };
203
+ const renderValue = (context) => {
204
+ if (renderValueLabel)
205
+ return renderValueLabel(context);
206
+ return context.missing ? missingLabel : showValues ? formatNumber(context.value, context.axis, getValueLabel) : '•';
207
+ };
208
+ return _jsxs("div", { ...props, ref: forwardedRef, className: ['inkra-radar', className].filter(Boolean).join(' '), style: rootStyle, children: [_jsxs("div", { className: "inkra-radar__heading", children: [_jsx("div", { className: "inkra-radar__label", id: labelId, children: typeof label === 'string' ? _jsx(PencilText, { seed: `${id}-label`, children: label }) : label }), description == null ? null : _jsx("div", { className: "inkra-radar__description", id: descriptionId, children: textNode(description, `${id}-description`) })] }), axes.length < 3 || visibleSeries.length === 0 ? _jsx("div", { className: "inkra-radar__empty", children: textNode(emptyMessage, `${id}-empty`) }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: "inkra-radar__viewport", children: [_jsx(RadarInk, { axisAngles: angles, axes: showAxes ? axes : [], center: center, levels: Math.max(2, Math.min(8, Math.round(levels))), radius: radius, seriesPoints: visibleSeries.map((item, index) => ({ item, points: valuePoints(item), color: item.color ?? palette[index % palette.length], index })) }), _jsxs("svg", { "aria-hidden": "true", className: "inkra-radar__svg-fallback", viewBox: `0 0 ${side} ${side}`, focusable: "false", role: "presentation", children: [_jsx("circle", { cx: center, cy: center, r: radius, fill: "none", stroke: "currentColor", strokeWidth: "1" }), visibleSeries.map(item => _jsx("path", { d: line(valuePoints(item)) ?? undefined, transform: `translate(${center} ${center})` }, keyPart(item.id)))] }), showLabels ? _jsx("div", { "aria-hidden": "true", className: "inkra-radar__labels", children: axes.map((axis, index) => {
209
+ const x = 50 + Math.sin(angles[index]) * 48;
210
+ const y = 50 - Math.cos(angles[index]) * 48;
211
+ return _jsx("span", { className: "inkra-radar__axis-label", style: { left: `${x}%`, top: `${y}%` }, children: renderAxisLabel ? renderAxisLabel(axis) : textNode(axis.label, `${id}-axis-${String(axis.id)}`) }, keyPart(axis.id));
212
+ }) }) : null] }), _jsx("div", { "aria-describedby": descriptionId, "aria-labelledby": labelId, className: "inkra-radar__summary", role: "group", children: visibleSeries.map((item, index) => {
213
+ const points = valuePoints(item);
214
+ const color = item.color ?? palette[index % palette.length];
215
+ return _jsxs("div", { className: "inkra-radar__series-summary", "data-series-id": item.id, children: [_jsxs(Button, { "aria-pressed": sameKey(selected, item.id), className: "inkra-radar__legend-button", onClick: () => selectSeries(item), size: "sm", variant: "ghost", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-radar__legend-mark", radius: 4, seed: `radar-${keyPart(item.id)}`, style: paintStyle(color), variant: sameKey(selected, item.id) ? 'selection' : 'quiet' }), textNode(item.label, `${id}-summary-${String(item.id)}`)] }), _jsx("div", { className: "inkra-radar__values", children: points.map((point, pointIndex) => {
216
+ const context = { axis: point.axis, series: item, point: point.point, value: point.value, missing: point.value == null };
217
+ return _jsxs("span", { className: "inkra-radar__value", "data-missing": context.missing || undefined, children: [_jsx("strong", { children: textNode(point.axis.label, `${id}-value-axis-${String(point.axis.id)}`) }), " ", textNode(renderValue(context), `${id}-value-${String(item.id)}-${String(point.axis.id)}`)] }, pointIndex);
218
+ }) })] }, keyPart(item.id));
219
+ }) }), showLegend ? _jsxs("div", { "aria-label": typeof legendLabel === 'string' ? legendLabel : undefined, className: "inkra-radar__legend", role: "group", children: [_jsx("span", { className: "inkra-radar__legend-title", children: textNode(legendLabel, `${id}-legend-title`) }), visibleSeries.map((item, index) => _jsxs(Button, { "aria-pressed": sameKey(selected, item.id), className: "inkra-radar__legend-button", onClick: () => selectSeries(item), size: "sm", variant: "ghost", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-radar__legend-mark", radius: 4, seed: `radar-legend-${keyPart(item.id)}`, style: paintStyle(item.color ?? palette[index % palette.length]), variant: sameKey(selected, item.id) ? 'selection' : 'quiet' }), textNode(item.label, `${id}-legend-${String(item.id)}`)] }, keyPart(item.id)))] }) : null, showTable ? _jsx(Table.Container, { "aria-label": typeof label === 'string' ? label : undefined, "aria-describedby": descriptionId, children: _jsxs(Table.Root, { className: "inkra-radar__table", children: [_jsx(Table.Caption, { children: textNode('Radar data', `${id}-table-caption`) }), _jsx(Table.Header, { children: _jsxs(Table.Row, { children: [_jsx(Table.Head, { children: textNode('Series', `${id}-table-series`) }), axes.map(axis => _jsx(Table.Head, { children: textNode(axis.label, `${id}-table-axis-${String(axis.id)}`) }, keyPart(axis.id)))] }) }), _jsx(Table.Body, { children: visibleSeries.map(item => _jsxs(Table.Row, { children: [_jsx(Table.Head, { scope: "row", children: textNode(item.label, `${id}-table-series-${String(item.id)}`) }), axes.map(axis => { const point = item.data.find(candidate => candidate.axisId === axis.id); const missing = !finite(point?.value); return _jsx(Table.Cell, { "data-missing": missing || undefined, children: textNode(missing ? missingLabel : formatNumber(point?.value, axis, getValueLabel), `${id}-table-value-${String(item.id)}-${String(axis.id)}`) }, keyPart(axis.id)); })] }, keyPart(item.id))) })] }) }) : null] })] });
220
+ });
@@ -0,0 +1,16 @@
1
+ import type { SliderProps } from '../slider/index.js';
2
+ /** A numeric range ordered as [minimum, maximum]. */
3
+ export type RangeSliderValue = [number, number];
4
+ export interface RangeSliderProps extends Omit<SliderProps, 'defaultValue' | 'minStepsBetweenThumbs' | 'onValueChange' | 'onValueCommit' | 'preserveThumbOrder' | 'thumbLabels' | 'value'> {
5
+ value?: RangeSliderValue;
6
+ defaultValue?: RangeSliderValue;
7
+ onValueChange?: (value: RangeSliderValue) => void;
8
+ onValueCommit?: (value: RangeSliderValue) => void;
9
+ /** Minimum numeric distance between endpoints. Rounded up to the next step and ignored when allowCross is true. */
10
+ minDistance?: number;
11
+ /** Allow a thumb to pass the other and exchange its low/high endpoint role. */
12
+ allowCross?: boolean;
13
+ /** Accessible names for the current low and high endpoints. */
14
+ thumbLabels?: readonly [string, string];
15
+ }
16
+ export declare const RangeSlider: import("react").ForwardRefExoticComponent<RangeSliderProps & import("react").RefAttributes<HTMLSpanElement>>;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { Slider } from '../slider/index.js';
4
+ const DEFAULT_LABELS = ['Minimum value', 'Maximum value'];
5
+ function asRangeValue(value) {
6
+ return [value[0], value[1]];
7
+ }
8
+ function normalizeRangeValue(value) {
9
+ return value[0] <= value[1] ? value : [value[1], value[0]];
10
+ }
11
+ function getMinimumSteps(minDistance, step) {
12
+ if (!Number.isFinite(minDistance) || minDistance <= 0)
13
+ return 0;
14
+ if (!Number.isFinite(step) || step <= 0)
15
+ return 0;
16
+ return Math.ceil(minDistance / step);
17
+ }
18
+ export const RangeSlider = forwardRef(function RangeSlider({ allowCross = false, defaultValue, max = 100, min = 0, minDistance = 0, onValueChange, onValueCommit, step = 1, thumbLabels = DEFAULT_LABELS, value, ...props }, forwardedRef) {
19
+ const controlledValue = value === undefined ? undefined : normalizeRangeValue(value);
20
+ const uncontrolledDefault = value === undefined ? normalizeRangeValue(defaultValue ?? [min, max]) : undefined;
21
+ return _jsx(Slider, { ...props, defaultValue: uncontrolledDefault, max: max, min: min, minStepsBetweenThumbs: allowCross ? 0 : getMinimumSteps(minDistance, step), onValueChange: next => onValueChange?.(asRangeValue(next)), onValueCommit: next => onValueCommit?.(asRangeValue(next)), preserveThumbOrder: !allowCross, ref: forwardedRef, step: step, thumbLabels: thumbLabels, value: controlledValue });
22
+ });
@@ -0,0 +1,28 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type RatingSize = 'sm' | 'md' | 'lg';
3
+ export type RatingClearStrategy = boolean | 'reselect' | 'keyboard';
4
+ export interface RatingSymbolState {
5
+ /** One-based symbol position. */
6
+ index: number;
7
+ /** Fraction displayed for this symbol, from 0 to 1. */
8
+ fraction: number;
9
+ filled: boolean;
10
+ previewing: boolean;
11
+ }
12
+ export interface RatingProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'defaultValue' | 'onChange'> {
13
+ value?: number;
14
+ defaultValue?: number;
15
+ onValueChange?: (value: number) => void;
16
+ count?: number;
17
+ step?: number;
18
+ clearable?: RatingClearStrategy;
19
+ disabled?: boolean;
20
+ readOnly?: boolean;
21
+ size?: RatingSize;
22
+ dir?: 'ltr' | 'rtl';
23
+ symbol?: ReactNode;
24
+ renderSymbol?: (state: RatingSymbolState) => ReactNode;
25
+ getValueLabel?: (value: number, count: number) => string;
26
+ }
27
+ export declare function normalizeRatingValue(value: number | undefined, count: number, step: number): number;
28
+ export declare const Rating: import("react").ForwardRefExoticComponent<RatingProps & import("react").RefAttributes<HTMLDivElement>>;