@inkra/react 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -3
- package/dist/Button.js +3 -2
- package/dist/PencilIcon.d.ts +10 -0
- package/dist/PencilIcon.js +83 -39
- package/dist/PencilLine.js +9 -1
- package/dist/PencilText.js +15 -9
- package/dist/accordion/Accordion.js +65 -5
- package/dist/alert-dialog/index.js +14 -4
- package/dist/audio-player/index.d.ts +2 -0
- package/dist/audio-player/index.js +131 -34
- package/dist/breadcrumb/index.js +4 -1
- package/dist/calendar/index.d.ts +4 -1
- package/dist/calendar/index.js +37 -12
- package/dist/calendar/locales.d.ts +7 -0
- package/dist/calendar/locales.js +2 -0
- package/dist/carousel/index.js +20 -10
- package/dist/choice-grid/index.d.ts +6 -1
- package/dist/choice-grid/index.js +63 -33
- package/dist/color-picker/GradientEditor.d.ts +40 -0
- package/dist/color-picker/GradientEditor.js +64 -0
- package/dist/color-picker/gradient.d.ts +25 -0
- package/dist/color-picker/gradient.js +60 -0
- package/dist/color-picker/index.d.ts +27 -7
- package/dist/color-picker/index.js +144 -40
- package/dist/color-swatch/index.d.ts +10 -0
- package/dist/color-swatch/index.js +9 -0
- package/dist/command/index.js +27 -4
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +15 -3
- package/dist/context-menu/index.js +1 -1
- package/dist/data-table/index.d.ts +7 -1
- package/dist/data-table/index.js +67 -16
- package/dist/data-table/model.d.ts +2 -0
- package/dist/data-table/model.js +7 -0
- package/dist/date-picker/index.d.ts +2 -0
- package/dist/date-picker/index.js +20 -21
- package/dist/day-agenda/index.d.ts +6 -1
- package/dist/day-agenda/index.js +49 -21
- package/dist/day-agenda/layout.js +65 -9
- package/dist/display/ProgressRing.js +7 -3
- package/dist/display/index.d.ts +4 -1
- package/dist/display/index.js +3 -2
- package/dist/dockable-panel/index.js +15 -13
- package/dist/drawer/index.js +11 -5
- package/dist/editable/index.d.ts +24 -2
- package/dist/editable/index.js +148 -40
- package/dist/empty/illustrations.d.ts +8 -0
- package/dist/empty/illustrations.js +78 -0
- package/dist/empty/index.d.ts +6 -1
- package/dist/empty/index.js +11 -3
- package/dist/flip/index.js +28 -8
- package/dist/form/Field.js +11 -10
- package/dist/form/Input.js +4 -1
- package/dist/form/Textarea.d.ts +3 -1
- package/dist/form/Textarea.js +9 -5
- package/dist/heatmap/index.js +4 -1
- package/dist/hotkey/index.d.ts +1 -0
- package/dist/hotkey/index.js +9 -4
- package/dist/hover-card/index.js +1 -1
- package/dist/i18n/catalog.d.ts +5 -0
- package/dist/i18n/catalog.js +289 -0
- package/dist/i18n/index.d.ts +23 -0
- package/dist/i18n/index.js +50 -0
- package/dist/image-cropper/index.js +21 -17
- package/dist/image-viewer/VideoResource.d.ts +21 -0
- package/dist/image-viewer/VideoResource.js +196 -0
- package/dist/image-viewer/index.d.ts +14 -0
- package/dist/image-viewer/index.js +141 -86
- package/dist/image-viewer/lucideGeometry.d.ts +3 -0
- package/dist/image-viewer/lucideGeometry.js +19 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/input-number/index.js +13 -5
- package/dist/internal/cache.d.ts +4 -0
- package/dist/internal/cache.js +18 -1
- package/dist/internal/iconGeometry.d.ts +2 -0
- package/dist/internal/iconGeometry.js +32 -0
- package/dist/internal/renderTokens.d.ts +4 -0
- package/dist/internal/renderTokens.js +4 -0
- package/dist/internal/stroke.d.ts +7 -0
- package/dist/internal/stroke.js +63 -7
- package/dist/internal/surfaceBudget.d.ts +20 -0
- package/dist/internal/surfaceBudget.js +69 -0
- package/dist/internal/surfaceRenderer.js +87 -28
- package/dist/internal/surfaceVisibility.d.ts +3 -0
- package/dist/internal/surfaceVisibility.js +60 -0
- package/dist/internal/textRenderer.d.ts +3 -0
- package/dist/internal/textRenderer.js +13 -5
- package/dist/internal/usePencilSurface.js +17 -1
- package/dist/internal/wash.js +4 -3
- package/dist/journey-map/index.d.ts +1 -1
- package/dist/journey-map/index.js +6 -3
- package/dist/journey-map/terrain.js +10 -5
- package/dist/keypad/index.js +4 -1
- package/dist/layout/index.js +1 -1
- package/dist/log/index.js +42 -33
- package/dist/markdown/CodeBlock.d.ts +19 -0
- package/dist/markdown/CodeBlock.js +86 -0
- package/dist/markdown/Deletion.d.ts +2 -0
- package/dist/markdown/Deletion.js +69 -0
- package/dist/markdown/icons.d.ts +14 -0
- package/dist/markdown/icons.js +16 -0
- package/dist/markdown/index.d.ts +17 -0
- package/dist/markdown/index.js +177 -0
- package/dist/node-graph/index.d.ts +3 -1
- package/dist/node-graph/index.js +63 -28
- package/dist/number-ticker/index.js +4 -1
- package/dist/number-ticker/precision.js +9 -2
- package/dist/overflow-list/index.js +9 -6
- package/dist/overlay/Overlay.js +8 -3
- package/dist/overlay/Popover.js +3 -3
- package/dist/overlay/themeBridge.js +14 -2
- package/dist/pagination/index.d.ts +10 -1
- package/dist/pagination/index.js +13 -9
- package/dist/pan-zoom/index.d.ts +2 -0
- package/dist/pan-zoom/index.js +12 -8
- package/dist/pencil.d.ts +1 -0
- package/dist/pencil.js +4 -4
- package/dist/radar/index.d.ts +2 -0
- package/dist/radar/index.js +44 -21
- package/dist/range-slider/index.js +4 -3
- package/dist/rating/index.d.ts +4 -0
- package/dist/rating/index.js +131 -24
- package/dist/resizable/index.d.ts +1 -1
- package/dist/resizable/index.js +5 -2
- package/dist/scroll-area/index.d.ts +1 -1
- package/dist/scroll-area/index.js +5 -2
- package/dist/select/MultipleSelect.js +5 -4
- package/dist/select/SearchableSelect.js +64 -7
- package/dist/select/Select.d.ts +1 -0
- package/dist/select/Select.js +6 -2
- package/dist/select/StepperSelect.d.ts +29 -0
- package/dist/select/StepperSelect.js +122 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.js +1 -0
- package/dist/select/stepperModel.d.ts +7 -0
- package/dist/select/stepperModel.js +29 -0
- package/dist/selection/Checkbox.js +4 -3
- package/dist/selection/RadioGroup.js +29 -13
- package/dist/selection/Switch.js +4 -3
- package/dist/selection/useNativeChecked.d.ts +2 -1
- package/dist/selection/useNativeChecked.js +26 -8
- package/dist/slider/index.js +50 -6
- package/dist/sortable/index.js +17 -11
- package/dist/sparkline/index.js +21 -6
- package/dist/sparkline/model.js +11 -4
- package/dist/steps/index.js +4 -1
- package/dist/styles/accordion.css +228 -0
- package/dist/styles/alert-dialog.css +139 -0
- package/dist/styles/audio-player.css +22 -0
- package/dist/styles/base.css +638 -0
- package/dist/styles/breadcrumb.css +7 -0
- package/dist/styles/calendar.css +62 -0
- package/dist/styles/carousel.css +173 -0
- package/dist/styles/choice-grid.css +226 -0
- package/dist/styles/color-picker.css +281 -0
- package/dist/styles/color-swatch.css +47 -0
- package/dist/styles/command.css +52 -0
- package/dist/styles/content.css +79 -0
- package/dist/styles/context-menu.css +35 -0
- package/dist/styles/data-table.css +31 -0
- package/dist/styles/date-picker.css +107 -0
- package/dist/styles/day-agenda.css +30 -0
- package/dist/styles/display.css +304 -0
- package/dist/styles/dockable-panel.css +110 -0
- package/dist/styles/drawer.css +133 -0
- package/dist/styles/dropdown-menu.css +68 -0
- package/dist/styles/editable.css +145 -0
- package/dist/styles/empty.css +108 -0
- package/dist/styles/flip.css +101 -0
- package/dist/styles/form.css +634 -0
- package/dist/styles/heatmap.css +256 -0
- package/dist/styles/hotkey.css +189 -0
- package/dist/styles/hover-card.css +27 -0
- package/dist/styles/image-cropper.css +205 -0
- package/dist/styles/image-viewer.css +422 -0
- package/dist/styles/input-number.css +21 -0
- package/dist/styles/journey-map.css +19 -0
- package/dist/styles/keypad.css +92 -0
- package/dist/styles/layout.css +72 -0
- package/dist/styles/leaflet.css +661 -0
- package/dist/styles/log.css +203 -0
- package/dist/styles/markdown.css +379 -0
- package/dist/styles/node-graph.css +142 -0
- package/dist/styles/number-ticker.css +83 -0
- package/dist/styles/overflow-list.css +51 -0
- package/dist/styles/overlay.css +284 -0
- package/dist/styles/pagination.css +21 -0
- package/dist/styles/pan-zoom.css +136 -0
- package/dist/styles/radar.css +1 -0
- package/dist/styles/rating.css +121 -0
- package/dist/styles/resizable.css +108 -0
- package/dist/styles/scroll-area.css +122 -0
- package/dist/styles/select.css +554 -0
- package/dist/styles/selection.css +264 -0
- package/dist/styles/slider.css +145 -0
- package/dist/styles/slot.css +109 -0
- package/dist/styles/sortable.css +159 -0
- package/dist/styles/sparkline.css +24 -0
- package/dist/styles/steps.css +27 -0
- package/dist/styles/tabs.css +79 -0
- package/dist/styles/tag-input.css +213 -0
- package/dist/styles/time-picker.css +66 -0
- package/dist/styles/timeline.css +24 -0
- package/dist/styles/toast.css +160 -0
- package/dist/styles/toggle-group.css +107 -0
- package/dist/styles/tooltip.css +78 -0
- package/dist/styles/transfer.css +148 -0
- package/dist/styles/tree-select.css +131 -0
- package/dist/styles/tree.css +59 -0
- package/dist/styles/typewriter.css +62 -0
- package/dist/styles/upload.css +60 -0
- package/dist/styles/virtual-list.css +112 -0
- package/dist/styles.css +2091 -421
- package/dist/tabs/index.d.ts +9 -0
- package/dist/tabs/index.js +69 -16
- package/dist/tabs/useOverflow.d.ts +20 -0
- package/dist/tabs/useOverflow.js +145 -0
- package/dist/tag-input/index.d.ts +7 -2
- package/dist/tag-input/index.js +81 -42
- package/dist/theme/index.d.ts +11 -0
- package/dist/theme/index.js +23 -0
- package/dist/time-picker/index.js +45 -27
- package/dist/timeline/index.js +4 -1
- package/dist/toast/index.d.ts +19 -11
- package/dist/toast/index.js +51 -8
- package/dist/toast/manager.d.ts +42 -0
- package/dist/toast/manager.js +85 -0
- package/dist/toast/useToastNavigation.d.ts +3 -0
- package/dist/toast/useToastNavigation.js +75 -0
- package/dist/tokens.css +4 -0
- package/dist/tooltip/Tooltip.js +3 -3
- package/dist/transfer/index.d.ts +5 -0
- package/dist/transfer/index.js +120 -53
- package/dist/transfer/model.d.ts +4 -0
- package/dist/transfer/model.js +37 -11
- package/dist/tree/index.d.ts +5 -1
- package/dist/tree/index.js +11 -6
- package/dist/tree-select/index.js +70 -31
- package/dist/tree-select/model.d.ts +11 -5
- package/dist/tree-select/model.js +111 -83
- package/dist/typewriter/index.js +10 -10
- package/dist/upload/index.js +50 -33
- package/dist/virtual-list/index.js +11 -6
- package/package.json +423 -3
package/dist/pan-zoom/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
|
|
3
|
-
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef,
|
|
4
|
+
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef, } from 'react';
|
|
4
5
|
import { PencilSurface } from '../PencilSurface.js';
|
|
5
6
|
const defaultTransform = { x: 0, y: 0, scale: 1 };
|
|
6
7
|
const interactionExclusions = [
|
|
@@ -26,16 +27,20 @@ function transformsEqual(left, right) {
|
|
|
26
27
|
function prefersReducedMotion(element) {
|
|
27
28
|
return element?.ownerDocument.defaultView?.matchMedia('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
28
29
|
}
|
|
29
|
-
function PanZoomRoot(
|
|
30
|
+
function PanZoomRoot(inkraProps, forwardedRef) {
|
|
31
|
+
const { t } = useInkraLocale();
|
|
32
|
+
const { children, value, defaultValue = defaultTransform, onTransformChange, minScale = 0.25, maxScale = 8, wheel = 'modifier', wheelStep = 0.001, 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 = t('panZoom.label'), ...props } = inkraProps;
|
|
30
33
|
const resolvedMinScale = Math.max(Number.isFinite(minScale) ? minScale : 0.25, 0.0001);
|
|
31
34
|
const resolvedMaxScale = Math.max(Number.isFinite(maxScale) ? maxScale : 8, resolvedMinScale);
|
|
35
|
+
const resolvedWheelStep = Number.isFinite(wheelStep) && wheelStep > 0 ? wheelStep : 0.001;
|
|
32
36
|
const controlled = value !== undefined;
|
|
33
37
|
const initialTransform = normalizedTransform(value ?? defaultValue, resolvedMinScale, resolvedMaxScale);
|
|
34
|
-
const [uncontrolledTransform, setUncontrolledTransform] = useState(initialTransform);
|
|
35
|
-
const visibleTransform = normalizedTransform(value ?? uncontrolledTransform, resolvedMinScale, resolvedMaxScale);
|
|
36
38
|
const engineRef = useRef(null);
|
|
37
39
|
const instructionsId = useId();
|
|
40
|
+
// The engine owns the live DOM transform. Keep its snapshot for imperative reads,
|
|
41
|
+
// rather than rerendering the complete content subtree on every gesture frame.
|
|
38
42
|
const committedRef = useRef(initialTransform);
|
|
43
|
+
const visibleTransform = normalizedTransform(value ?? committedRef.current, resolvedMinScale, resolvedMaxScale);
|
|
39
44
|
const programmaticRef = useRef(false);
|
|
40
45
|
const pointerBlocksPanRef = useRef(false);
|
|
41
46
|
const wheelTargetBlocksZoomRef = useRef(false);
|
|
@@ -120,8 +125,8 @@ function PanZoomRoot({ children, value, defaultValue = defaultTransform, onTrans
|
|
|
120
125
|
? (keys) => !wheelTargetBlocksZoomRef.current && (keys.includes('Control') || keys.includes('Meta'))
|
|
121
126
|
: () => !wheelTargetBlocksZoomRef.current;
|
|
122
127
|
const defaultInstructions = wheel === 'modifier'
|
|
123
|
-
? '
|
|
124
|
-
: '
|
|
128
|
+
? t('panZoom.modifierInstructions')
|
|
129
|
+
: t('panZoom.instructions');
|
|
125
130
|
const wheelHint = instructions ?? defaultInstructions;
|
|
126
131
|
const mergedViewportLabel = viewportProps?.['aria-label'] ?? ariaLabel;
|
|
127
132
|
const describedBy = [viewportProps?.['aria-describedby'], keyboard && !disabled ? instructionsId : null]
|
|
@@ -132,6 +137,7 @@ function PanZoomRoot({ children, value, defaultValue = defaultTransform, onTrans
|
|
|
132
137
|
excluded: interactionExclusions,
|
|
133
138
|
activationKeys: () => !pointerBlocksPanRef.current,
|
|
134
139
|
}, pinch: { disabled: userInteractionDisabled || !pinch, excluded: interactionExclusions }, wheel: {
|
|
140
|
+
step: resolvedWheelStep,
|
|
135
141
|
disabled: userInteractionDisabled || wheel === 'disabled',
|
|
136
142
|
activationKeys: wheelActivation,
|
|
137
143
|
excluded: interactionExclusions,
|
|
@@ -144,8 +150,6 @@ function PanZoomRoot({ children, value, defaultValue = defaultTransform, onTrans
|
|
|
144
150
|
}, 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
151
|
const next = normalizedTransform({ x: state.positionX, y: state.positionY, scale: state.scale }, resolvedMinScale, resolvedMaxScale);
|
|
146
152
|
committedRef.current = next;
|
|
147
|
-
if (!controlled)
|
|
148
|
-
setUncontrolledTransform(next);
|
|
149
153
|
onTransformChange?.(next, { reason: programmaticRef.current ? 'programmatic' : 'gesture' });
|
|
150
154
|
}, 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
155
|
...viewportProps,
|
package/dist/pencil.d.ts
CHANGED
package/dist/pencil.js
CHANGED
|
@@ -19,11 +19,11 @@ function createRandom(seed) {
|
|
|
19
19
|
}
|
|
20
20
|
export function createPencilPoints({ seed, width = 100, height = 8, points = 7, roughness = 1, }) {
|
|
21
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));
|
|
22
|
+
const safeWidth = Number.isFinite(width) ? Math.max(1, width) : 100;
|
|
23
|
+
const safeHeight = Number.isFinite(height) ? Math.max(1, height) : 8;
|
|
24
|
+
const pointCount = Number.isFinite(points) ? Math.max(2, Math.min(65536, Math.floor(points))) : 7;
|
|
25
25
|
const center = safeHeight / 2;
|
|
26
|
-
const amplitude = Math.min(safeHeight * 0.32, Math.max(0, roughness));
|
|
26
|
+
const amplitude = Math.min(safeHeight * 0.32, Math.max(0, Number.isFinite(roughness) ? roughness : 1));
|
|
27
27
|
return Array.from({ length: pointCount }, (_, index) => {
|
|
28
28
|
const progress = index / (pointCount - 1);
|
|
29
29
|
const xJitter = index === 0 || index === pointCount - 1 ? 0 : (random() - 0.5) * 1.4;
|
package/dist/radar/index.d.ts
CHANGED
|
@@ -29,7 +29,9 @@ export interface RadarValueContext {
|
|
|
29
29
|
missing: boolean;
|
|
30
30
|
}
|
|
31
31
|
export interface RadarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
|
|
32
|
+
/** Immutable snapshots: replace the axes array when an axis changes. */
|
|
32
33
|
axes: readonly RadarAxis[];
|
|
34
|
+
/** Immutable snapshots: replace series and changed records/data when values change. */
|
|
33
35
|
series: readonly RadarSeries[];
|
|
34
36
|
label: ReactNode;
|
|
35
37
|
description?: ReactNode;
|
package/dist/radar/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import { scaleLinear } from 'd3-scale';
|
|
3
4
|
import { curveLinearClosed, lineRadial } from 'd3-shape';
|
|
4
5
|
import { forwardRef, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
@@ -87,10 +88,13 @@ function RadarInk({ axisAngles, axes, levels, radius, center, seriesPoints, }) {
|
|
|
87
88
|
const valid = points.filter(point => point.value != null).map(point => coordinate(point.angle, point.radius));
|
|
88
89
|
if (valid.length < 2)
|
|
89
90
|
return;
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
92
|
+
for (const [x, y] of valid) {
|
|
93
|
+
minX = Math.min(minX, x);
|
|
94
|
+
maxX = Math.max(maxX, x);
|
|
95
|
+
minY = Math.min(minY, y);
|
|
96
|
+
maxY = Math.max(maxY, y);
|
|
97
|
+
}
|
|
94
98
|
context.save();
|
|
95
99
|
context.beginPath();
|
|
96
100
|
valid.forEach(([x, y], pointIndex) => pointIndex ? context.lineTo(x, y) : context.moveTo(x, y));
|
|
@@ -139,6 +143,7 @@ function RadarInk({ axisAngles, axes, levels, radius, center, seriesPoints, }) {
|
|
|
139
143
|
}, [axes, axisAngles, center, levels, radius, seriesPoints]);
|
|
140
144
|
return _jsx("canvas", { "aria-hidden": "true", className: "inkra-radar__ink", "data-ready": ready || undefined, ref: canvasRef });
|
|
141
145
|
}
|
|
146
|
+
const noAxes = [];
|
|
142
147
|
const palette = ['#4b96aa', '#994f3f', '#629568', '#8d6b9c', '#c58b47'];
|
|
143
148
|
function keyPart(value) { return JSON.stringify([typeof value, value]); }
|
|
144
149
|
function sameKey(a, b) { return a != null && b != null && a === b; }
|
|
@@ -156,8 +161,14 @@ function textNode(value, seed) {
|
|
|
156
161
|
return typeof value === 'string' || typeof value === 'number' ? _jsx(PencilText, { seed: seed, children: String(value) }) : value;
|
|
157
162
|
}
|
|
158
163
|
function domainForAxis(axis, values) {
|
|
159
|
-
let
|
|
160
|
-
let
|
|
164
|
+
let minimum = values.length ? values[0] : 0;
|
|
165
|
+
let maximum = values.length ? values[0] : 1;
|
|
166
|
+
for (let index = 1; index < values.length; index++) {
|
|
167
|
+
minimum = Math.min(minimum, values[index]);
|
|
168
|
+
maximum = Math.max(maximum, values[index]);
|
|
169
|
+
}
|
|
170
|
+
let min = finite(axis.min) ? axis.min : minimum;
|
|
171
|
+
let max = finite(axis.max) ? axis.max : maximum;
|
|
161
172
|
if (min > max)
|
|
162
173
|
[min, max] = [max, min];
|
|
163
174
|
if (min === max) {
|
|
@@ -169,13 +180,15 @@ function domainForAxis(axis, values) {
|
|
|
169
180
|
function paintStyle(color) {
|
|
170
181
|
return { '--inkra-accent': color, '--inkra-accent-soft': color, '--inkra-accent-strong': color };
|
|
171
182
|
}
|
|
172
|
-
export const Radar = forwardRef(function Radar(
|
|
183
|
+
export const Radar = forwardRef(function Radar(inkraProps, forwardedRef) {
|
|
184
|
+
const { t } = useInkraLocale();
|
|
185
|
+
const { axes, series, label, description, size = 'min(100%, 34rem)', maxRadius, levels = 4, showAxes = true, showLabels = true, showLegend = true, showValues = true, showTable = true, legendLabel = t('radar.series'), tableCaption = t('radar.caption'), seriesLabel = t('radar.series'), missingLabel = t('common.missing'), emptyMessage = t('radar.empty'), selectedSeries, defaultSelectedSeries = null, onSelectedSeriesChange, renderAxisLabel, renderValueLabel, getValueLabel, className, style, ...props } = inkraProps;
|
|
173
186
|
const id = useId();
|
|
174
187
|
const labelId = `inkra-radar-${id}-label`;
|
|
175
188
|
const descriptionId = description == null ? undefined : `inkra-radar-${id}-description`;
|
|
176
189
|
const [internalSelected, setInternalSelected] = useState(defaultSelectedSeries);
|
|
177
190
|
const selected = selectedSeries === undefined ? internalSelected : selectedSeries ?? null;
|
|
178
|
-
const visibleSeries = series.filter(item => !item.hidden);
|
|
191
|
+
const visibleSeries = useMemo(() => series.filter(item => !item.hidden), [series]);
|
|
179
192
|
const domains = useMemo(() => new Map(axes.map(axis => {
|
|
180
193
|
const values = visibleSeries.flatMap(item => item.data.filter(point => point.axisId === axis.id && finite(point.value)).map(point => point.value));
|
|
181
194
|
return [keyPart(axis.id), domainForAxis(axis, values)];
|
|
@@ -183,15 +196,27 @@ export const Radar = forwardRef(function Radar({ axes, series, label, descriptio
|
|
|
183
196
|
const side = 1000;
|
|
184
197
|
const radius = Math.max(120, Math.min(maxRadius ?? 390, 470));
|
|
185
198
|
const axisCount = axes.length;
|
|
186
|
-
const angles = axes.map((_, index) => (index * Math.PI * 2) / Math.max(1, axisCount));
|
|
199
|
+
const angles = useMemo(() => axes.map((_, index) => (index * Math.PI * 2) / Math.max(1, axisCount)), [axes, axisCount]);
|
|
187
200
|
const center = side / 2;
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
const scales = useMemo(() => axes.map(axis => scaleLinear()
|
|
202
|
+
.domain(domains.get(keyPart(axis.id)) ?? [0, 1]).range([0, radius]).clamp(true)), [axes, domains, radius]);
|
|
203
|
+
const seriesPoints = useMemo(() => visibleSeries.map((item, index) => {
|
|
204
|
+
// Preserve the first point for duplicate axis ids, matching Array.find.
|
|
205
|
+
const pointsByAxis = new Map();
|
|
206
|
+
for (const point of item.data) {
|
|
207
|
+
// Map uses SameValueZero, while the prior lookup used strict equality.
|
|
208
|
+
if (typeof point.axisId === 'number' && Number.isNaN(point.axisId))
|
|
209
|
+
continue;
|
|
210
|
+
if (!pointsByAxis.has(point.axisId))
|
|
211
|
+
pointsByAxis.set(point.axisId, point);
|
|
212
|
+
}
|
|
213
|
+
const points = axes.map((axis, axisIndex) => {
|
|
214
|
+
const point = pointsByAxis.get(axis.id) ?? { axisId: axis.id, value: null };
|
|
215
|
+
const value = finite(point.value) ? point.value : null;
|
|
216
|
+
return { angle: angles[axisIndex], radius: value == null ? 0 : scales[axisIndex](value), value, point, axis };
|
|
217
|
+
});
|
|
218
|
+
return { item, points, color: item.color ?? palette[index % palette.length], index };
|
|
219
|
+
}), [angles, axes, scales, visibleSeries]);
|
|
195
220
|
const line = useMemo(() => lineRadial().angle(point => point.angle).radius(point => point.radius).curve(curveLinearClosed), []);
|
|
196
221
|
const rootStyle = { ...style, '--inkra-radar-size': cssSize(size, 'min(100%, 34rem)'), '--inkra-radar-levels': Math.max(2, Math.min(8, Math.round(levels))) };
|
|
197
222
|
const selectSeries = (item) => {
|
|
@@ -205,16 +230,14 @@ export const Radar = forwardRef(function Radar({ axes, series, label, descriptio
|
|
|
205
230
|
return renderValueLabel(context);
|
|
206
231
|
return context.missing ? missingLabel : showValues ? formatNumber(context.value, context.axis, getValueLabel) : '•';
|
|
207
232
|
};
|
|
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 :
|
|
233
|
+
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 : noAxes, center: center, levels: Math.max(2, Math.min(8, Math.round(levels))), radius: radius, seriesPoints: seriesPoints }), _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" }), seriesPoints.map(({ item, points }) => _jsx("path", { d: line(points) ?? undefined, transform: `translate(${center} ${center})` }, keyPart(item.id)))] }), showLabels ? _jsx("div", { "aria-hidden": "true", className: "inkra-radar__labels", children: axes.map((axis, index) => {
|
|
209
234
|
const x = 50 + Math.sin(angles[index]) * 48;
|
|
210
235
|
const y = 50 - Math.cos(angles[index]) * 48;
|
|
211
236
|
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:
|
|
213
|
-
const points = valuePoints(item);
|
|
214
|
-
const color = item.color ?? palette[index % palette.length];
|
|
237
|
+
}) }) : null] }), _jsx("div", { "aria-describedby": descriptionId, "aria-labelledby": labelId, className: "inkra-radar__summary", role: "group", children: seriesPoints.map(({ item, points, color }) => {
|
|
215
238
|
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
239
|
const context = { axis: point.axis, series: item, point: point.point, value: point.value, missing: point.value == null };
|
|
217
240
|
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
241
|
}) })] }, 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(tableCaption, `${id}-table-caption`) }), _jsx(Table.Header, { children: _jsxs(Table.Row, { children: [_jsx(Table.Head, { children: textNode(seriesLabel, `${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:
|
|
242
|
+
}) }), 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(tableCaption, `${id}-table-caption`) }), _jsx(Table.Header, { children: _jsxs(Table.Row, { children: [_jsx(Table.Head, { children: textNode(seriesLabel, `${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: seriesPoints.map(({ item, points }) => _jsxs(Table.Row, { children: [_jsx(Table.Head, { scope: "row", children: textNode(item.label, `${id}-table-series-${String(item.id)}`) }), points.map(({ axis, value }) => { const missing = value === null; return _jsx(Table.Cell, { "data-missing": missing || undefined, children: textNode(missing ? missingLabel : formatNumber(value, axis, getValueLabel), `${id}-table-value-${String(item.id)}-${String(axis.id)}`) }, keyPart(axis.id)); })] }, keyPart(item.id))) })] }) }) : null] })] });
|
|
220
243
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
3
4
|
import { Slider } from '../slider/index.js';
|
|
4
|
-
const DEFAULT_LABELS = ['Minimum value', 'Maximum value'];
|
|
5
5
|
function asRangeValue(value) {
|
|
6
6
|
return [value[0], value[1]];
|
|
7
7
|
}
|
|
@@ -15,8 +15,9 @@ function getMinimumSteps(minDistance, step) {
|
|
|
15
15
|
return 0;
|
|
16
16
|
return Math.ceil(minDistance / step);
|
|
17
17
|
}
|
|
18
|
-
export const RangeSlider = forwardRef(function RangeSlider({ allowCross = false, defaultValue, max = 100, min = 0, minDistance = 0, onValueChange, onValueCommit, step = 1, thumbLabels
|
|
18
|
+
export const RangeSlider = forwardRef(function RangeSlider({ allowCross = false, defaultValue, max = 100, min = 0, minDistance = 0, onValueChange, onValueCommit, step = 1, thumbLabels, value, ...props }, forwardedRef) {
|
|
19
|
+
const { t } = useInkraLocale();
|
|
19
20
|
const controlledValue = value === undefined ? undefined : normalizeRangeValue(value);
|
|
20
21
|
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
|
+
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 ?? [t('rangeSlider.minimum'), t('rangeSlider.maximum')], value: controlledValue });
|
|
22
23
|
});
|
package/dist/rating/index.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export interface RatingProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
|
|
|
13
13
|
value?: number;
|
|
14
14
|
defaultValue?: number;
|
|
15
15
|
onValueChange?: (value: number) => void;
|
|
16
|
+
name?: string;
|
|
17
|
+
form?: string;
|
|
18
|
+
hoverPreview?: boolean;
|
|
19
|
+
onPreviewChange?: (value: number | null) => void;
|
|
16
20
|
count?: number;
|
|
17
21
|
step?: number;
|
|
18
22
|
clearable?: RatingClearStrategy;
|
package/dist/rating/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
3
|
+
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
3
4
|
import { PencilIcon } from '../PencilIcon.js';
|
|
5
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
4
6
|
const defaultSymbolPaths = [
|
|
5
7
|
{ d: 'M12 2.5 L21.5 12 L12 21.5 L2.5 12 Z' },
|
|
6
8
|
{ d: 'M7.5 12 L12 7.5 L16.5 12 L12 16.5 Z', mode: 'scuff' },
|
|
@@ -16,6 +18,10 @@ function decimalPlaces(value) {
|
|
|
16
18
|
}
|
|
17
19
|
export function normalizeRatingValue(value, count, step) {
|
|
18
20
|
const numeric = typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
21
|
+
if (numeric <= 0)
|
|
22
|
+
return 0;
|
|
23
|
+
if (numeric >= count)
|
|
24
|
+
return count;
|
|
19
25
|
const precision = Math.min(8, Math.max(decimalPlaces(step), decimalPlaces(count)));
|
|
20
26
|
const snapped = Math.round(numeric / step) * step;
|
|
21
27
|
return Number(Math.min(count, Math.max(0, snapped)).toFixed(precision));
|
|
@@ -26,28 +32,88 @@ function allowsReselectClear(clearable) {
|
|
|
26
32
|
function allowsKeyboardClear(clearable) {
|
|
27
33
|
return clearable === true || clearable === 'keyboard';
|
|
28
34
|
}
|
|
29
|
-
|
|
35
|
+
function previousRatingValue(value, count, step) {
|
|
36
|
+
if (value >= count) {
|
|
37
|
+
const precision = Math.min(8, Math.max(decimalPlaces(step), decimalPlaces(count)));
|
|
38
|
+
const previousIndex = Math.max(0, Math.ceil(count / step) - 1);
|
|
39
|
+
return Number((previousIndex * step).toFixed(precision));
|
|
40
|
+
}
|
|
41
|
+
return value - step;
|
|
42
|
+
}
|
|
43
|
+
export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onValueChange, name, form, hoverPreview = true, onPreviewChange, count: countProp = 5, step: stepProp = 1, clearable = false, disabled = false, readOnly = false, size = 'md', dir, symbol, renderSymbol, getValueLabel: valueLabelProp, className = '', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, onBlur, onKeyDown, onPointerDown, onPointerLeave, ...props }, forwardedRef) {
|
|
44
|
+
const { t } = useInkraLocale();
|
|
45
|
+
const getValueLabel = valueLabelProp ?? ((value, count) => t('rating.value', { value, count }));
|
|
30
46
|
const count = Math.max(1, Math.floor(finitePositive(countProp, 5)));
|
|
31
47
|
const step = Math.min(count, finitePositive(stepProp, 1));
|
|
32
48
|
const controlled = value !== undefined;
|
|
33
49
|
const [uncontrolledValue, setUncontrolledValue] = useState(() => normalizeRatingValue(defaultValue, count, step));
|
|
34
50
|
const committedValue = normalizeRatingValue(controlled ? value : uncontrolledValue, count, step);
|
|
35
51
|
const [previewValue, setPreviewValue] = useState(null);
|
|
36
|
-
const
|
|
52
|
+
const previewRef = useRef(null);
|
|
53
|
+
const previewCallback = useRef(onPreviewChange);
|
|
54
|
+
previewCallback.current = onPreviewChange;
|
|
55
|
+
const resetState = useRef({ controlled, defaultValue, count, step });
|
|
56
|
+
resetState.current = { controlled, defaultValue, count, step };
|
|
57
|
+
const inputRef = useRef(null);
|
|
58
|
+
const rootRef = useRef(null);
|
|
59
|
+
const symbolsRef = useRef(null);
|
|
60
|
+
const pendingKeyboardValueRef = useRef(null);
|
|
61
|
+
const pendingKeyboardTimerRef = useRef(undefined);
|
|
62
|
+
useImperativeHandle(forwardedRef, () => rootRef.current);
|
|
63
|
+
const clearPendingKeyboard = useCallback(() => {
|
|
64
|
+
clearTimeout(pendingKeyboardTimerRef.current);
|
|
65
|
+
pendingKeyboardTimerRef.current = undefined;
|
|
66
|
+
pendingKeyboardValueRef.current = null;
|
|
67
|
+
}, []);
|
|
68
|
+
const changePreview = useCallback((next) => {
|
|
69
|
+
if (previewRef.current === next)
|
|
70
|
+
return;
|
|
71
|
+
previewRef.current = next;
|
|
72
|
+
setPreviewValue(next);
|
|
73
|
+
previewCallback.current?.(next);
|
|
74
|
+
}, []);
|
|
75
|
+
const previewing = hoverPreview && !disabled && !readOnly && previewValue !== null;
|
|
76
|
+
const displayValue = previewing ? normalizeRatingValue(previewValue, count, step) : committedValue;
|
|
37
77
|
const symbolId = useId();
|
|
38
78
|
const symbolIndexes = useMemo(() => Array.from({ length: count }, (_, index) => index), [count]);
|
|
39
79
|
const defaultIconSize = size === 'sm' ? 26 : size === 'lg' ? 38 : 32;
|
|
40
80
|
useEffect(() => {
|
|
41
|
-
if (disabled || readOnly)
|
|
42
|
-
|
|
43
|
-
}, [disabled, readOnly]);
|
|
81
|
+
if (disabled || readOnly || !hoverPreview)
|
|
82
|
+
changePreview(null);
|
|
83
|
+
}, [disabled, readOnly, hoverPreview, changePreview]);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
clearPendingKeyboard();
|
|
86
|
+
changePreview(null);
|
|
87
|
+
}, [count, step, changePreview, clearPendingKeyboard]);
|
|
88
|
+
useEffect(() => clearPendingKeyboard, [clearPendingKeyboard]);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const owner = inputRef.current?.form;
|
|
91
|
+
if (!owner)
|
|
92
|
+
return;
|
|
93
|
+
let resetTimer;
|
|
94
|
+
const reset = (event) => {
|
|
95
|
+
clearTimeout(resetTimer);
|
|
96
|
+
resetTimer = setTimeout(() => {
|
|
97
|
+
if (event.defaultPrevented)
|
|
98
|
+
return;
|
|
99
|
+
clearPendingKeyboard();
|
|
100
|
+
changePreview(null);
|
|
101
|
+
const state = resetState.current;
|
|
102
|
+
if (!state.controlled)
|
|
103
|
+
setUncontrolledValue(normalizeRatingValue(state.defaultValue, state.count, state.step));
|
|
104
|
+
}, 0);
|
|
105
|
+
};
|
|
106
|
+
owner.addEventListener('reset', reset);
|
|
107
|
+
return () => { clearTimeout(resetTimer); owner.removeEventListener('reset', reset); };
|
|
108
|
+
}, [form, changePreview, clearPendingKeyboard]);
|
|
44
109
|
const commit = (nextValue) => {
|
|
45
110
|
if (disabled || readOnly)
|
|
46
111
|
return;
|
|
112
|
+
clearPendingKeyboard();
|
|
47
113
|
const normalized = normalizeRatingValue(nextValue, count, step);
|
|
48
114
|
const cleared = normalized === committedValue && normalized !== 0 && allowsReselectClear(clearable);
|
|
49
115
|
const resolved = cleared ? 0 : normalized;
|
|
50
|
-
|
|
116
|
+
changePreview(null);
|
|
51
117
|
if (resolved === committedValue)
|
|
52
118
|
return;
|
|
53
119
|
if (!controlled)
|
|
@@ -63,6 +129,30 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
63
129
|
const rawValue = index + Math.min(1, Math.max(0, logicalFraction));
|
|
64
130
|
return normalizeRatingValue(Math.max(step, Math.ceil((rawValue - Number.EPSILON) / step) * step), count, step);
|
|
65
131
|
};
|
|
132
|
+
const revealValue = useCallback((nextValue) => {
|
|
133
|
+
const scroller = symbolsRef.current;
|
|
134
|
+
if (!scroller)
|
|
135
|
+
return;
|
|
136
|
+
const targetIndex = Math.max(1, Math.ceil(normalizeRatingValue(nextValue, count, step)));
|
|
137
|
+
const target = scroller.querySelector(`[data-index="${targetIndex}"]`);
|
|
138
|
+
if (!target)
|
|
139
|
+
return;
|
|
140
|
+
const viewport = scroller.getBoundingClientRect();
|
|
141
|
+
const item = target.getBoundingClientRect();
|
|
142
|
+
const delta = item.left < viewport.left ? item.left - viewport.left
|
|
143
|
+
: item.right > viewport.right ? item.right - viewport.right
|
|
144
|
+
: 0;
|
|
145
|
+
if (delta)
|
|
146
|
+
scroller.scrollLeft += delta;
|
|
147
|
+
}, [count, step]);
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
const pending = pendingKeyboardValueRef.current;
|
|
150
|
+
if (pending === null)
|
|
151
|
+
return;
|
|
152
|
+
clearPendingKeyboard();
|
|
153
|
+
if (pending === committedValue)
|
|
154
|
+
revealValue(committedValue);
|
|
155
|
+
}, [committedValue, revealValue, clearPendingKeyboard]);
|
|
66
156
|
const handleKeyDown = (event) => {
|
|
67
157
|
onKeyDown?.(event);
|
|
68
158
|
if (event.defaultPrevented || disabled || readOnly)
|
|
@@ -72,11 +162,15 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
72
162
|
if (event.key === 'ArrowUp')
|
|
73
163
|
nextValue = committedValue + step;
|
|
74
164
|
if (event.key === 'ArrowDown')
|
|
75
|
-
nextValue = committedValue
|
|
165
|
+
nextValue = previousRatingValue(committedValue, count, step);
|
|
76
166
|
if (event.key === 'ArrowRight')
|
|
77
|
-
nextValue =
|
|
167
|
+
nextValue = rtl
|
|
168
|
+
? previousRatingValue(committedValue, count, step)
|
|
169
|
+
: committedValue + step;
|
|
78
170
|
if (event.key === 'ArrowLeft')
|
|
79
|
-
nextValue =
|
|
171
|
+
nextValue = rtl
|
|
172
|
+
? committedValue + step
|
|
173
|
+
: previousRatingValue(committedValue, count, step);
|
|
80
174
|
if (event.key === 'Home')
|
|
81
175
|
nextValue = allowsKeyboardClear(clearable) ? 0 : Math.min(step, count);
|
|
82
176
|
if (event.key === 'End')
|
|
@@ -86,13 +180,19 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
86
180
|
if (nextValue === null)
|
|
87
181
|
return;
|
|
88
182
|
event.preventDefault();
|
|
89
|
-
|
|
183
|
+
changePreview(null);
|
|
90
184
|
let normalized = normalizeRatingValue(nextValue, count, step);
|
|
91
185
|
if (normalized === 0 && committedValue > 0 && !allowsKeyboardClear(clearable)) {
|
|
92
186
|
normalized = Math.min(step, count);
|
|
93
187
|
}
|
|
94
188
|
if (normalized === committedValue)
|
|
95
189
|
return;
|
|
190
|
+
clearPendingKeyboard();
|
|
191
|
+
pendingKeyboardValueRef.current = normalized;
|
|
192
|
+
pendingKeyboardTimerRef.current = setTimeout(() => {
|
|
193
|
+
pendingKeyboardTimerRef.current = undefined;
|
|
194
|
+
pendingKeyboardValueRef.current = null;
|
|
195
|
+
}, 100);
|
|
96
196
|
if (!controlled)
|
|
97
197
|
setUncontrolledValue(normalized);
|
|
98
198
|
onValueChange?.(normalized);
|
|
@@ -102,7 +202,7 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
102
202
|
index: index + 1,
|
|
103
203
|
fraction,
|
|
104
204
|
filled,
|
|
105
|
-
previewing
|
|
205
|
+
previewing,
|
|
106
206
|
};
|
|
107
207
|
if (renderSymbol)
|
|
108
208
|
return renderSymbol(state);
|
|
@@ -110,18 +210,25 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
110
210
|
return symbol;
|
|
111
211
|
return _jsx(PencilIcon, { paths: defaultSymbolPaths, sourceSize: 24, size: defaultIconSize, active: filled, seed: `${symbolId}-${index}` });
|
|
112
212
|
};
|
|
113
|
-
return (
|
|
114
|
-
|
|
213
|
+
return (_jsxs("div", { ...props, "aria-disabled": disabled || undefined, "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : t('rating.label')), "aria-labelledby": ariaLabelledBy, "aria-readonly": readOnly || undefined, "aria-valuemax": count, "aria-valuemin": 0, "aria-valuenow": committedValue, "aria-valuetext": getValueLabel(committedValue, count), className: ['inkra-rating', className].filter(Boolean).join(' '), "data-disabled": disabled || undefined, "data-previewing": previewing || undefined, "data-readonly": readOnly || undefined, "data-size": size, dir: dir, onBlur: (event) => {
|
|
214
|
+
clearPendingKeyboard();
|
|
215
|
+
changePreview(null);
|
|
115
216
|
onBlur?.(event);
|
|
116
|
-
}, onKeyDown: handleKeyDown,
|
|
117
|
-
|
|
217
|
+
}, onKeyDown: handleKeyDown, onPointerDown: (event) => {
|
|
218
|
+
onPointerDown?.(event);
|
|
219
|
+
if (!event.defaultPrevented && !disabled) {
|
|
220
|
+
clearPendingKeyboard();
|
|
221
|
+
rootRef.current?.focus({ preventScroll: true });
|
|
222
|
+
}
|
|
223
|
+
}, onPointerLeave: (event) => {
|
|
224
|
+
changePreview(null);
|
|
118
225
|
onPointerLeave?.(event);
|
|
119
|
-
}, ref:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
226
|
+
}, ref: rootRef, role: "slider", tabIndex: disabled ? undefined : (props.tabIndex ?? 0), children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-rating__focus-ring", variant: "outline", seed: `${symbolId}-focus` }), _jsx("input", { ref: inputRef, type: "hidden", name: name, form: form, value: committedValue, disabled: disabled }), _jsx("span", { className: "inkra-rating__symbols", "aria-hidden": "true", ref: symbolsRef, children: symbolIndexes.map((index) => {
|
|
227
|
+
const fraction = Math.min(1, Math.max(0, displayValue - index));
|
|
228
|
+
return (_jsxs("span", { className: "inkra-rating__symbol-target", "data-index": index + 1, onClick: (event) => commit(valueFromPointer(event, index)), onPointerMove: (event) => {
|
|
229
|
+
if (hoverPreview && event.pointerType === 'mouse' && !disabled && !readOnly) {
|
|
230
|
+
changePreview(valueFromPointer(event, index));
|
|
231
|
+
}
|
|
232
|
+
}, children: [_jsx("span", { className: "inkra-rating__symbol inkra-rating__symbol--empty", children: renderLayerSymbol(index, 0, false) }), _jsx("span", { className: "inkra-rating__fill", style: { inlineSize: `${fraction * 100}%` }, children: _jsx("span", { className: "inkra-rating__symbol inkra-rating__symbol--filled", children: renderLayerSymbol(index, fraction, true) }) })] }, index));
|
|
233
|
+
}) })] }));
|
|
127
234
|
});
|
|
@@ -23,7 +23,7 @@ export interface ResizableHandleProps extends SeparatorProps {
|
|
|
23
23
|
label?: string;
|
|
24
24
|
lineSeed?: string | number;
|
|
25
25
|
}
|
|
26
|
-
declare function ResizableHandle(
|
|
26
|
+
declare function ResizableHandle(inkraProps: ResizableHandleProps): import("react").JSX.Element;
|
|
27
27
|
export declare const Resizable: Readonly<{
|
|
28
28
|
Root: typeof ResizableRoot;
|
|
29
29
|
Panel: typeof ResizablePanel;
|
package/dist/resizable/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import { Group as ResizableGroupPrimitive, Panel as ResizablePanelPrimitive, Separator as ResizableSeparatorPrimitive, useGroupCallbackRef, useGroupRef, usePanelCallbackRef, usePanelRef, } from 'react-resizable-panels';
|
|
3
4
|
import { useCallback, useReducer } from 'react';
|
|
4
5
|
import { PencilLine } from '../PencilLine.js';
|
|
@@ -39,10 +40,12 @@ function ResizableRoot({ apiRef, className, defaultLayout, layout, onLayoutChang
|
|
|
39
40
|
function ResizablePanel({ className, ...props }) {
|
|
40
41
|
return _jsx(ResizablePanelPrimitive, { ...props, className: classes('inkra-resizable__panel', className) });
|
|
41
42
|
}
|
|
42
|
-
function ResizableHandle(
|
|
43
|
+
function ResizableHandle(inkraProps) {
|
|
44
|
+
const { t } = useInkraLocale();
|
|
45
|
+
const { 'aria-label': ariaLabel, children, className, label = t('resizable.label'), lineSeed = 'inkra-resizable-handle', ...props } = inkraProps;
|
|
43
46
|
return _jsxs(ResizableSeparatorPrimitive, { ...props, "aria-label": ariaLabel ?? label, className: classes('inkra-resizable__handle', className), children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-resizable__handle-focus", seed: `${lineSeed}-focus`, variant: "outline" }), _jsxs("span", { "aria-hidden": "true", className: "inkra-resizable__handle-mark", children: [_jsx(PencilLine, { className: "inkra-resizable__handle-line inkra-resizable__handle-line--horizontal", seed: `${lineSeed}-horizontal` }), _jsx(PencilLine, { className: "inkra-resizable__handle-line inkra-resizable__handle-line--vertical", orientation: "vertical", seed: `${lineSeed}-vertical` }), children] })] });
|
|
44
47
|
}
|
|
45
|
-
export const Resizable = Object.freeze({
|
|
48
|
+
export const Resizable = /* @__PURE__ */ Object.freeze({
|
|
46
49
|
Root: ResizableRoot,
|
|
47
50
|
Panel: ResizablePanel,
|
|
48
51
|
Handle: ResizableHandle,
|
|
@@ -13,7 +13,7 @@ export interface ScrollAreaProps extends Omit<HTMLAttributes<HTMLDivElement>, 'c
|
|
|
13
13
|
viewportLabel?: string;
|
|
14
14
|
viewportProps?: Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'role'>;
|
|
15
15
|
}
|
|
16
|
-
export type ScrollAreaRootProps =
|
|
16
|
+
export type ScrollAreaRootProps = ScrollAreaProps;
|
|
17
17
|
export type ScrollAreaViewportProps = PrimitiveViewportProps;
|
|
18
18
|
export type ScrollAreaScrollbarProps = PrimitiveScrollbarProps;
|
|
19
19
|
export type ScrollAreaThumbProps = PrimitiveThumbProps;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DirectionProvider, useDirection } from '@base-ui/react/direction-provider';
|
|
2
3
|
import { ScrollArea as ScrollAreaPrimitive } from '@base-ui/react/scroll-area';
|
|
3
4
|
import { forwardRef, useId } from 'react';
|
|
4
5
|
import { PencilSurface } from '../PencilSurface.js';
|
|
@@ -10,13 +11,15 @@ function withClassName(base, value) {
|
|
|
10
11
|
? (state) => classes(base, value(state))
|
|
11
12
|
: classes(base, value);
|
|
12
13
|
}
|
|
13
|
-
const ScrollAreaRoot = forwardRef(function ScrollAreaRoot({ 'aria-label': ariaLabel, children, className, overflowEdgeThreshold, seed, style, type = 'auto', viewportLabel, viewportProps, ...props }, ref) {
|
|
14
|
+
const ScrollAreaRoot = forwardRef(function ScrollAreaRoot({ 'aria-label': ariaLabel, children, className, dir, overflowEdgeThreshold, seed, style, type = 'auto', viewportLabel, viewportProps, ...props }, ref) {
|
|
15
|
+
const inheritedDirection = useDirection();
|
|
16
|
+
const resolvedDirection = dir === 'rtl' || dir === 'ltr' ? dir : inheritedDirection;
|
|
14
17
|
const generatedId = useId();
|
|
15
18
|
const resolvedSeed = seed ?? `inkra-scroll-area-${generatedId}`;
|
|
16
19
|
const viewportClassName = classes('inkra-scroll-area__viewport', viewportProps?.className);
|
|
17
20
|
const viewportStyle = viewportProps?.style;
|
|
18
21
|
const viewportLabelValue = viewportProps?.['aria-label'] ?? viewportLabel ?? ariaLabel;
|
|
19
|
-
return _jsxs(ScrollAreaPrimitive.Root, { ...props, ...(ariaLabel ? { 'aria-label': ariaLabel } : {}), className: classes('inkra-scroll-area', className), "data-type": type, overflowEdgeThreshold: overflowEdgeThreshold, ref: ref, style: style, children: [_jsx(ScrollAreaPrimitive.Viewport, { ...viewportProps, "aria-label": viewportLabelValue, className: viewportClassName, style: viewportStyle, children: children }), _jsxs(ScrollAreaPrimitive.Scrollbar, { className: "inkra-scroll-area__scrollbar", orientation: "vertical", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-scroll-area__track", radius: 3, seed: `${resolvedSeed}-vertical-track`, variant: "quiet" }), _jsx(ScrollAreaPrimitive.Thumb, { className: "inkra-scroll-area__thumb", children: _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-scroll-area__thumb-surface", radius: 3, seed: `${resolvedSeed}-vertical-thumb`, variant: "
|
|
22
|
+
return _jsx(DirectionProvider, { direction: resolvedDirection, children: _jsxs(ScrollAreaPrimitive.Root, { ...props, ...(ariaLabel ? { 'aria-label': ariaLabel } : {}), className: classes('inkra-scroll-area', className), "data-type": type, dir: dir, overflowEdgeThreshold: overflowEdgeThreshold, ref: ref, style: style, children: [_jsx(ScrollAreaPrimitive.Viewport, { ...viewportProps, "aria-label": viewportLabelValue, className: viewportClassName, style: viewportStyle, children: children }), _jsxs(ScrollAreaPrimitive.Scrollbar, { className: "inkra-scroll-area__scrollbar", orientation: "vertical", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-scroll-area__track", radius: 3, seed: `${resolvedSeed}-vertical-track`, variant: "quiet" }), _jsx(ScrollAreaPrimitive.Thumb, { className: "inkra-scroll-area__thumb", children: _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-scroll-area__thumb-surface", radius: 3, seed: `${resolvedSeed}-vertical-thumb`, variant: "quiet" }) })] }), _jsxs(ScrollAreaPrimitive.Scrollbar, { className: "inkra-scroll-area__scrollbar", orientation: "horizontal", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-scroll-area__track", radius: 3, seed: `${resolvedSeed}-horizontal-track`, variant: "quiet" }), _jsx(ScrollAreaPrimitive.Thumb, { className: "inkra-scroll-area__thumb", children: _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-scroll-area__thumb-surface", radius: 3, seed: `${resolvedSeed}-horizontal-thumb`, variant: "quiet" }) })] }), _jsx(ScrollAreaPrimitive.Corner, { className: "inkra-scroll-area__corner" })] }) });
|
|
20
23
|
});
|
|
21
24
|
const Viewport = forwardRef(function Viewport({ className, ...props }, ref) {
|
|
22
25
|
return _jsx(ScrollAreaPrimitive.Viewport, { ...props, className: withClassName('inkra-scroll-area__viewport', className), ref: ref });
|
|
@@ -15,13 +15,14 @@ export function MultipleSelectRoot({ value, defaultValue = emptyValues, onValueC
|
|
|
15
15
|
const isControlled = value !== undefined;
|
|
16
16
|
const contentEvents = useRef({});
|
|
17
17
|
const labels = useMemo(() => new Map(items?.map(item => [item.value, item.label])), [items]);
|
|
18
|
-
const change = (next) => {
|
|
18
|
+
const change = useCallback((next) => {
|
|
19
19
|
if (disabled || readOnly)
|
|
20
20
|
return;
|
|
21
|
-
if (
|
|
21
|
+
if (!isControlled)
|
|
22
22
|
setUncontrolled(next);
|
|
23
23
|
onValueChange?.(next);
|
|
24
|
-
};
|
|
24
|
+
}, [disabled, readOnly, isControlled, onValueChange]);
|
|
25
|
+
const context = useMemo(() => ({ values, labels, disabled, readOnly, change, triggerRef, contentEvents }), [values, labels, disabled, readOnly, change]);
|
|
25
26
|
useEffect(() => {
|
|
26
27
|
if (isControlled)
|
|
27
28
|
return;
|
|
@@ -37,7 +38,7 @@ export function MultipleSelectRoot({ value, defaultValue = emptyValues, onValueC
|
|
|
37
38
|
form?.addEventListener('reset', reset);
|
|
38
39
|
return () => { clearTimeout(timer); form?.removeEventListener('reset', reset); };
|
|
39
40
|
}, [isControlled, props.form]);
|
|
40
|
-
return (_jsx(MultipleSelectContext.Provider, { value:
|
|
41
|
+
return (_jsx(MultipleSelectContext.Provider, { value: context, children: _jsx(DirectionProvider, { direction: dir, children: _jsx(BaseSelect.Root, { ...props, multiple: true, disabled: disabled, readOnly: readOnly, open: readOnly ? false : props.open, value: values, onValueChange: change, items: items, inputRef: inputRef, onOpenChange: (open, details) => {
|
|
41
42
|
if (readOnly && open)
|
|
42
43
|
details.cancel();
|
|
43
44
|
if (details.reason === 'outside-press' || details.reason === 'cancel-open') {
|