@inkra/react 0.3.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -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 +126 -31
- package/dist/calendar/index.d.ts +4 -1
- package/dist/calendar/index.js +31 -12
- package/dist/carousel/index.js +13 -6
- package/dist/choice-grid/index.d.ts +6 -1
- package/dist/choice-grid/index.js +61 -33
- package/dist/color-picker/GradientEditor.d.ts +39 -0
- package/dist/color-picker/GradientEditor.js +63 -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 +134 -32
- 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 +12 -3
- package/dist/context-menu/index.js +1 -1
- package/dist/data-table/index.d.ts +7 -1
- package/dist/data-table/index.js +64 -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 +10 -9
- package/dist/day-agenda/index.d.ts +6 -1
- package/dist/day-agenda/index.js +46 -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 +5 -5
- package/dist/drawer/index.js +4 -3
- package/dist/editable/index.d.ts +24 -2
- package/dist/editable/index.js +142 -37
- 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/Textarea.d.ts +3 -1
- package/dist/form/Textarea.js +6 -4
- package/dist/hotkey/index.d.ts +1 -0
- package/dist/hotkey/index.js +6 -4
- package/dist/hover-card/index.js +1 -1
- package/dist/image-cropper/index.js +4 -2
- package/dist/image-viewer/VideoResource.d.ts +21 -0
- package/dist/image-viewer/VideoResource.js +194 -0
- package/dist/image-viewer/index.d.ts +14 -0
- package/dist/image-viewer/index.js +118 -65
- package/dist/image-viewer/lucideGeometry.d.ts +3 -0
- package/dist/image-viewer/lucideGeometry.js +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/input-number/index.js +9 -4
- 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 +2 -0
- package/dist/internal/textRenderer.js +3 -3
- package/dist/internal/usePencilSurface.js +17 -1
- package/dist/internal/wash.js +4 -3
- package/dist/journey-map/terrain.js +10 -5
- package/dist/layout/index.js +1 -1
- package/dist/log/index.js +29 -22
- 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 +175 -0
- package/dist/node-graph/index.d.ts +2 -0
- package/dist/node-graph/index.js +49 -25
- package/dist/number-ticker/precision.js +9 -2
- package/dist/overflow-list/index.js +5 -2
- package/dist/overlay/Overlay.js +1 -1
- 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 +6 -4
- package/dist/pan-zoom/index.d.ts +2 -0
- package/dist/pan-zoom/index.js +7 -6
- 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 +40 -20
- package/dist/rating/index.d.ts +4 -0
- package/dist/rating/index.js +128 -24
- package/dist/resizable/index.js +1 -1
- 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 +61 -6
- package/dist/select/Select.d.ts +1 -0
- package/dist/select/Select.js +3 -1
- package/dist/select/StepperSelect.d.ts +29 -0
- package/dist/select/StepperSelect.js +119 -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 +6 -2
- package/dist/sparkline/index.js +17 -2
- package/dist/sparkline/model.js +11 -4
- 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 +557 -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 +2094 -421
- package/dist/tabs/index.d.ts +9 -0
- package/dist/tabs/index.js +66 -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 +70 -26
- package/dist/time-picker/index.js +38 -20
- package/dist/toast/index.d.ts +19 -11
- package/dist/toast/index.js +48 -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 +93 -29
- 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 +5 -5
- package/dist/tree-select/index.js +67 -30
- 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 +30 -16
- package/dist/virtual-list/index.js +7 -5
- package/package.json +413 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HTMLAttributes } from 'react';
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
2
|
export interface PaginationLabels {
|
|
3
3
|
nav: string;
|
|
4
4
|
first: string;
|
|
@@ -16,6 +16,15 @@ export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'chil
|
|
|
16
16
|
siblingCount?: number;
|
|
17
17
|
boundaryCount?: number;
|
|
18
18
|
showFirstLast?: boolean;
|
|
19
|
+
showPreviousNext?: boolean;
|
|
20
|
+
hideOnSinglePage?: boolean;
|
|
21
|
+
simple?: boolean;
|
|
22
|
+
/** Non-interactive content inside each numbered page button. */
|
|
23
|
+
renderPage?: (page: number, state: {
|
|
24
|
+
isCurrent: boolean;
|
|
25
|
+
}) => ReactNode;
|
|
26
|
+
/** Non-interactive current/total content in simple mode. */
|
|
27
|
+
renderSummary?: (page: number, totalPages: number) => ReactNode;
|
|
19
28
|
disabled?: boolean;
|
|
20
29
|
size?: 'sm' | 'md' | 'lg';
|
|
21
30
|
labels?: Partial<PaginationLabels>;
|
package/dist/pagination/index.js
CHANGED
|
@@ -9,7 +9,7 @@ const defaultLabels = {
|
|
|
9
9
|
nav: 'Pagination', first: 'First page', previous: 'Previous page', next: 'Next page', last: 'Last page',
|
|
10
10
|
page: page => `Page ${page}`, ellipsis: 'More pages',
|
|
11
11
|
};
|
|
12
|
-
export const Pagination = forwardRef(function Pagination({ totalPages, page, defaultPage = 1, onPageChange, siblingCount = 1, boundaryCount = 1, showFirstLast = false, disabled = false, size = 'md', labels, className, ...props }, ref) {
|
|
12
|
+
export const Pagination = forwardRef(function Pagination({ totalPages, page, defaultPage = 1, onPageChange, siblingCount = 1, boundaryCount = 1, showFirstLast = false, showPreviousNext = true, hideOnSinglePage = false, simple = false, renderPage, renderSummary, disabled = false, size = 'md', labels, className, ...props }, ref) {
|
|
13
13
|
const total = normalizeTotal(totalPages);
|
|
14
14
|
const [internalPage, setInternalPage] = useState(() => clampPage(defaultPage, total));
|
|
15
15
|
const current = clampPage(page ?? internalPage, total);
|
|
@@ -35,7 +35,9 @@ export const Pagination = forwardRef(function Pagination({ totalPages, page, def
|
|
|
35
35
|
paths.push({ d: 'M19 6v12' });
|
|
36
36
|
return _jsx("li", { children: _jsx(Button, { variant: "ghost", size: size === 'md' ? 'icon' : `icon-${size}`, "aria-label": text[kind], title: text[kind], disabled: disabled || unavailable, onClick: () => select(target), children: _jsx(PencilIcon, { className: "inkra-pagination__arrow", paths: paths, size: 20, sourceSize: 24 }) }) }, kind);
|
|
37
37
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
if (hideOnSinglePage && total <= 1)
|
|
39
|
+
return null;
|
|
40
|
+
return _jsx("nav", { "aria-label": text.nav, ...props, ref: ref, className: ['inkra-pagination', className].filter(Boolean).join(' '), "data-empty": !total || undefined, "data-simple": simple || undefined, children: _jsxs("ul", { className: "inkra-pagination__list", children: [showFirstLast && arrow('first', 1, current <= 1), showPreviousNext && arrow('previous', current - 1, current <= 1), simple ? _jsx("li", { className: "inkra-pagination__summary", "aria-live": "polite", "aria-atomic": "true", children: renderSummary ? renderSummary(current, total) : _jsx(PencilText, { children: `${current} / ${total}` }) }) : getPaginationEntries(total, current, siblingCount, boundaryCount).map(entry => typeof entry === 'number'
|
|
41
|
+
? _jsx("li", { className: "inkra-pagination__page-item", children: _jsxs(Button, { size: size, variant: "ghost", className: "inkra-pagination__page", "aria-label": text.page(entry), "aria-current": entry === current ? 'page' : undefined, disabled: disabled, onClick: () => select(entry), children: [entry === current && _jsx(PencilSurface, { as: "span", "aria-hidden": "true", variant: "selection", className: "inkra-pagination__selection", seed: `page-${entry}` }), renderPage ? renderPage(entry, { isCurrent: entry === current }) : _jsx(PencilText, { children: String(entry) })] }) }, entry)
|
|
42
|
+
: _jsxs("li", { className: "inkra-pagination__ellipsis", children: [_jsx("span", { className: "inkra-pagination__sr-only", children: text.ellipsis }), _jsx(PencilText, { "aria-hidden": "true", children: "..." })] }, entry)), showPreviousNext && arrow('next', current + 1, !total || current >= total), showFirstLast && arrow('last', total, !total || current >= total)] }) });
|
|
41
43
|
});
|
package/dist/pan-zoom/index.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface PanZoomProps extends Omit<HTMLAttributes<HTMLDivElement>, 'chil
|
|
|
48
48
|
minScale?: number;
|
|
49
49
|
maxScale?: number;
|
|
50
50
|
wheel?: PanZoomWheelMode;
|
|
51
|
+
/** Scale increment per wheel delta unit. Must be finite and positive. */
|
|
52
|
+
wheelStep?: number;
|
|
51
53
|
pinch?: boolean;
|
|
52
54
|
pan?: boolean;
|
|
53
55
|
keyboard?: boolean;
|
package/dist/pan-zoom/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
|
|
3
|
-
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef,
|
|
3
|
+
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef, } from 'react';
|
|
4
4
|
import { PencilSurface } from '../PencilSurface.js';
|
|
5
5
|
const defaultTransform = { x: 0, y: 0, scale: 1 };
|
|
6
6
|
const interactionExclusions = [
|
|
@@ -26,16 +26,18 @@ function transformsEqual(left, right) {
|
|
|
26
26
|
function prefersReducedMotion(element) {
|
|
27
27
|
return element?.ownerDocument.defaultView?.matchMedia('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
28
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) {
|
|
29
|
+
function PanZoomRoot({ 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 = 'Pan and zoom content', ...props }, forwardedRef) {
|
|
30
30
|
const resolvedMinScale = Math.max(Number.isFinite(minScale) ? minScale : 0.25, 0.0001);
|
|
31
31
|
const resolvedMaxScale = Math.max(Number.isFinite(maxScale) ? maxScale : 8, resolvedMinScale);
|
|
32
|
+
const resolvedWheelStep = Number.isFinite(wheelStep) && wheelStep > 0 ? wheelStep : 0.001;
|
|
32
33
|
const controlled = value !== undefined;
|
|
33
34
|
const initialTransform = normalizedTransform(value ?? defaultValue, resolvedMinScale, resolvedMaxScale);
|
|
34
|
-
const [uncontrolledTransform, setUncontrolledTransform] = useState(initialTransform);
|
|
35
|
-
const visibleTransform = normalizedTransform(value ?? uncontrolledTransform, resolvedMinScale, resolvedMaxScale);
|
|
36
35
|
const engineRef = useRef(null);
|
|
37
36
|
const instructionsId = useId();
|
|
37
|
+
// The engine owns the live DOM transform. Keep its snapshot for imperative reads,
|
|
38
|
+
// rather than rerendering the complete content subtree on every gesture frame.
|
|
38
39
|
const committedRef = useRef(initialTransform);
|
|
40
|
+
const visibleTransform = normalizedTransform(value ?? committedRef.current, resolvedMinScale, resolvedMaxScale);
|
|
39
41
|
const programmaticRef = useRef(false);
|
|
40
42
|
const pointerBlocksPanRef = useRef(false);
|
|
41
43
|
const wheelTargetBlocksZoomRef = useRef(false);
|
|
@@ -132,6 +134,7 @@ function PanZoomRoot({ children, value, defaultValue = defaultTransform, onTrans
|
|
|
132
134
|
excluded: interactionExclusions,
|
|
133
135
|
activationKeys: () => !pointerBlocksPanRef.current,
|
|
134
136
|
}, pinch: { disabled: userInteractionDisabled || !pinch, excluded: interactionExclusions }, wheel: {
|
|
137
|
+
step: resolvedWheelStep,
|
|
135
138
|
disabled: userInteractionDisabled || wheel === 'disabled',
|
|
136
139
|
activationKeys: wheelActivation,
|
|
137
140
|
excluded: interactionExclusions,
|
|
@@ -144,8 +147,6 @@ function PanZoomRoot({ children, value, defaultValue = defaultTransform, onTrans
|
|
|
144
147
|
}, 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
148
|
const next = normalizedTransform({ x: state.positionX, y: state.positionY, scale: state.scale }, resolvedMinScale, resolvedMaxScale);
|
|
146
149
|
committedRef.current = next;
|
|
147
|
-
if (!controlled)
|
|
148
|
-
setUncontrolledTransform(next);
|
|
149
150
|
onTransformChange?.(next, { reason: programmaticRef.current ? 'programmatic' : 'gesture' });
|
|
150
151
|
}, 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
152
|
...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
|
@@ -87,10 +87,13 @@ function RadarInk({ axisAngles, axes, levels, radius, center, seriesPoints, }) {
|
|
|
87
87
|
const valid = points.filter(point => point.value != null).map(point => coordinate(point.angle, point.radius));
|
|
88
88
|
if (valid.length < 2)
|
|
89
89
|
return;
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
91
|
+
for (const [x, y] of valid) {
|
|
92
|
+
minX = Math.min(minX, x);
|
|
93
|
+
maxX = Math.max(maxX, x);
|
|
94
|
+
minY = Math.min(minY, y);
|
|
95
|
+
maxY = Math.max(maxY, y);
|
|
96
|
+
}
|
|
94
97
|
context.save();
|
|
95
98
|
context.beginPath();
|
|
96
99
|
valid.forEach(([x, y], pointIndex) => pointIndex ? context.lineTo(x, y) : context.moveTo(x, y));
|
|
@@ -139,6 +142,7 @@ function RadarInk({ axisAngles, axes, levels, radius, center, seriesPoints, }) {
|
|
|
139
142
|
}, [axes, axisAngles, center, levels, radius, seriesPoints]);
|
|
140
143
|
return _jsx("canvas", { "aria-hidden": "true", className: "inkra-radar__ink", "data-ready": ready || undefined, ref: canvasRef });
|
|
141
144
|
}
|
|
145
|
+
const noAxes = [];
|
|
142
146
|
const palette = ['#4b96aa', '#994f3f', '#629568', '#8d6b9c', '#c58b47'];
|
|
143
147
|
function keyPart(value) { return JSON.stringify([typeof value, value]); }
|
|
144
148
|
function sameKey(a, b) { return a != null && b != null && a === b; }
|
|
@@ -156,8 +160,14 @@ function textNode(value, seed) {
|
|
|
156
160
|
return typeof value === 'string' || typeof value === 'number' ? _jsx(PencilText, { seed: seed, children: String(value) }) : value;
|
|
157
161
|
}
|
|
158
162
|
function domainForAxis(axis, values) {
|
|
159
|
-
let
|
|
160
|
-
let
|
|
163
|
+
let minimum = values.length ? values[0] : 0;
|
|
164
|
+
let maximum = values.length ? values[0] : 1;
|
|
165
|
+
for (let index = 1; index < values.length; index++) {
|
|
166
|
+
minimum = Math.min(minimum, values[index]);
|
|
167
|
+
maximum = Math.max(maximum, values[index]);
|
|
168
|
+
}
|
|
169
|
+
let min = finite(axis.min) ? axis.min : minimum;
|
|
170
|
+
let max = finite(axis.max) ? axis.max : maximum;
|
|
161
171
|
if (min > max)
|
|
162
172
|
[min, max] = [max, min];
|
|
163
173
|
if (min === max) {
|
|
@@ -175,7 +185,7 @@ export const Radar = forwardRef(function Radar({ axes, series, label, descriptio
|
|
|
175
185
|
const descriptionId = description == null ? undefined : `inkra-radar-${id}-description`;
|
|
176
186
|
const [internalSelected, setInternalSelected] = useState(defaultSelectedSeries);
|
|
177
187
|
const selected = selectedSeries === undefined ? internalSelected : selectedSeries ?? null;
|
|
178
|
-
const visibleSeries = series.filter(item => !item.hidden);
|
|
188
|
+
const visibleSeries = useMemo(() => series.filter(item => !item.hidden), [series]);
|
|
179
189
|
const domains = useMemo(() => new Map(axes.map(axis => {
|
|
180
190
|
const values = visibleSeries.flatMap(item => item.data.filter(point => point.axisId === axis.id && finite(point.value)).map(point => point.value));
|
|
181
191
|
return [keyPart(axis.id), domainForAxis(axis, values)];
|
|
@@ -183,15 +193,27 @@ export const Radar = forwardRef(function Radar({ axes, series, label, descriptio
|
|
|
183
193
|
const side = 1000;
|
|
184
194
|
const radius = Math.max(120, Math.min(maxRadius ?? 390, 470));
|
|
185
195
|
const axisCount = axes.length;
|
|
186
|
-
const angles = axes.map((_, index) => (index * Math.PI * 2) / Math.max(1, axisCount));
|
|
196
|
+
const angles = useMemo(() => axes.map((_, index) => (index * Math.PI * 2) / Math.max(1, axisCount)), [axes, axisCount]);
|
|
187
197
|
const center = side / 2;
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
const scales = useMemo(() => axes.map(axis => scaleLinear()
|
|
199
|
+
.domain(domains.get(keyPart(axis.id)) ?? [0, 1]).range([0, radius]).clamp(true)), [axes, domains, radius]);
|
|
200
|
+
const seriesPoints = useMemo(() => visibleSeries.map((item, index) => {
|
|
201
|
+
// Preserve the first point for duplicate axis ids, matching Array.find.
|
|
202
|
+
const pointsByAxis = new Map();
|
|
203
|
+
for (const point of item.data) {
|
|
204
|
+
// Map uses SameValueZero, while the prior lookup used strict equality.
|
|
205
|
+
if (typeof point.axisId === 'number' && Number.isNaN(point.axisId))
|
|
206
|
+
continue;
|
|
207
|
+
if (!pointsByAxis.has(point.axisId))
|
|
208
|
+
pointsByAxis.set(point.axisId, point);
|
|
209
|
+
}
|
|
210
|
+
const points = axes.map((axis, axisIndex) => {
|
|
211
|
+
const point = pointsByAxis.get(axis.id) ?? { axisId: axis.id, value: null };
|
|
212
|
+
const value = finite(point.value) ? point.value : null;
|
|
213
|
+
return { angle: angles[axisIndex], radius: value == null ? 0 : scales[axisIndex](value), value, point, axis };
|
|
214
|
+
});
|
|
215
|
+
return { item, points, color: item.color ?? palette[index % palette.length], index };
|
|
216
|
+
}), [angles, axes, scales, visibleSeries]);
|
|
195
217
|
const line = useMemo(() => lineRadial().angle(point => point.angle).radius(point => point.radius).curve(curveLinearClosed), []);
|
|
196
218
|
const rootStyle = { ...style, '--inkra-radar-size': cssSize(size, 'min(100%, 34rem)'), '--inkra-radar-levels': Math.max(2, Math.min(8, Math.round(levels))) };
|
|
197
219
|
const selectSeries = (item) => {
|
|
@@ -205,16 +227,14 @@ export const Radar = forwardRef(function Radar({ axes, series, label, descriptio
|
|
|
205
227
|
return renderValueLabel(context);
|
|
206
228
|
return context.missing ? missingLabel : showValues ? formatNumber(context.value, context.axis, getValueLabel) : '•';
|
|
207
229
|
};
|
|
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 :
|
|
230
|
+
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
231
|
const x = 50 + Math.sin(angles[index]) * 48;
|
|
210
232
|
const y = 50 - Math.cos(angles[index]) * 48;
|
|
211
233
|
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];
|
|
234
|
+
}) }) : null] }), _jsx("div", { "aria-describedby": descriptionId, "aria-labelledby": labelId, className: "inkra-radar__summary", role: "group", children: seriesPoints.map(({ item, points, color }) => {
|
|
215
235
|
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
236
|
const context = { axis: point.axis, series: item, point: point.point, value: point.value, missing: point.value == null };
|
|
217
237
|
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
238
|
}) })] }, 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:
|
|
239
|
+
}) }), 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
240
|
});
|
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,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useEffect, useId, useMemo, useState, } from 'react';
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { PencilIcon } from '../PencilIcon.js';
|
|
4
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
4
5
|
const defaultSymbolPaths = [
|
|
5
6
|
{ d: 'M12 2.5 L21.5 12 L12 21.5 L2.5 12 Z' },
|
|
6
7
|
{ d: 'M7.5 12 L12 7.5 L16.5 12 L12 16.5 Z', mode: 'scuff' },
|
|
@@ -16,6 +17,10 @@ function decimalPlaces(value) {
|
|
|
16
17
|
}
|
|
17
18
|
export function normalizeRatingValue(value, count, step) {
|
|
18
19
|
const numeric = typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
20
|
+
if (numeric <= 0)
|
|
21
|
+
return 0;
|
|
22
|
+
if (numeric >= count)
|
|
23
|
+
return count;
|
|
19
24
|
const precision = Math.min(8, Math.max(decimalPlaces(step), decimalPlaces(count)));
|
|
20
25
|
const snapped = Math.round(numeric / step) * step;
|
|
21
26
|
return Number(Math.min(count, Math.max(0, snapped)).toFixed(precision));
|
|
@@ -26,28 +31,86 @@ function allowsReselectClear(clearable) {
|
|
|
26
31
|
function allowsKeyboardClear(clearable) {
|
|
27
32
|
return clearable === true || clearable === 'keyboard';
|
|
28
33
|
}
|
|
29
|
-
|
|
34
|
+
function previousRatingValue(value, count, step) {
|
|
35
|
+
if (value >= count) {
|
|
36
|
+
const precision = Math.min(8, Math.max(decimalPlaces(step), decimalPlaces(count)));
|
|
37
|
+
const previousIndex = Math.max(0, Math.ceil(count / step) - 1);
|
|
38
|
+
return Number((previousIndex * step).toFixed(precision));
|
|
39
|
+
}
|
|
40
|
+
return value - step;
|
|
41
|
+
}
|
|
42
|
+
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 = (nextValue, maximum) => `${nextValue} of ${maximum}`, className = '', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, onBlur, onKeyDown, onPointerDown, onPointerLeave, ...props }, forwardedRef) {
|
|
30
43
|
const count = Math.max(1, Math.floor(finitePositive(countProp, 5)));
|
|
31
44
|
const step = Math.min(count, finitePositive(stepProp, 1));
|
|
32
45
|
const controlled = value !== undefined;
|
|
33
46
|
const [uncontrolledValue, setUncontrolledValue] = useState(() => normalizeRatingValue(defaultValue, count, step));
|
|
34
47
|
const committedValue = normalizeRatingValue(controlled ? value : uncontrolledValue, count, step);
|
|
35
48
|
const [previewValue, setPreviewValue] = useState(null);
|
|
36
|
-
const
|
|
49
|
+
const previewRef = useRef(null);
|
|
50
|
+
const previewCallback = useRef(onPreviewChange);
|
|
51
|
+
previewCallback.current = onPreviewChange;
|
|
52
|
+
const resetState = useRef({ controlled, defaultValue, count, step });
|
|
53
|
+
resetState.current = { controlled, defaultValue, count, step };
|
|
54
|
+
const inputRef = useRef(null);
|
|
55
|
+
const rootRef = useRef(null);
|
|
56
|
+
const symbolsRef = useRef(null);
|
|
57
|
+
const pendingKeyboardValueRef = useRef(null);
|
|
58
|
+
const pendingKeyboardTimerRef = useRef(undefined);
|
|
59
|
+
useImperativeHandle(forwardedRef, () => rootRef.current);
|
|
60
|
+
const clearPendingKeyboard = useCallback(() => {
|
|
61
|
+
clearTimeout(pendingKeyboardTimerRef.current);
|
|
62
|
+
pendingKeyboardTimerRef.current = undefined;
|
|
63
|
+
pendingKeyboardValueRef.current = null;
|
|
64
|
+
}, []);
|
|
65
|
+
const changePreview = useCallback((next) => {
|
|
66
|
+
if (previewRef.current === next)
|
|
67
|
+
return;
|
|
68
|
+
previewRef.current = next;
|
|
69
|
+
setPreviewValue(next);
|
|
70
|
+
previewCallback.current?.(next);
|
|
71
|
+
}, []);
|
|
72
|
+
const previewing = hoverPreview && !disabled && !readOnly && previewValue !== null;
|
|
73
|
+
const displayValue = previewing ? normalizeRatingValue(previewValue, count, step) : committedValue;
|
|
37
74
|
const symbolId = useId();
|
|
38
75
|
const symbolIndexes = useMemo(() => Array.from({ length: count }, (_, index) => index), [count]);
|
|
39
76
|
const defaultIconSize = size === 'sm' ? 26 : size === 'lg' ? 38 : 32;
|
|
40
77
|
useEffect(() => {
|
|
41
|
-
if (disabled || readOnly)
|
|
42
|
-
|
|
43
|
-
}, [disabled, readOnly]);
|
|
78
|
+
if (disabled || readOnly || !hoverPreview)
|
|
79
|
+
changePreview(null);
|
|
80
|
+
}, [disabled, readOnly, hoverPreview, changePreview]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
clearPendingKeyboard();
|
|
83
|
+
changePreview(null);
|
|
84
|
+
}, [count, step, changePreview, clearPendingKeyboard]);
|
|
85
|
+
useEffect(() => clearPendingKeyboard, [clearPendingKeyboard]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const owner = inputRef.current?.form;
|
|
88
|
+
if (!owner)
|
|
89
|
+
return;
|
|
90
|
+
let resetTimer;
|
|
91
|
+
const reset = (event) => {
|
|
92
|
+
clearTimeout(resetTimer);
|
|
93
|
+
resetTimer = setTimeout(() => {
|
|
94
|
+
if (event.defaultPrevented)
|
|
95
|
+
return;
|
|
96
|
+
clearPendingKeyboard();
|
|
97
|
+
changePreview(null);
|
|
98
|
+
const state = resetState.current;
|
|
99
|
+
if (!state.controlled)
|
|
100
|
+
setUncontrolledValue(normalizeRatingValue(state.defaultValue, state.count, state.step));
|
|
101
|
+
}, 0);
|
|
102
|
+
};
|
|
103
|
+
owner.addEventListener('reset', reset);
|
|
104
|
+
return () => { clearTimeout(resetTimer); owner.removeEventListener('reset', reset); };
|
|
105
|
+
}, [form, changePreview, clearPendingKeyboard]);
|
|
44
106
|
const commit = (nextValue) => {
|
|
45
107
|
if (disabled || readOnly)
|
|
46
108
|
return;
|
|
109
|
+
clearPendingKeyboard();
|
|
47
110
|
const normalized = normalizeRatingValue(nextValue, count, step);
|
|
48
111
|
const cleared = normalized === committedValue && normalized !== 0 && allowsReselectClear(clearable);
|
|
49
112
|
const resolved = cleared ? 0 : normalized;
|
|
50
|
-
|
|
113
|
+
changePreview(null);
|
|
51
114
|
if (resolved === committedValue)
|
|
52
115
|
return;
|
|
53
116
|
if (!controlled)
|
|
@@ -63,6 +126,30 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
63
126
|
const rawValue = index + Math.min(1, Math.max(0, logicalFraction));
|
|
64
127
|
return normalizeRatingValue(Math.max(step, Math.ceil((rawValue - Number.EPSILON) / step) * step), count, step);
|
|
65
128
|
};
|
|
129
|
+
const revealValue = useCallback((nextValue) => {
|
|
130
|
+
const scroller = symbolsRef.current;
|
|
131
|
+
if (!scroller)
|
|
132
|
+
return;
|
|
133
|
+
const targetIndex = Math.max(1, Math.ceil(normalizeRatingValue(nextValue, count, step)));
|
|
134
|
+
const target = scroller.querySelector(`[data-index="${targetIndex}"]`);
|
|
135
|
+
if (!target)
|
|
136
|
+
return;
|
|
137
|
+
const viewport = scroller.getBoundingClientRect();
|
|
138
|
+
const item = target.getBoundingClientRect();
|
|
139
|
+
const delta = item.left < viewport.left ? item.left - viewport.left
|
|
140
|
+
: item.right > viewport.right ? item.right - viewport.right
|
|
141
|
+
: 0;
|
|
142
|
+
if (delta)
|
|
143
|
+
scroller.scrollLeft += delta;
|
|
144
|
+
}, [count, step]);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const pending = pendingKeyboardValueRef.current;
|
|
147
|
+
if (pending === null)
|
|
148
|
+
return;
|
|
149
|
+
clearPendingKeyboard();
|
|
150
|
+
if (pending === committedValue)
|
|
151
|
+
revealValue(committedValue);
|
|
152
|
+
}, [committedValue, revealValue, clearPendingKeyboard]);
|
|
66
153
|
const handleKeyDown = (event) => {
|
|
67
154
|
onKeyDown?.(event);
|
|
68
155
|
if (event.defaultPrevented || disabled || readOnly)
|
|
@@ -72,11 +159,15 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
72
159
|
if (event.key === 'ArrowUp')
|
|
73
160
|
nextValue = committedValue + step;
|
|
74
161
|
if (event.key === 'ArrowDown')
|
|
75
|
-
nextValue = committedValue
|
|
162
|
+
nextValue = previousRatingValue(committedValue, count, step);
|
|
76
163
|
if (event.key === 'ArrowRight')
|
|
77
|
-
nextValue =
|
|
164
|
+
nextValue = rtl
|
|
165
|
+
? previousRatingValue(committedValue, count, step)
|
|
166
|
+
: committedValue + step;
|
|
78
167
|
if (event.key === 'ArrowLeft')
|
|
79
|
-
nextValue =
|
|
168
|
+
nextValue = rtl
|
|
169
|
+
? committedValue + step
|
|
170
|
+
: previousRatingValue(committedValue, count, step);
|
|
80
171
|
if (event.key === 'Home')
|
|
81
172
|
nextValue = allowsKeyboardClear(clearable) ? 0 : Math.min(step, count);
|
|
82
173
|
if (event.key === 'End')
|
|
@@ -86,13 +177,19 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
86
177
|
if (nextValue === null)
|
|
87
178
|
return;
|
|
88
179
|
event.preventDefault();
|
|
89
|
-
|
|
180
|
+
changePreview(null);
|
|
90
181
|
let normalized = normalizeRatingValue(nextValue, count, step);
|
|
91
182
|
if (normalized === 0 && committedValue > 0 && !allowsKeyboardClear(clearable)) {
|
|
92
183
|
normalized = Math.min(step, count);
|
|
93
184
|
}
|
|
94
185
|
if (normalized === committedValue)
|
|
95
186
|
return;
|
|
187
|
+
clearPendingKeyboard();
|
|
188
|
+
pendingKeyboardValueRef.current = normalized;
|
|
189
|
+
pendingKeyboardTimerRef.current = setTimeout(() => {
|
|
190
|
+
pendingKeyboardTimerRef.current = undefined;
|
|
191
|
+
pendingKeyboardValueRef.current = null;
|
|
192
|
+
}, 100);
|
|
96
193
|
if (!controlled)
|
|
97
194
|
setUncontrolledValue(normalized);
|
|
98
195
|
onValueChange?.(normalized);
|
|
@@ -102,7 +199,7 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
102
199
|
index: index + 1,
|
|
103
200
|
fraction,
|
|
104
201
|
filled,
|
|
105
|
-
previewing
|
|
202
|
+
previewing,
|
|
106
203
|
};
|
|
107
204
|
if (renderSymbol)
|
|
108
205
|
return renderSymbol(state);
|
|
@@ -110,18 +207,25 @@ export const Rating = forwardRef(function Rating({ value, defaultValue = 0, onVa
|
|
|
110
207
|
return symbol;
|
|
111
208
|
return _jsx(PencilIcon, { paths: defaultSymbolPaths, sourceSize: 24, size: defaultIconSize, active: filled, seed: `${symbolId}-${index}` });
|
|
112
209
|
};
|
|
113
|
-
return (
|
|
114
|
-
|
|
210
|
+
return (_jsxs("div", { ...props, "aria-disabled": disabled || undefined, "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Rating'), "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) => {
|
|
211
|
+
clearPendingKeyboard();
|
|
212
|
+
changePreview(null);
|
|
115
213
|
onBlur?.(event);
|
|
116
|
-
}, onKeyDown: handleKeyDown,
|
|
117
|
-
|
|
214
|
+
}, onKeyDown: handleKeyDown, onPointerDown: (event) => {
|
|
215
|
+
onPointerDown?.(event);
|
|
216
|
+
if (!event.defaultPrevented && !disabled) {
|
|
217
|
+
clearPendingKeyboard();
|
|
218
|
+
rootRef.current?.focus({ preventScroll: true });
|
|
219
|
+
}
|
|
220
|
+
}, onPointerLeave: (event) => {
|
|
221
|
+
changePreview(null);
|
|
118
222
|
onPointerLeave?.(event);
|
|
119
|
-
}, ref:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
223
|
+
}, 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) => {
|
|
224
|
+
const fraction = Math.min(1, Math.max(0, displayValue - index));
|
|
225
|
+
return (_jsxs("span", { className: "inkra-rating__symbol-target", "data-index": index + 1, onClick: (event) => commit(valueFromPointer(event, index)), onPointerMove: (event) => {
|
|
226
|
+
if (hoverPreview && event.pointerType === 'mouse' && !disabled && !readOnly) {
|
|
227
|
+
changePreview(valueFromPointer(event, index));
|
|
228
|
+
}
|
|
229
|
+
}, 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));
|
|
230
|
+
}) })] }));
|
|
127
231
|
});
|
package/dist/resizable/index.js
CHANGED
|
@@ -42,7 +42,7 @@ function ResizablePanel({ className, ...props }) {
|
|
|
42
42
|
function ResizableHandle({ 'aria-label': ariaLabel, children, className, label = 'Resize panels', lineSeed = 'inkra-resizable-handle', ...props }) {
|
|
43
43
|
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
44
|
}
|
|
45
|
-
export const Resizable = Object.freeze({
|
|
45
|
+
export const Resizable = /* @__PURE__ */ Object.freeze({
|
|
46
46
|
Root: ResizableRoot,
|
|
47
47
|
Panel: ResizablePanel,
|
|
48
48
|
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') {
|