@inkra/react 0.8.0 → 0.10.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 +137 -2
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/Xiaolai-OFL.txt +94 -0
- package/dist/PencilIcon.d.ts +14 -1
- package/dist/PencilIcon.js +88 -52
- package/dist/PencilText.js +4 -1
- package/dist/assets/ui-hand-xiaolai-extended.woff2 +0 -0
- package/dist/assets/ui-hand-xiaolai.woff2 +0 -0
- package/dist/assets/xiaolai.css +17 -0
- package/dist/form/EditablePencilText.js +4 -2
- package/dist/guide/index.d.ts +77 -0
- package/dist/guide/index.js +379 -0
- package/dist/i18n/catalog.d.ts +1 -1
- package/dist/i18n/catalog.js +8 -0
- package/dist/image-viewer/index.d.ts +2 -0
- package/dist/image-viewer/index.js +28 -28
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/infinite-scroll/gesture.d.ts +19 -0
- package/dist/infinite-scroll/gesture.js +218 -0
- package/dist/infinite-scroll/index.d.ts +55 -0
- package/dist/infinite-scroll/index.js +543 -0
- package/dist/infinite-scroll/model.d.ts +33 -0
- package/dist/infinite-scroll/model.js +168 -0
- package/dist/internal/iconFill.d.ts +16 -0
- package/dist/internal/iconFill.js +275 -0
- package/dist/internal/surfaceRenderer.d.ts +4 -4
- package/dist/internal/surfaceRenderer.js +2 -2
- package/dist/internal/textRenderer.d.ts +1 -1
- package/dist/internal/textRenderer.js +23 -11
- package/dist/inventory-grid/index.js +13 -3
- package/dist/inventory-grid/outline.d.ts +9 -0
- package/dist/inventory-grid/outline.js +54 -0
- package/dist/loading/index.d.ts +58 -0
- package/dist/loading/index.js +188 -0
- package/dist/loading/locales.d.ts +15 -0
- package/dist/loading/locales.js +34 -0
- package/dist/loading/model.d.ts +6 -0
- package/dist/loading/model.js +29 -0
- package/dist/masonry/index.d.ts +25 -0
- package/dist/masonry/index.js +227 -0
- package/dist/masonry/layout.d.ts +35 -0
- package/dist/masonry/layout.js +67 -0
- package/dist/number-ticker/RollingValue.d.ts +6 -0
- package/dist/number-ticker/RollingValue.js +10 -0
- package/dist/number-ticker/index.d.ts +4 -0
- package/dist/number-ticker/index.js +55 -7
- package/dist/number-ticker/rolling.d.ts +35 -0
- package/dist/number-ticker/rolling.js +87 -0
- package/dist/selection/CircularSwitchVisual.d.ts +8 -0
- package/dist/selection/CircularSwitchVisual.js +82 -0
- package/dist/selection/Switch.d.ts +7 -0
- package/dist/selection/Switch.js +15 -3
- package/dist/slider/circular.d.ts +35 -0
- package/dist/slider/circular.js +196 -0
- package/dist/slider/circularMath.d.ts +10 -0
- package/dist/slider/circularMath.js +34 -0
- package/dist/slider/index.d.ts +5 -0
- package/dist/slider/index.js +5 -3
- package/dist/styles/base.css +4 -2
- package/dist/styles/breadcrumb.css +1 -1
- package/dist/styles/guide.css +202 -0
- package/dist/styles/image-viewer.css +20 -0
- package/dist/styles/infinite-scroll.css +171 -0
- package/dist/styles/inventory-grid.css +20 -8
- package/dist/styles/loading.css +348 -0
- package/dist/styles/markdown.css +1 -1
- package/dist/styles/masonry.css +68 -0
- package/dist/styles/number-ticker.css +47 -1
- package/dist/styles/selection.css +50 -0
- package/dist/styles/slider.css +65 -0
- package/dist/styles/slot.css +1 -1
- package/dist/styles.css +1180 -192
- package/dist/toast/manager.d.ts +4 -4
- package/dist/tokens.css +13 -10
- package/package.json +28 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
4
|
+
import { PencilText } from '../PencilText.js';
|
|
5
|
+
import { ProgressRing } from '../display/ProgressRing.js';
|
|
6
|
+
import { advanceArcPosition, initialArcPosition, normalizeAngle, snapCircularValue, stepCircularValue } from './circularMath.js';
|
|
7
|
+
const diameters = { sm: 128, md: 160, lg: 192 };
|
|
8
|
+
const thicknesses = { sm: 5, md: 6, lg: 8 };
|
|
9
|
+
const thumbSizes = { sm: 20, md: 24, lg: 28 };
|
|
10
|
+
export const CircularSlider = forwardRef(function CircularSlider({ value, defaultValue, onValueChange, onValueCommit, min: suppliedMin = 0, max: suppliedMax = 100, step: suppliedStep = 1, disabled = false, readOnly = false, name, form, size = 'md', diameter, startAngle = 225, sweepAngle = 270, clockwise = true, showValue = true, centerIcon, centerContent, getValueLabel, seed, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-valuetext': ariaValueText, className, style, tabIndex, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onLostPointerCapture, onKeyDown, ...props }, forwardedRef) {
|
|
11
|
+
const id = useId();
|
|
12
|
+
const rootRef = useRef(null);
|
|
13
|
+
const inputRef = useRef(null);
|
|
14
|
+
const gesture = useRef(null);
|
|
15
|
+
useImperativeHandle(forwardedRef, () => rootRef.current);
|
|
16
|
+
const min = Number.isFinite(suppliedMin) ? suppliedMin : 0;
|
|
17
|
+
const max = Number.isFinite(suppliedMax) && Number.isFinite(suppliedMax - min) ? Math.max(min, suppliedMax) : min;
|
|
18
|
+
const step = Number.isFinite(suppliedStep) && suppliedStep > 0 ? suppliedStep : 1;
|
|
19
|
+
const initialValue = useRef(defaultValue ?? min);
|
|
20
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(initialValue.current);
|
|
21
|
+
const controlled = value !== undefined;
|
|
22
|
+
const current = snapCircularValue(value ?? uncontrolledValue, min, max, step);
|
|
23
|
+
const latest = useRef({ current, controlled, min, max, step });
|
|
24
|
+
latest.current = { current, controlled, min, max, step };
|
|
25
|
+
const blocked = disabled || readOnly || min === max;
|
|
26
|
+
const resolvedDiameter = Math.min(512, Math.max(96, Number.isFinite(diameter) ? diameter : diameters[size]));
|
|
27
|
+
const start = normalizeAngle(Number.isFinite(startAngle) ? startAngle : 225);
|
|
28
|
+
const sweep = Math.min(360, Math.max(1, Number.isFinite(sweepAngle) ? sweepAngle : 270));
|
|
29
|
+
const ringDiameter = resolvedDiameter - 32;
|
|
30
|
+
// Explicitly tiny diameters scale visual weight rather than consuming the center slot.
|
|
31
|
+
const thickness = Math.min(thicknesses[size], 5 + (resolvedDiameter - 96) / 4);
|
|
32
|
+
const thumbSize = Math.min(thumbSizes[size], 16 + (resolvedDiameter - 96) / 2);
|
|
33
|
+
const radius = ringDiameter / 2 - thickness - 4;
|
|
34
|
+
const progress = max > min ? (current - min) / (max - min) : 0;
|
|
35
|
+
const angle = (start + progress * sweep * (clockwise ? 1 : -1)) * Math.PI / 180;
|
|
36
|
+
const label = getValueLabel?.(current) ?? String(current);
|
|
37
|
+
const content = centerContent === undefined ? (showValue ? label : null) : typeof centerContent === 'function' ? centerContent(current) : centerContent;
|
|
38
|
+
const hasCenterIcon = centerIcon != null && typeof centerIcon !== 'boolean';
|
|
39
|
+
const hasCenterContent = content != null && typeof content !== 'boolean';
|
|
40
|
+
// Compact icon/value stacks need more vertical room than an inscribed square.
|
|
41
|
+
const centerWidth = Math.max(16, (radius - thickness - 4) * 2) + 10;
|
|
42
|
+
const centerHeight = Math.max(20, (radius - thickness - 2) * 2);
|
|
43
|
+
const combinedCenter = hasCenterIcon && hasCenterContent;
|
|
44
|
+
const requestedIconSize = Math.min(24, Math.max(12, 16 + (resolvedDiameter - 104) / 4));
|
|
45
|
+
// Account for gap + padding, reserving a complete value line at every supported size.
|
|
46
|
+
const centerIconSize = combinedCenter ? Math.min(requestedIconSize, centerHeight - 20) : 24;
|
|
47
|
+
const centerLineHeight = Math.min(resolvedDiameter <= 112 ? 18 : 24, centerHeight - centerIconSize - 6);
|
|
48
|
+
const centerFontSize = centerLineHeight < 18 ? '12px' : centerLineHeight < 22 ? '14px' : 'var(--inkra-text-lg)';
|
|
49
|
+
const resolvedSeed = seed ?? `inkra-circular-slider-${id}`;
|
|
50
|
+
function releaseCapture(pointerId) {
|
|
51
|
+
const root = rootRef.current;
|
|
52
|
+
if (root?.hasPointerCapture?.(pointerId))
|
|
53
|
+
root.releasePointerCapture(pointerId);
|
|
54
|
+
}
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!blocked || !gesture.current)
|
|
57
|
+
return;
|
|
58
|
+
const pointerId = gesture.current.id;
|
|
59
|
+
gesture.current = null;
|
|
60
|
+
releaseCapture(pointerId);
|
|
61
|
+
}, [blocked]);
|
|
62
|
+
useEffect(() => () => {
|
|
63
|
+
const active = gesture.current;
|
|
64
|
+
gesture.current = null;
|
|
65
|
+
if (active)
|
|
66
|
+
releaseCapture(active.id);
|
|
67
|
+
}, []);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const document = inputRef.current?.ownerDocument;
|
|
70
|
+
if (!document)
|
|
71
|
+
return;
|
|
72
|
+
let timer;
|
|
73
|
+
const reset = (event) => {
|
|
74
|
+
// Resolve ownership at dispatch time, including forms moved or replaced after mount.
|
|
75
|
+
if (event.target !== inputRef.current?.form)
|
|
76
|
+
return;
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
timer = setTimeout(() => {
|
|
79
|
+
if (event.defaultPrevented)
|
|
80
|
+
return;
|
|
81
|
+
const active = gesture.current;
|
|
82
|
+
gesture.current = null;
|
|
83
|
+
if (active)
|
|
84
|
+
releaseCapture(active.id);
|
|
85
|
+
const state = latest.current;
|
|
86
|
+
const next = state.controlled ? state.current : snapCircularValue(initialValue.current, state.min, state.max, state.step);
|
|
87
|
+
if (!state.controlled)
|
|
88
|
+
setUncontrolledValue(next);
|
|
89
|
+
if (inputRef.current)
|
|
90
|
+
inputRef.current.value = String(next);
|
|
91
|
+
}, 0);
|
|
92
|
+
};
|
|
93
|
+
document.addEventListener('reset', reset, true);
|
|
94
|
+
return () => { clearTimeout(timer); document.removeEventListener('reset', reset, true); };
|
|
95
|
+
}, []);
|
|
96
|
+
function propose(next) {
|
|
97
|
+
if (!controlled)
|
|
98
|
+
setUncontrolledValue(next);
|
|
99
|
+
onValueChange?.(next);
|
|
100
|
+
}
|
|
101
|
+
function pointerPosition(clientX, clientY) {
|
|
102
|
+
const rect = rootRef.current?.getBoundingClientRect();
|
|
103
|
+
if (!rect || rect.width === 0 || rect.height === 0)
|
|
104
|
+
return null;
|
|
105
|
+
const x = (clientX - rect.left) / rect.width - 0.5;
|
|
106
|
+
const y = (clientY - rect.top) / rect.height - 0.5;
|
|
107
|
+
return { angle: normalizeAngle(Math.atan2(x, -y) * 180 / Math.PI), distance: Math.hypot(x, y) * resolvedDiameter };
|
|
108
|
+
}
|
|
109
|
+
function movePointer(clientX, clientY) {
|
|
110
|
+
const active = gesture.current;
|
|
111
|
+
const point = pointerPosition(clientX, clientY);
|
|
112
|
+
if (!active || !point || point.distance < Math.max(8, radius * 0.25))
|
|
113
|
+
return;
|
|
114
|
+
active.position = advanceArcPosition(active.position, active.angle, point.angle, sweep, clockwise);
|
|
115
|
+
active.angle = point.angle;
|
|
116
|
+
const next = snapCircularValue(min + active.position / sweep * (max - min), min, max, step);
|
|
117
|
+
if (next === active.value)
|
|
118
|
+
return;
|
|
119
|
+
active.value = next;
|
|
120
|
+
active.changed = true;
|
|
121
|
+
propose(next);
|
|
122
|
+
}
|
|
123
|
+
return _jsxs("div", { ...props, ref: rootRef, role: "slider", className: ['inkra-circular-slider', className].filter(Boolean).join(' '), "data-size": size, "data-disabled": disabled ? '' : undefined, "data-readonly": readOnly ? '' : undefined, "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Value'), "aria-labelledby": ariaLabelledBy, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": getValueLabel ? label : ariaValueText, "aria-disabled": disabled || undefined, "aria-readonly": readOnly || undefined, tabIndex: disabled ? -1 : tabIndex ?? 0, style: { '--inkra-circular-slider-diameter': `${resolvedDiameter}px`, '--inkra-circular-slider-thumb-size': `${thumbSize}px`, '--inkra-circular-slider-center-width': `${centerWidth}px`, '--inkra-circular-slider-center-height': `${centerHeight + 6}px`, '--inkra-circular-slider-center-icon-size': `${centerIconSize}px`, '--inkra-circular-slider-center-font-size': combinedCenter ? centerFontSize : 'var(--inkra-text-lg)', '--inkra-circular-slider-center-line-height': combinedCenter ? `${centerLineHeight}px` : 'var(--inkra-line-ui)', ...style }, onPointerDown: event => {
|
|
124
|
+
onPointerDown?.(event);
|
|
125
|
+
if (event.defaultPrevented || blocked || gesture.current || event.button !== 0 || event.isPrimary === false)
|
|
126
|
+
return;
|
|
127
|
+
const point = pointerPosition(event.clientX, event.clientY);
|
|
128
|
+
if (!point || point.distance < Math.max(8, radius - 22) || point.distance > radius + 22)
|
|
129
|
+
return;
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
event.currentTarget.focus({ preventScroll: true });
|
|
132
|
+
const thumbAngle = start + progress * sweep * (clockwise ? 1 : -1);
|
|
133
|
+
const delta = (point.angle - thumbAngle) * Math.PI / 180;
|
|
134
|
+
const thumbDistance = Math.sqrt(Math.max(0, point.distance ** 2 + radius ** 2 - 2 * point.distance * radius * Math.cos(delta)));
|
|
135
|
+
// The whole 44px thumb target grabs without changing the current value.
|
|
136
|
+
const position = thumbDistance <= 22 ? progress * sweep : initialArcPosition(point.angle, start, sweep, clockwise, progress * sweep);
|
|
137
|
+
const next = snapCircularValue(min + position / sweep * (max - min), min, max, step);
|
|
138
|
+
gesture.current = { id: event.pointerId, angle: point.angle, position, value: next, changed: next !== current };
|
|
139
|
+
try {
|
|
140
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
141
|
+
}
|
|
142
|
+
catch { /* Pointer capture is unavailable in some synthetic event environments. */ }
|
|
143
|
+
if (next !== current)
|
|
144
|
+
propose(next);
|
|
145
|
+
}, onPointerMove: event => {
|
|
146
|
+
onPointerMove?.(event);
|
|
147
|
+
if (event.defaultPrevented || blocked || event.pointerId !== gesture.current?.id)
|
|
148
|
+
return;
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
movePointer(event.clientX, event.clientY);
|
|
151
|
+
}, onPointerUp: event => {
|
|
152
|
+
onPointerUp?.(event);
|
|
153
|
+
const active = gesture.current;
|
|
154
|
+
if (!active || active.id !== event.pointerId)
|
|
155
|
+
return;
|
|
156
|
+
if (!event.defaultPrevented && !blocked)
|
|
157
|
+
movePointer(event.clientX, event.clientY);
|
|
158
|
+
gesture.current = null;
|
|
159
|
+
releaseCapture(event.pointerId);
|
|
160
|
+
if (!event.defaultPrevented && !blocked && active.changed)
|
|
161
|
+
onValueCommit?.(active.value);
|
|
162
|
+
}, onPointerCancel: event => {
|
|
163
|
+
onPointerCancel?.(event);
|
|
164
|
+
if (event.pointerId !== gesture.current?.id)
|
|
165
|
+
return;
|
|
166
|
+
gesture.current = null;
|
|
167
|
+
releaseCapture(event.pointerId);
|
|
168
|
+
}, onLostPointerCapture: event => {
|
|
169
|
+
onLostPointerCapture?.(event);
|
|
170
|
+
if (event.pointerId === gesture.current?.id)
|
|
171
|
+
gesture.current = null;
|
|
172
|
+
}, onKeyDown: event => {
|
|
173
|
+
onKeyDown?.(event);
|
|
174
|
+
if (event.defaultPrevented || blocked || event.altKey || event.ctrlKey || event.metaKey)
|
|
175
|
+
return;
|
|
176
|
+
let next;
|
|
177
|
+
if (event.key === 'Home')
|
|
178
|
+
next = min;
|
|
179
|
+
else if (event.key === 'End')
|
|
180
|
+
next = max;
|
|
181
|
+
else if (['ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowDown', 'PageUp', 'PageDown'].includes(event.key)) {
|
|
182
|
+
const sign = ['ArrowRight', 'ArrowUp', 'PageUp'].includes(event.key) ? 1 : -1;
|
|
183
|
+
const count = event.key.startsWith('Page') || event.shiftKey ? 10 : 1;
|
|
184
|
+
next = stepCircularValue(current, sign * count, min, max, step);
|
|
185
|
+
}
|
|
186
|
+
else
|
|
187
|
+
return;
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
if (next !== current) {
|
|
190
|
+
propose(next);
|
|
191
|
+
onValueCommit?.(next);
|
|
192
|
+
}
|
|
193
|
+
}, children: [_jsxs("span", { className: "inkra-circular-slider__arc", "aria-hidden": "true", style: { transform: `rotate(${start}deg) scaleX(${clockwise ? 1 : -1})` }, children: [_jsx("span", { className: "inkra-circular-slider__track", children: _jsx(ProgressRing, { diameter: ringDiameter, thickness: thickness, percent: sweep / 3.6, seed: `${resolvedSeed}-track` }) }), _jsx("span", { className: "inkra-circular-slider__range", children: _jsx(ProgressRing, { diameter: ringDiameter, thickness: thickness, percent: progress * sweep / 3.6, seed: `${resolvedSeed}-range` }) })] }), (hasCenterIcon || hasCenterContent) && _jsxs("div", { className: "inkra-circular-slider__center", "aria-hidden": "true", inert: true, children: [hasCenterIcon && _jsx("div", { className: "inkra-circular-slider__center-icon", children: centerIcon }), hasCenterContent && _jsx("div", { className: "inkra-circular-slider__center-content", children: typeof content === 'string' || typeof content === 'number'
|
|
194
|
+
? _jsx(PencilText, { className: "inkra-circular-slider__value", ellipsis: centerContent !== undefined, children: String(content) })
|
|
195
|
+
: content })] }), _jsx("span", { "aria-hidden": "true", className: "inkra-circular-slider__thumb", style: { left: `${50 + Math.sin(angle) * radius / resolvedDiameter * 100}%`, top: `${50 - Math.cos(angle) * radius / resolvedDiameter * 100}%` }, children: _jsx(PencilSurface, { as: "span", className: "inkra-circular-slider__thumb-surface", variant: "paper", radius: thumbSize / 2, seed: `${resolvedSeed}-thumb`, children: _jsx(PencilSurface, { as: "span", className: "inkra-circular-slider__focus-ring", variant: "outline", radius: thumbSize / 2 + 4, seed: `${resolvedSeed}-focus` }) }) }), _jsx("input", { ref: inputRef, type: "hidden", name: name, form: form, value: current, disabled: disabled })] });
|
|
196
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Clock angles: 0 points up and positive angles turn clockwise. */
|
|
2
|
+
export declare const normalizeAngle: (angle: number) => number;
|
|
3
|
+
export declare const clampCircularValue: (value: number, min: number, max: number) => number;
|
|
4
|
+
/** Snap to the grid anchored at min, retaining max as an attainable final stop. */
|
|
5
|
+
export declare function snapCircularValue(value: number, min: number, max: number, step: number): number;
|
|
6
|
+
export declare function stepCircularValue(value: number, steps: number, min: number, max: number, step: number): number;
|
|
7
|
+
/** Map an initial track press to an arc, selecting the closest endpoint in its gap. */
|
|
8
|
+
export declare function initialArcPosition(angle: number, start: number, sweep: number, clockwise: boolean, current: number): number;
|
|
9
|
+
/** Unwrap small consecutive movements; crossing the seam never resets to zero. */
|
|
10
|
+
export declare function advanceArcPosition(position: number, previousAngle: number, angle: number, sweep: number, clockwise: boolean): number;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Clock angles: 0 points up and positive angles turn clockwise. */
|
|
2
|
+
export const normalizeAngle = (angle) => ((angle % 360) + 360) % 360;
|
|
3
|
+
export const clampCircularValue = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
4
|
+
function cleanNumber(value) {
|
|
5
|
+
return Number(value.toPrecision(14));
|
|
6
|
+
}
|
|
7
|
+
/** Snap to the grid anchored at min, retaining max as an attainable final stop. */
|
|
8
|
+
export function snapCircularValue(value, min, max, step) {
|
|
9
|
+
if (!Number.isFinite(value))
|
|
10
|
+
return min;
|
|
11
|
+
const bounded = clampCircularValue(value, min, max);
|
|
12
|
+
const snapped = clampCircularValue(cleanNumber(min + Math.round((bounded - min) / step) * step), min, max);
|
|
13
|
+
return max - bounded < Math.abs(snapped - bounded) ? max : snapped;
|
|
14
|
+
}
|
|
15
|
+
export function stepCircularValue(value, steps, min, max, step) {
|
|
16
|
+
const position = (value - min) / step;
|
|
17
|
+
const index = steps > 0 ? Math.floor(position + 1e-9) : Math.ceil(position - 1e-9);
|
|
18
|
+
return clampCircularValue(cleanNumber(min + (index + steps) * step), min, max);
|
|
19
|
+
}
|
|
20
|
+
/** Map an initial track press to an arc, selecting the closest endpoint in its gap. */
|
|
21
|
+
export function initialArcPosition(angle, start, sweep, clockwise, current) {
|
|
22
|
+
const position = normalizeAngle((angle - start) * (clockwise ? 1 : -1));
|
|
23
|
+
if (sweep < 360)
|
|
24
|
+
return position <= sweep ? position : position - sweep < 360 - position ? sweep : 0;
|
|
25
|
+
// Only the exact seam represents two values; other track presses are absolute.
|
|
26
|
+
if (position < 1e-7 || 360 - position < 1e-7)
|
|
27
|
+
return current > 180 ? 360 : 0;
|
|
28
|
+
return position;
|
|
29
|
+
}
|
|
30
|
+
/** Unwrap small consecutive movements; crossing the seam never resets to zero. */
|
|
31
|
+
export function advanceArcPosition(position, previousAngle, angle, sweep, clockwise) {
|
|
32
|
+
const delta = normalizeAngle(angle - previousAngle + 180) - 180;
|
|
33
|
+
return clampCircularValue(position + delta * (clockwise ? 1 : -1), 0, sweep);
|
|
34
|
+
}
|
package/dist/slider/index.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import * as SliderPrimitive from '@radix-ui/react-slider';
|
|
2
2
|
export type SliderSize = 'sm' | 'md' | 'lg';
|
|
3
3
|
export type SliderVariant = 'standard' | 'inset';
|
|
4
|
+
export type SliderThumbAlignment = 'edge' | 'contain';
|
|
4
5
|
export interface SliderProps extends Omit<SliderPrimitive.SliderProps, 'children' | 'asChild'> {
|
|
5
6
|
size?: SliderSize;
|
|
6
7
|
variant?: SliderVariant;
|
|
8
|
+
/** Align thumb centers with track endpoints, or retain the inset hit-target bounds. */
|
|
9
|
+
thumbAlignment?: SliderThumbAlignment;
|
|
7
10
|
seed?: string | number;
|
|
8
11
|
thumbLabels?: readonly string[];
|
|
9
12
|
getValueLabel?: (value: number, index: number) => string;
|
|
10
13
|
}
|
|
11
14
|
export declare const Slider: import("react").ForwardRefExoticComponent<SliderProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
15
|
+
export { CircularSlider } from './circular.js';
|
|
16
|
+
export type { CircularSliderProps } from './circular.js';
|
package/dist/slider/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import * as SliderPrimitive from '@radix-ui/react-slider';
|
|
3
3
|
import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
|
|
4
4
|
import { PencilSurface } from '../PencilSurface.js';
|
|
5
|
-
export const Slider = forwardRef(function Slider({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-valuetext': ariaValueText, className, defaultValue, dir, disabled = false, getValueLabel, inverted = false, min = 0, max = 100, name, form, onValueChange, onValueCommit, orientation = 'horizontal', seed, size = 'md', thumbLabels, value, variant = 'standard', ...props }, forwardedRef) {
|
|
5
|
+
export const Slider = forwardRef(function Slider({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-valuetext': ariaValueText, className, defaultValue, dir, disabled = false, getValueLabel, inverted = false, min = 0, max = 100, name, form, onValueChange, onValueCommit, orientation = 'horizontal', seed, size = 'md', thumbAlignment = 'edge', thumbLabels, value, variant = 'standard', ...props }, forwardedRef) {
|
|
6
6
|
const generatedId = useId();
|
|
7
7
|
const rootRef = useRef(null);
|
|
8
8
|
useImperativeHandle(forwardedRef, () => rootRef.current);
|
|
@@ -70,16 +70,17 @@ export const Slider = forwardRef(function Slider({ 'aria-label': ariaLabel, 'ari
|
|
|
70
70
|
const clip = {
|
|
71
71
|
clipPath: orientation === 'horizontal' ? `inset(0 ${end}% 0 ${start}%)` : `inset(${start}% 0 ${end}% 0)`,
|
|
72
72
|
};
|
|
73
|
-
return _jsxs(SliderPrimitive.Root, { ...props, className: ['inkra-slider', className].filter(Boolean).join(' '), "data-size": size, "data-variant": variant, dir: resolvedDir, disabled: disabled, form: radixFormId, inverted: inverted, min: min, max: max, onValueChange: next => {
|
|
73
|
+
return _jsxs(SliderPrimitive.Root, { ...props, className: ['inkra-slider', className].filter(Boolean).join(' '), "data-size": size, "data-variant": variant, "data-thumb-alignment": thumbAlignment, dir: resolvedDir, disabled: disabled, form: radixFormId, inverted: inverted, min: min, max: max, onValueChange: next => {
|
|
74
74
|
if (!controlled)
|
|
75
75
|
setUncontrolledValue(next);
|
|
76
76
|
onValueChange?.(next);
|
|
77
77
|
}, onValueCommit: onValueCommit, orientation: orientation, ref: rootRef, value: values, children: [_jsxs(SliderPrimitive.Track, { className: "inkra-slider__track", children: [_jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-slider__track-surface", variant: "outline", radius: variant === 'inset' ? 8 : 3, seed: `${resolvedSeed}-track` }), _jsx("span", { "aria-hidden": "true", className: "inkra-slider__range", style: clip, children: _jsx(PencilSurface, { as: "span", className: "inkra-slider__range-surface", variant: "selection", seed: `${resolvedSeed}-range` }) })] }), values.map((current, index) => {
|
|
78
|
+
const position = reversed ? 100 - percentage(current) : percentage(current);
|
|
78
79
|
const thumbLabel = thumbLabels?.[index];
|
|
79
80
|
const labelledBy = thumbLabel ? undefined : ariaLabelledBy;
|
|
80
81
|
const label = thumbLabel ?? (ariaLabel ? `${ariaLabel}${values.length > 1 ? ` ${index + 1}` : ''}` : labelledBy ? undefined : `Value${values.length > 1 ? ` ${index + 1}` : ''}`);
|
|
81
82
|
const inputName = name ? values.length > 1 ? `${name}[]` : name : undefined;
|
|
82
|
-
return _jsxs(SliderPrimitive.unstable_ThumbProvider, { name: inputName, children: [_jsx(SliderPrimitive.unstable_ThumbTrigger, { "aria-describedby": ariaDescribedBy, "aria-label": label, "aria-labelledby": labelledBy, "aria-valuenow": current, "aria-valuetext": getValueLabel?.(current, index) ?? ariaValueText, className: "inkra-slider__thumb", children: _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-slider__thumb-surface", variant: "paper", radius: 8, seed: `${resolvedSeed}-thumb-${index}`, children: _jsx(PencilSurface, { as: "span", className: "inkra-slider__focus-ring", variant: "outline", radius: 8, seed: `${resolvedSeed}-focus-${index}` }) }) }), _jsx(SliderPrimitive.unstable_BubbleInput, { disabled: disabled, form: form, name: inputName, ref: input => {
|
|
83
|
+
return _jsxs(SliderPrimitive.unstable_ThumbProvider, { name: inputName, children: [_jsx(SliderPrimitive.unstable_ThumbTrigger, { "aria-describedby": ariaDescribedBy, "aria-label": label, "aria-labelledby": labelledBy, "aria-valuenow": current, "aria-valuetext": getValueLabel?.(current, index) ?? ariaValueText, className: "inkra-slider__thumb", style: { '--inkra-slider-thumb-offset': `${position - 50}%` }, children: _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-slider__thumb-surface", variant: "paper", radius: 8, seed: `${resolvedSeed}-thumb-${index}`, children: _jsx(PencilSurface, { as: "span", className: "inkra-slider__focus-ring", variant: "outline", radius: 8, seed: `${resolvedSeed}-focus-${index}` }) }) }), _jsx(SliderPrimitive.unstable_BubbleInput, { disabled: disabled, form: form, name: inputName, ref: input => {
|
|
83
84
|
if (input)
|
|
84
85
|
formInputRefs.current.set(index, input);
|
|
85
86
|
else
|
|
@@ -87,3 +88,4 @@ export const Slider = forwardRef(function Slider({ 'aria-label': ariaLabel, 'ari
|
|
|
87
88
|
} })] }, index);
|
|
88
89
|
})] });
|
|
89
90
|
});
|
|
91
|
+
export { CircularSlider } from './circular.js';
|
package/dist/styles/base.css
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
@import "../tokens.css";
|
|
2
2
|
|
|
3
|
+
@import "../assets/xiaolai.css";
|
|
4
|
+
|
|
3
5
|
@font-face {
|
|
4
6
|
font-family: "Trip Map Hand";
|
|
5
7
|
font-style: normal;
|
|
@@ -129,13 +131,13 @@
|
|
|
129
131
|
.inkra-pencil-text__source {
|
|
130
132
|
color: inherit;
|
|
131
133
|
font: inherit;
|
|
132
|
-
font-family: "Trip Map Hand", "HanziPen SC", "Hannotate SC", "Kaiti SC", "STKaiti", cursive;
|
|
133
134
|
font-kerning: none;
|
|
134
135
|
font-variant-ligatures: none;
|
|
135
136
|
letter-spacing: 0;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
.inkra-pencil-text {
|
|
140
|
+
font-family: var(--inkra-font-hand);
|
|
139
141
|
display: inline-block;
|
|
140
142
|
max-width: 100%;
|
|
141
143
|
min-width: 0;
|
|
@@ -216,7 +218,7 @@
|
|
|
216
218
|
color: var(--inkra-on-accent, var(--inkra-accent-strong));
|
|
217
219
|
cursor: pointer;
|
|
218
220
|
display: inline-flex;
|
|
219
|
-
font-family:
|
|
221
|
+
font-family: var(--inkra-font-hand);
|
|
220
222
|
font-weight: var(--inkra-weight-regular);
|
|
221
223
|
justify-content: center;
|
|
222
224
|
letter-spacing: var(--inkra-letter-spacing);
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
align-items: center;
|
|
56
56
|
box-sizing: border-box;
|
|
57
57
|
display: inline-flex;
|
|
58
|
-
font-family:
|
|
58
|
+
font-family: var(--inkra-font-hand);
|
|
59
59
|
font-size: var(--inkra-breadcrumb-item-font-size);
|
|
60
60
|
gap: var(--inkra-breadcrumb-item-gap);
|
|
61
61
|
justify-content: center;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
.inkra-guide__spotlight,
|
|
2
|
+
.inkra-guide__backdrop {
|
|
3
|
+
inset: 0;
|
|
4
|
+
pointer-events: none;
|
|
5
|
+
position: fixed;
|
|
6
|
+
z-index: calc(var(--inkra-z-modal, 30) + var(--inkra-floating-layer-offset, 0));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.inkra-guide__backdrop {
|
|
10
|
+
background: var(--inkra-overlay);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.inkra-guide__spotlight {
|
|
14
|
+
box-shadow: 0 0 0 100vmax var(--inkra-overlay);
|
|
15
|
+
overflow: visible;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.inkra-guide__spotlight > .inkra-pencil-surface__ink,
|
|
19
|
+
.inkra-guide__spotlight > .inkra-pencil-surface__border {
|
|
20
|
+
border-radius: var(--inkra-guide-highlight-radius, 8px);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.inkra-guide__content {
|
|
24
|
+
max-height: min(70dvh, var(--radix-popover-content-available-height, 70dvh));
|
|
25
|
+
max-width: min(24rem, calc(100dvw - var(--inkra-space-6)));
|
|
26
|
+
outline: 0;
|
|
27
|
+
transform-origin: var(--radix-popover-content-transform-origin);
|
|
28
|
+
width: min(22rem, calc(100dvw - var(--inkra-space-6)));
|
|
29
|
+
z-index: calc(var(--inkra-z-modal, 30) + var(--inkra-floating-layer-offset, 0) + 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.inkra-guide__content:focus-visible {
|
|
33
|
+
outline: var(--inkra-focus-width) solid var(--inkra-focus);
|
|
34
|
+
outline-offset: var(--inkra-focus-offset);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.inkra-guide__skip-layer {
|
|
38
|
+
inset: 0;
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
position: fixed;
|
|
41
|
+
z-index: calc(var(--inkra-z-modal, 30) + var(--inkra-floating-layer-offset, 0) + 2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.inkra-guide__skip {
|
|
45
|
+
color: var(--inkra-ink-muted);
|
|
46
|
+
opacity: .72;
|
|
47
|
+
pointer-events: auto;
|
|
48
|
+
position: absolute;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.inkra-guide__skip:is(:hover, :focus-visible) {
|
|
52
|
+
color: var(--inkra-ink);
|
|
53
|
+
opacity: 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.inkra-guide__skip[data-placement^="top"] {
|
|
57
|
+
inset-block-start: max(var(--inkra-space-4), env(safe-area-inset-top));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.inkra-guide__skip[data-placement^="bottom"] {
|
|
61
|
+
inset-block-end: max(var(--inkra-space-4), env(safe-area-inset-bottom));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.inkra-guide__skip[data-placement$="start"] {
|
|
65
|
+
inset-inline-start: max(var(--inkra-space-4), env(safe-area-inset-left));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.inkra-guide__skip[data-placement$="end"] {
|
|
69
|
+
inset-inline-end: max(var(--inkra-space-4), env(safe-area-inset-right));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.inkra-guide__surface {
|
|
73
|
+
color: var(--inkra-ink);
|
|
74
|
+
display: block;
|
|
75
|
+
max-height: inherit;
|
|
76
|
+
width: 100%;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.inkra-guide__panel {
|
|
80
|
+
box-sizing: border-box;
|
|
81
|
+
display: grid;
|
|
82
|
+
gap: var(--inkra-space-4);
|
|
83
|
+
max-height: inherit;
|
|
84
|
+
overflow: auto;
|
|
85
|
+
overflow-wrap: anywhere;
|
|
86
|
+
overscroll-behavior: contain;
|
|
87
|
+
padding: var(--inkra-space-5);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.inkra-guide__header,
|
|
91
|
+
.inkra-guide__footer,
|
|
92
|
+
.inkra-guide__navigation {
|
|
93
|
+
align-items: center;
|
|
94
|
+
display: flex;
|
|
95
|
+
gap: var(--inkra-space-2);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.inkra-guide__header {
|
|
99
|
+
align-items: flex-start;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.inkra-guide__heading {
|
|
103
|
+
display: grid;
|
|
104
|
+
flex: 1;
|
|
105
|
+
gap: var(--inkra-space-1);
|
|
106
|
+
min-width: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.inkra-guide__progress {
|
|
110
|
+
color: var(--inkra-ink-subtle);
|
|
111
|
+
font-family: var(--inkra-font-body);
|
|
112
|
+
font-size: var(--inkra-text-sm);
|
|
113
|
+
line-height: var(--inkra-line-ui);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.inkra-guide__title {
|
|
117
|
+
font-family: var(--inkra-font-display);
|
|
118
|
+
font-size: var(--inkra-text-xl);
|
|
119
|
+
font-weight: var(--inkra-weight-regular);
|
|
120
|
+
line-height: var(--inkra-line-ui);
|
|
121
|
+
margin: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.inkra-guide__description {
|
|
125
|
+
color: var(--inkra-ink-muted);
|
|
126
|
+
font-family: var(--inkra-font-body);
|
|
127
|
+
font-size: var(--inkra-text-md);
|
|
128
|
+
line-height: var(--inkra-line-body);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.inkra-guide__close {
|
|
132
|
+
flex: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.inkra-guide__footer {
|
|
136
|
+
flex-wrap: wrap;
|
|
137
|
+
justify-content: space-between;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.inkra-guide__footer > :empty {
|
|
141
|
+
display: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.inkra-guide__navigation {
|
|
145
|
+
flex-wrap: wrap;
|
|
146
|
+
justify-content: flex-end;
|
|
147
|
+
margin-inline-start: auto;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.inkra-guide__footer .inkra-button {
|
|
151
|
+
max-width: 100%;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@media (max-width: 30rem) {
|
|
155
|
+
.inkra-guide__content {
|
|
156
|
+
max-height: calc(100dvh - var(--inkra-space-4));
|
|
157
|
+
max-width: calc(100dvw - var(--inkra-space-4));
|
|
158
|
+
width: calc(100dvw - var(--inkra-space-4));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.inkra-guide__panel {
|
|
162
|
+
padding: var(--inkra-space-4);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.inkra-guide__footer,
|
|
166
|
+
.inkra-guide__navigation {
|
|
167
|
+
width: 100%;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.inkra-guide__footer > .inkra-button,
|
|
171
|
+
.inkra-guide__navigation > .inkra-button {
|
|
172
|
+
flex: 1 1 auto;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@media (prefers-reduced-motion: reduce) {
|
|
177
|
+
.inkra-guide__content {
|
|
178
|
+
animation: none !important;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@media (forced-colors: active) {
|
|
183
|
+
.inkra-guide__backdrop {
|
|
184
|
+
background: CanvasText;
|
|
185
|
+
opacity: 0.25;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.inkra-guide__spotlight {
|
|
189
|
+
box-shadow: 0 0 0 100vmax color-mix(in srgb, CanvasText 25%, transparent);
|
|
190
|
+
outline: 2px solid Highlight;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.inkra-guide__surface {
|
|
194
|
+
background: Canvas;
|
|
195
|
+
border: 1px solid CanvasText;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.inkra-guide__skip {
|
|
199
|
+
color: ButtonText;
|
|
200
|
+
opacity: 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
.inkra-image-viewer__body {
|
|
82
|
+
position: relative;
|
|
82
83
|
display: grid;
|
|
83
84
|
flex: 1 1 auto;
|
|
84
85
|
min-block-size: var(--inkra-touch-target);
|
|
@@ -226,6 +227,24 @@
|
|
|
226
227
|
padding: var(--inkra-focus-offset);
|
|
227
228
|
position: relative;
|
|
228
229
|
scrollbar-gutter: auto;
|
|
230
|
+
pointer-events: auto;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.inkra-image-viewer__floating-tools {
|
|
234
|
+
position: absolute;
|
|
235
|
+
inset-inline: max(var(--inkra-space-3), env(safe-area-inset-left)) max(var(--inkra-space-3), env(safe-area-inset-right));
|
|
236
|
+
inset-block-start: max(var(--inkra-space-3), env(safe-area-inset-top));
|
|
237
|
+
display: flex;
|
|
238
|
+
justify-content: center;
|
|
239
|
+
padding-inline: var(--inkra-touch-target);
|
|
240
|
+
pointer-events: none;
|
|
241
|
+
z-index: 6;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.inkra-image-viewer__floating-tools[data-position="bottom"] {
|
|
245
|
+
inset-block-start: auto;
|
|
246
|
+
inset-block-end: max(var(--inkra-space-3), env(safe-area-inset-bottom));
|
|
247
|
+
padding-inline: 0;
|
|
229
248
|
}
|
|
230
249
|
|
|
231
250
|
.inkra-image-viewer__video-navigation {
|
|
@@ -345,6 +364,7 @@
|
|
|
345
364
|
@media print {
|
|
346
365
|
.inkra-image-viewer__close-control,
|
|
347
366
|
.inkra-image-viewer__navigation,
|
|
367
|
+
.inkra-image-viewer__floating-tools,
|
|
348
368
|
.inkra-image-viewer__bottom { display: none; }
|
|
349
369
|
}
|
|
350
370
|
|