@inkra/react 0.1.0 → 0.3.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 +186 -1
- package/dist/PencilIcon.js +14 -16
- package/dist/PencilLine.js +18 -12
- package/dist/PencilText.js +192 -59
- package/dist/alert-dialog/index.d.ts +64 -0
- package/dist/alert-dialog/index.js +111 -0
- package/dist/audio-player/index.d.ts +60 -0
- package/dist/audio-player/index.js +101 -0
- package/dist/carousel/index.d.ts +51 -0
- package/dist/carousel/index.js +290 -0
- package/dist/choice-grid/index.d.ts +38 -0
- package/dist/choice-grid/index.js +85 -0
- package/dist/color-picker/color.d.ts +15 -0
- package/dist/color-picker/color.js +55 -0
- package/dist/color-picker/index.d.ts +31 -0
- package/dist/color-picker/index.js +69 -0
- package/dist/command/index.d.ts +45 -0
- package/dist/command/index.js +39 -0
- package/dist/context-menu/index.d.ts +76 -0
- package/dist/context-menu/index.js +241 -0
- package/dist/data-table/index.d.ts +47 -0
- package/dist/data-table/index.js +91 -0
- package/dist/date-picker/index.d.ts +50 -0
- package/dist/date-picker/index.js +180 -0
- package/dist/display/ProgressRing.d.ts +6 -0
- package/dist/display/ProgressRing.js +146 -0
- package/dist/display/index.d.ts +27 -0
- package/dist/display/index.js +18 -2
- package/dist/dockable-panel/index.d.ts +41 -0
- package/dist/dockable-panel/index.js +253 -0
- package/dist/dockable-panel/model.d.ts +21 -0
- package/dist/dockable-panel/model.js +61 -0
- package/dist/drawer/index.d.ts +63 -0
- package/dist/drawer/index.js +88 -0
- package/dist/dropdown-menu/index.d.ts +1 -1
- package/dist/dropdown-menu/index.js +3 -2
- package/dist/editable/index.d.ts +53 -0
- package/dist/editable/index.js +162 -0
- package/dist/empty/index.d.ts +21 -0
- package/dist/empty/index.js +23 -0
- package/dist/flip/index.d.ts +49 -0
- package/dist/flip/index.js +138 -0
- package/dist/form/EditablePencilText.js +188 -143
- package/dist/form/Input.js +1 -1
- package/dist/form/Textarea.d.ts +12 -0
- package/dist/form/Textarea.js +83 -4
- package/dist/form/index.d.ts +1 -1
- package/dist/heatmap/index.d.ts +59 -0
- package/dist/heatmap/index.js +248 -0
- package/dist/hotkey/index.d.ts +37 -0
- package/dist/hotkey/index.js +130 -0
- package/dist/hotkey/model.d.ts +27 -0
- package/dist/hotkey/model.js +97 -0
- package/dist/hover-card/index.d.ts +33 -0
- package/dist/hover-card/index.js +141 -0
- package/dist/image-cropper/index.d.ts +82 -0
- package/dist/image-cropper/index.js +261 -0
- package/dist/image-cropper/model.d.ts +42 -0
- package/dist/image-cropper/model.js +94 -0
- package/dist/image-viewer/index.d.ts +79 -0
- package/dist/image-viewer/index.js +220 -0
- package/dist/image-viewer/model.d.ts +9 -0
- package/dist/image-viewer/model.js +18 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +47 -0
- package/dist/input-number/index.d.ts +6 -0
- package/dist/input-number/index.js +126 -15
- package/dist/input-number/model.d.ts +13 -0
- package/dist/input-number/model.js +50 -0
- package/dist/internal/cache.d.ts +1 -1
- package/dist/internal/cache.js +1 -1
- package/dist/internal/renderTokens.d.ts +10 -0
- package/dist/internal/renderTokens.js +13 -0
- package/dist/internal/surfaceRenderer.d.ts +14 -6
- package/dist/internal/surfaceRenderer.js +15 -12
- package/dist/internal/textRenderer.d.ts +1 -0
- package/dist/internal/textRenderer.js +33 -7
- package/dist/internal/usePencilSurface.js +16 -8
- package/dist/internal/useSelectionDrawing.js +15 -11
- package/dist/journey-map/MapScale.js +3 -1
- package/dist/journey-map/index.js +3 -1
- package/dist/keypad/index.d.ts +23 -0
- package/dist/keypad/index.js +63 -0
- package/dist/keypad/model.d.ts +20 -0
- package/dist/keypad/model.js +33 -0
- package/dist/layout/index.d.ts +63 -0
- package/dist/layout/index.js +62 -0
- package/dist/log/index.d.ts +45 -0
- package/dist/log/index.js +159 -0
- package/dist/log/model.d.ts +16 -0
- package/dist/log/model.js +13 -0
- package/dist/node-graph/index.d.ts +123 -0
- package/dist/node-graph/index.js +304 -0
- package/dist/number-ticker/index.d.ts +31 -0
- package/dist/number-ticker/index.js +135 -0
- package/dist/number-ticker/model.d.ts +41 -0
- package/dist/number-ticker/model.js +163 -0
- package/dist/number-ticker/precision.d.ts +7 -0
- package/dist/number-ticker/precision.js +134 -0
- package/dist/overflow-list/index.d.ts +39 -0
- package/dist/overflow-list/index.js +230 -0
- package/dist/overflow-list/layout.d.ts +19 -0
- package/dist/overflow-list/layout.js +44 -0
- package/dist/overlay/Overlay.d.ts +1 -1
- package/dist/overlay/Overlay.js +4 -2
- package/dist/overlay/Popover.d.ts +1 -1
- package/dist/overlay/Popover.js +3 -2
- package/dist/overlay/themeBridge.d.ts +12 -1
- package/dist/overlay/themeBridge.js +27 -1
- package/dist/pagination/index.d.ts +23 -0
- package/dist/pagination/index.js +41 -0
- package/dist/pagination/model.d.ts +4 -0
- package/dist/pagination/model.js +42 -0
- package/dist/pan-zoom/index.d.ts +71 -0
- package/dist/pan-zoom/index.js +175 -0
- package/dist/radar/index.d.ts +56 -0
- package/dist/radar/index.js +220 -0
- package/dist/range-slider/index.d.ts +16 -0
- package/dist/range-slider/index.js +22 -0
- package/dist/rating/index.d.ts +28 -0
- package/dist/rating/index.js +127 -0
- package/dist/resizable/index.d.ts +32 -0
- package/dist/resizable/index.js +50 -0
- package/dist/scroll-area/index.d.ts +36 -0
- package/dist/scroll-area/index.js +44 -0
- package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
- package/dist/segmented-progress/SegmentedProgress.js +42 -0
- package/dist/slider/index.js +1 -1
- package/dist/slot/index.d.ts +24 -0
- package/dist/slot/index.js +29 -0
- package/dist/sortable/index.d.ts +50 -0
- package/dist/sortable/index.js +283 -0
- package/dist/sortable/model.d.ts +30 -0
- package/dist/sortable/model.js +64 -0
- package/dist/sparkline/index.d.ts +19 -0
- package/dist/sparkline/index.js +240 -0
- package/dist/sparkline/model.d.ts +16 -0
- package/dist/sparkline/model.js +28 -0
- package/dist/steps/index.d.ts +16 -0
- package/dist/steps/index.js +37 -0
- package/dist/styles.css +4957 -269
- package/dist/tag-input/index.d.ts +69 -0
- package/dist/tag-input/index.js +325 -0
- package/dist/tag-input/model.d.ts +14 -0
- package/dist/tag-input/model.js +38 -0
- package/dist/time-picker/index.d.ts +15 -0
- package/dist/time-picker/index.js +168 -0
- package/dist/timeline/index.d.ts +14 -0
- package/dist/timeline/index.js +16 -0
- package/dist/toast/index.d.ts +27 -0
- package/dist/toast/index.js +36 -0
- package/dist/toggle-group/index.d.ts +17 -0
- package/dist/toggle-group/index.js +26 -0
- package/dist/tokens.css +10 -9
- package/dist/tooltip/Tooltip.d.ts +1 -1
- package/dist/tooltip/Tooltip.js +3 -2
- package/dist/transfer/index.d.ts +64 -0
- package/dist/transfer/index.js +174 -0
- package/dist/transfer/model.d.ts +12 -0
- package/dist/transfer/model.js +33 -0
- package/dist/tree/index.d.ts +39 -0
- package/dist/tree/index.js +36 -0
- package/dist/tree-select/index.d.ts +59 -0
- package/dist/tree-select/index.js +142 -0
- package/dist/tree-select/model.d.ts +31 -0
- package/dist/tree-select/model.js +135 -0
- package/dist/typewriter/index.d.ts +21 -0
- package/dist/typewriter/index.js +101 -0
- package/dist/typewriter/model.d.ts +7 -0
- package/dist/typewriter/model.js +45 -0
- package/dist/upload/index.d.ts +59 -0
- package/dist/upload/index.js +230 -0
- package/dist/virtual-list/index.d.ts +44 -0
- package/dist/virtual-list/index.js +129 -0
- package/dist/virtual-list/model.d.ts +7 -0
- package/dist/virtual-list/model.js +27 -0
- package/package.json +23 -3
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
3
3
|
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
4
4
|
import { Button } from '../Button.js';
|
|
5
5
|
import { PencilIcon } from '../PencilIcon.js';
|
|
6
6
|
import { Input } from '../form/Input.js';
|
|
7
7
|
import { useResolvedControlProps } from '../form/controlProps.js';
|
|
8
8
|
import { Tooltip } from '../tooltip/Tooltip.js';
|
|
9
|
+
import { normalizeInputNumberRepeat, resolveInputNumberStep, stepInputNumber, } from './model.js';
|
|
9
10
|
// Lucide Plus / Minus ISC geometry uses the shared pencil renderer.
|
|
10
11
|
const plusPaths = [{ d: 'M5 12h14' }, { d: 'M12 5v14' }];
|
|
11
12
|
const minusPaths = [{ d: 'M5 12h14' }];
|
|
12
13
|
const finiteValue = (value) => typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
13
14
|
const format = (value) => String(finiteValue(value) ?? '');
|
|
14
15
|
const numberFromDraft = (draft) => draft === '' ? null : finiteValue(Number(draft));
|
|
15
|
-
export const InputNumber = forwardRef(function InputNumber({ classNames, controls = true, decrementButtonLabel = 'Decrease value', defaultValue, disabled, incrementButtonLabel = 'Increase value', inputMode = 'decimal', max, min, onChange, onValueChange, readOnly, size = 'md', step = 1, textAlign = 'center', value, ...props }, forwardedRef) {
|
|
16
|
+
export const InputNumber = forwardRef(function InputNumber({ classNames, controls = true, decrementButtonLabel = 'Decrease value', defaultValue, disabled, incrementButtonLabel = 'Increase value', inputMode = 'decimal', max, min, onChange, onBlur, onKeyDown, onValueChange, pressAndHold = true, readOnly, repeatDelay = 400, repeatInterval = 80, size = 'md', step = 1, stepModifiers, textAlign = 'center', value, ...props }, forwardedRef) {
|
|
16
17
|
const inputRef = useRef(null);
|
|
17
18
|
const isControlled = value !== undefined;
|
|
18
19
|
const [draft, setDraft] = useState(() => format(isControlled ? value : defaultValue));
|
|
@@ -21,6 +22,50 @@ export const InputNumber = forwardRef(function InputNumber({ classNames, control
|
|
|
21
22
|
const latest = useRef({ isControlled, value, defaultValue });
|
|
22
23
|
latest.current = { isControlled, value, defaultValue };
|
|
23
24
|
useImperativeHandle(forwardedRef, () => inputRef.current);
|
|
25
|
+
const holdRef = useRef(null);
|
|
26
|
+
const timerRef = useRef(undefined);
|
|
27
|
+
const suppressClickRef = useRef(false);
|
|
28
|
+
const stopHolding = useCallback(() => {
|
|
29
|
+
clearTimeout(timerRef.current);
|
|
30
|
+
timerRef.current = undefined;
|
|
31
|
+
const hold = holdRef.current;
|
|
32
|
+
holdRef.current = null;
|
|
33
|
+
if (hold?.button.hasPointerCapture?.(hold.pointerId)) {
|
|
34
|
+
try {
|
|
35
|
+
hold.button.releasePointerCapture(hold.pointerId);
|
|
36
|
+
}
|
|
37
|
+
catch { /* The pointer may already be inactive. */ }
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const onVisibilityChange = () => { if (document.hidden)
|
|
42
|
+
stopHolding(); };
|
|
43
|
+
const onPointerEnd = (event) => {
|
|
44
|
+
if (event.pointerId === holdRef.current?.pointerId)
|
|
45
|
+
stopHolding();
|
|
46
|
+
};
|
|
47
|
+
const updateModifiers = (event) => {
|
|
48
|
+
if (holdRef.current)
|
|
49
|
+
holdRef.current.modifiers = {
|
|
50
|
+
shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
window.addEventListener('blur', stopHolding);
|
|
54
|
+
window.addEventListener('pointerup', onPointerEnd);
|
|
55
|
+
window.addEventListener('pointercancel', onPointerEnd);
|
|
56
|
+
window.addEventListener('keydown', updateModifiers);
|
|
57
|
+
window.addEventListener('keyup', updateModifiers);
|
|
58
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
59
|
+
return () => {
|
|
60
|
+
stopHolding();
|
|
61
|
+
window.removeEventListener('blur', stopHolding);
|
|
62
|
+
window.removeEventListener('pointerup', onPointerEnd);
|
|
63
|
+
window.removeEventListener('pointercancel', onPointerEnd);
|
|
64
|
+
window.removeEventListener('keydown', updateModifiers);
|
|
65
|
+
window.removeEventListener('keyup', updateModifiers);
|
|
66
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
67
|
+
};
|
|
68
|
+
}, [stopHolding]);
|
|
24
69
|
useBrowserLayoutEffect(() => {
|
|
25
70
|
if (isControlled && currentValue !== finiteValue(value))
|
|
26
71
|
setDraft(format(value));
|
|
@@ -32,6 +77,7 @@ export const InputNumber = forwardRef(function InputNumber({ classNames, control
|
|
|
32
77
|
return;
|
|
33
78
|
let resetTimer;
|
|
34
79
|
const reset = (event) => {
|
|
80
|
+
stopHolding();
|
|
35
81
|
clearTimeout(resetTimer);
|
|
36
82
|
// Native reset runs after the event; restore the public default, not the latest draft.
|
|
37
83
|
resetTimer = setTimeout(() => {
|
|
@@ -45,33 +91,98 @@ export const InputNumber = forwardRef(function InputNumber({ classNames, control
|
|
|
45
91
|
};
|
|
46
92
|
form.addEventListener('reset', reset);
|
|
47
93
|
return () => { clearTimeout(resetTimer); form.removeEventListener('reset', reset); };
|
|
48
|
-
}, [props.form]);
|
|
94
|
+
}, [props.form, stopHolding]);
|
|
49
95
|
const cannotStep = state.disabled || state.readOnly || step === 'any' || (min !== undefined && max !== undefined && min > max);
|
|
50
96
|
const decreaseDisabled = cannotStep || (currentValue !== null && min !== undefined && currentValue <= min);
|
|
51
97
|
const increaseDisabled = cannotStep || (currentValue !== null && max !== undefined && currentValue >= max);
|
|
52
|
-
|
|
98
|
+
useBrowserLayoutEffect(() => {
|
|
99
|
+
const direction = holdRef.current?.direction;
|
|
100
|
+
if (cannotStep || !controls || !pressAndHold
|
|
101
|
+
|| (direction === 'increment' && increaseDisabled)
|
|
102
|
+
|| (direction === 'decrement' && decreaseDisabled))
|
|
103
|
+
stopHolding();
|
|
104
|
+
}, [cannotStep, controls, decreaseDisabled, increaseDisabled, pressAndHold, stopHolding]);
|
|
105
|
+
function changeStep(direction, modifiers) {
|
|
53
106
|
const input = inputRef.current;
|
|
54
107
|
if (!input || input.matches(':disabled') || input.readOnly || cannotStep)
|
|
55
|
-
return;
|
|
56
|
-
const
|
|
57
|
-
// Native stepping applies the step base and decimal rounding without a custom parser.
|
|
58
|
-
if (direction === 'increment')
|
|
59
|
-
input.stepUp();
|
|
60
|
-
else
|
|
61
|
-
input.stepDown();
|
|
108
|
+
return false;
|
|
109
|
+
const changed = stepInputNumber(input, direction, resolveInputNumberStep(modifiers, stepModifiers));
|
|
62
110
|
input.focus({ preventScroll: true });
|
|
63
|
-
if (
|
|
64
|
-
return;
|
|
111
|
+
if (!changed)
|
|
112
|
+
return false;
|
|
65
113
|
setDraft(input.value);
|
|
66
114
|
onValueChange?.(Number.isFinite(input.valueAsNumber) ? input.valueAsNumber : null, { reason: direction });
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
const stepRef = useRef(changeStep);
|
|
118
|
+
stepRef.current = changeStep;
|
|
119
|
+
const intervalRef = useRef(repeatInterval);
|
|
120
|
+
intervalRef.current = repeatInterval;
|
|
121
|
+
function startHolding(event, direction) {
|
|
122
|
+
if (event.button !== 0 || !event.isPrimary)
|
|
123
|
+
return;
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
stopHolding();
|
|
126
|
+
suppressClickRef.current = false;
|
|
127
|
+
if (!pressAndHold)
|
|
128
|
+
return;
|
|
129
|
+
suppressClickRef.current = true;
|
|
130
|
+
const modifiers = {
|
|
131
|
+
shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey,
|
|
132
|
+
};
|
|
133
|
+
if (!changeStep(direction, modifiers))
|
|
134
|
+
return;
|
|
135
|
+
const button = event.currentTarget;
|
|
136
|
+
if (!button.isConnected || button.disabled)
|
|
137
|
+
return;
|
|
138
|
+
holdRef.current = { button, pointerId: event.pointerId, direction, modifiers };
|
|
139
|
+
try {
|
|
140
|
+
button.setPointerCapture(event.pointerId);
|
|
141
|
+
}
|
|
142
|
+
catch { /* Window listeners retain release handling. */ }
|
|
143
|
+
const repeat = () => {
|
|
144
|
+
const hold = holdRef.current;
|
|
145
|
+
if (!hold || document.hidden || !stepRef.current(hold.direction, hold.modifiers)) {
|
|
146
|
+
stopHolding();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (holdRef.current)
|
|
150
|
+
timerRef.current = setTimeout(repeat, normalizeInputNumberRepeat(intervalRef.current, 80, 32));
|
|
151
|
+
};
|
|
152
|
+
timerRef.current = setTimeout(repeat, normalizeInputNumberRepeat(repeatDelay, 400, 150));
|
|
153
|
+
}
|
|
154
|
+
function buttonEvents(direction) {
|
|
155
|
+
return {
|
|
156
|
+
onPointerDown: (event) => startHolding(event, direction),
|
|
157
|
+
onPointerUp: stopHolding,
|
|
158
|
+
onPointerCancel: stopHolding,
|
|
159
|
+
onLostPointerCapture: stopHolding,
|
|
160
|
+
onBlur: stopHolding,
|
|
161
|
+
onClick: (event) => {
|
|
162
|
+
if (event.detail > 0 && suppressClickRef.current) {
|
|
163
|
+
suppressClickRef.current = false;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
suppressClickRef.current = false;
|
|
167
|
+
changeStep(direction, event);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
67
170
|
}
|
|
68
171
|
const iconSize = size === 'sm' ? 18 : size === 'lg' ? 26 : 22;
|
|
69
172
|
const buttonSize = size === 'sm' ? 'icon-sm' : size === 'lg' ? 'icon-lg' : 'icon';
|
|
70
|
-
return _jsx(Input, { ...props, ref: inputRef, type: "number", inputMode: inputMode, textAlign: textAlign, clearable: false, size: size, min: min, max: max, step: step, disabled: state.disabled, readOnly: state.readOnly, value: draft, classNames: { ...classNames, root: ['inkra-input-number', classNames?.root].filter(Boolean).join(' ') },
|
|
173
|
+
return _jsx(Input, { ...props, ref: inputRef, type: "number", inputMode: inputMode, textAlign: textAlign, clearable: false, size: size, min: min, max: max, step: step, disabled: state.disabled, readOnly: state.readOnly, value: draft, classNames: { ...classNames, root: ['inkra-input-number', classNames?.root].filter(Boolean).join(' ') }, onBlur: event => { stopHolding(); onBlur?.(event); }, onKeyDown: event => {
|
|
174
|
+
onKeyDown?.(event);
|
|
175
|
+
if (event.defaultPrevented || event.nativeEvent.isComposing || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown'))
|
|
176
|
+
return;
|
|
177
|
+
if (resolveInputNumberStep(event, stepModifiers) === undefined)
|
|
178
|
+
return;
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
changeStep(event.key === 'ArrowUp' ? 'increment' : 'decrement', event);
|
|
181
|
+
}, onChange: event => {
|
|
71
182
|
const input = event.currentTarget;
|
|
72
183
|
const nextValue = Number.isFinite(input.valueAsNumber) ? input.valueAsNumber : null;
|
|
73
184
|
setDraft(input.value);
|
|
74
185
|
onChange?.(event);
|
|
75
186
|
onValueChange?.(nextValue, { reason: 'input' });
|
|
76
|
-
}, startAction: controls ? _jsx(Tooltip.Provider, { children: _jsx("span", { className: "inkra-input-number__controls", children: _jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: _jsx(Button, { type: "button", size: buttonSize, variant: "ghost", disabled: decreaseDisabled, "aria-label": decrementButtonLabel,
|
|
187
|
+
}, startAction: controls ? _jsx(Tooltip.Provider, { children: _jsx("span", { className: "inkra-input-number__controls", children: _jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: _jsx(Button, { type: "button", size: buttonSize, variant: "ghost", disabled: decreaseDisabled, "aria-label": decrementButtonLabel, ...buttonEvents('decrement'), children: _jsx(PencilIcon, { paths: minusPaths, size: iconSize, sourceSize: 24 }) }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Content, { children: decrementButtonLabel }) })] }) }) }) : undefined, endAction: controls ? _jsx(Tooltip.Provider, { children: _jsx("span", { className: "inkra-input-number__controls", children: _jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { asChild: true, children: _jsx(Button, { type: "button", size: buttonSize, variant: "ghost", disabled: increaseDisabled, "aria-label": incrementButtonLabel, ...buttonEvents('increment'), children: _jsx(PencilIcon, { paths: plusPaths, size: iconSize, sourceSize: 24 }) }) }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Content, { children: incrementButtonLabel }) })] }) }) }) : undefined });
|
|
77
188
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface InputNumberStepModifier {
|
|
2
|
+
shiftKey?: boolean;
|
|
3
|
+
altKey?: boolean;
|
|
4
|
+
ctrlKey?: boolean;
|
|
5
|
+
metaKey?: boolean;
|
|
6
|
+
step: number;
|
|
7
|
+
}
|
|
8
|
+
export type InputNumberModifierState = Pick<KeyboardEvent, 'shiftKey' | 'altKey' | 'ctrlKey' | 'metaKey'>;
|
|
9
|
+
export type InputNumberStepDirection = 'increment' | 'decrement';
|
|
10
|
+
export declare const defaultInputNumberStepModifiers: readonly InputNumberStepModifier[];
|
|
11
|
+
export declare function resolveInputNumberStep(modifiers: InputNumberModifierState, rules?: readonly InputNumberStepModifier[]): number | undefined;
|
|
12
|
+
export declare function normalizeInputNumberRepeat(value: number | undefined, fallback: number, minimum: number): number;
|
|
13
|
+
export declare function stepInputNumber(input: HTMLInputElement, direction: InputNumberStepDirection, modifierStep?: number): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const defaultInputNumberStepModifiers = [
|
|
2
|
+
{ shiftKey: true, step: 10 },
|
|
3
|
+
];
|
|
4
|
+
export function resolveInputNumberStep(modifiers, rules = defaultInputNumberStepModifiers) {
|
|
5
|
+
return rules.find(rule => Number.isFinite(rule.step) && rule.step > 0
|
|
6
|
+
&& Boolean(rule.shiftKey) === modifiers.shiftKey
|
|
7
|
+
&& Boolean(rule.altKey) === modifiers.altKey
|
|
8
|
+
&& Boolean(rule.ctrlKey) === modifiers.ctrlKey
|
|
9
|
+
&& Boolean(rule.metaKey) === modifiers.metaKey)?.step;
|
|
10
|
+
}
|
|
11
|
+
export function normalizeInputNumberRepeat(value, fallback, minimum) {
|
|
12
|
+
return value === undefined || !Number.isFinite(value)
|
|
13
|
+
? fallback
|
|
14
|
+
: Math.min(60000, Math.max(minimum, value));
|
|
15
|
+
}
|
|
16
|
+
export function stepInputNumber(input, direction, modifierStep) {
|
|
17
|
+
const before = input.value;
|
|
18
|
+
const min = input.min === '' ? -Infinity : Number(input.min);
|
|
19
|
+
const max = input.max === '' ? Infinity : Number(input.max);
|
|
20
|
+
if (input.disabled || input.readOnly || input.step === 'any' || min > max)
|
|
21
|
+
return false;
|
|
22
|
+
if ((direction === 'increment' && input.valueAsNumber >= max)
|
|
23
|
+
|| (direction === 'decrement' && input.valueAsNumber <= min))
|
|
24
|
+
return false;
|
|
25
|
+
if (modifierStep === undefined) {
|
|
26
|
+
if (direction === 'increment')
|
|
27
|
+
input.stepUp();
|
|
28
|
+
else
|
|
29
|
+
input.stepDown();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// A detached native input anchors the step base at the current value, so
|
|
33
|
+
// modifier steps are absolute deltas without floating-point arithmetic.
|
|
34
|
+
const probe = input.cloneNode(false);
|
|
35
|
+
probe.removeAttribute('min');
|
|
36
|
+
probe.removeAttribute('max');
|
|
37
|
+
probe.step = String(modifierStep);
|
|
38
|
+
probe.defaultValue = before || '0';
|
|
39
|
+
probe.value = before;
|
|
40
|
+
if (direction === 'increment')
|
|
41
|
+
probe.stepUp();
|
|
42
|
+
else
|
|
43
|
+
probe.stepDown();
|
|
44
|
+
const next = probe.valueAsNumber;
|
|
45
|
+
if (!Number.isFinite(next))
|
|
46
|
+
return false;
|
|
47
|
+
input.value = String(Math.min(Number.isFinite(max) ? max : Infinity, Math.max(Number.isFinite(min) ? min : -Infinity, next)));
|
|
48
|
+
}
|
|
49
|
+
return before !== input.value;
|
|
50
|
+
}
|
package/dist/internal/cache.d.ts
CHANGED
package/dist/internal/cache.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const pencilRenderDefaults: Readonly<{
|
|
2
|
+
strokeWidth: 2;
|
|
3
|
+
strokeRoughness: 1.5;
|
|
4
|
+
washStrength: 0.8;
|
|
5
|
+
washSpacing: 2;
|
|
6
|
+
washRoughness: 3;
|
|
7
|
+
washAngle: -45;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const maxStrokeRoughness = 4;
|
|
10
|
+
export declare function readRenderToken(style: Pick<CSSStyleDeclaration, 'getPropertyValue'>, name: string, fallback: number, min: number, max: number): number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const pencilRenderDefaults = Object.freeze({
|
|
2
|
+
strokeWidth: 2,
|
|
3
|
+
strokeRoughness: 1.5,
|
|
4
|
+
washStrength: 0.8,
|
|
5
|
+
washSpacing: 2,
|
|
6
|
+
washRoughness: 3,
|
|
7
|
+
washAngle: -45,
|
|
8
|
+
});
|
|
9
|
+
export const maxStrokeRoughness = 4;
|
|
10
|
+
export function readRenderToken(style, name, fallback, min, max) {
|
|
11
|
+
const value = Number.parseFloat(style.getPropertyValue(name));
|
|
12
|
+
return Number.isFinite(value) ? Math.max(min, Math.min(max, value)) : fallback;
|
|
13
|
+
}
|
|
@@ -4,6 +4,14 @@ export interface PencilSurfaceTheme {
|
|
|
4
4
|
dark: boolean;
|
|
5
5
|
ink: string;
|
|
6
6
|
wash: string;
|
|
7
|
+
strokeWidth?: number;
|
|
8
|
+
strokeRoughness?: number;
|
|
9
|
+
washSettings?: {
|
|
10
|
+
strength: number;
|
|
11
|
+
spacing: number;
|
|
12
|
+
roughness: number;
|
|
13
|
+
angle: number;
|
|
14
|
+
};
|
|
7
15
|
}
|
|
8
16
|
export declare const PENCIL_ACTION_WASH: Readonly<{
|
|
9
17
|
lightStrength: 0.2;
|
|
@@ -14,9 +22,9 @@ export declare const PENCIL_QUIET_WASH: Readonly<{
|
|
|
14
22
|
darkStrength: 0.08;
|
|
15
23
|
}>;
|
|
16
24
|
export declare const PENCIL_FILL_WASH: Readonly<{
|
|
17
|
-
angle: -
|
|
18
|
-
spacing:
|
|
19
|
-
roughness: 3
|
|
25
|
+
angle: -45;
|
|
26
|
+
spacing: 2;
|
|
27
|
+
roughness: 3;
|
|
20
28
|
inset: 2;
|
|
21
29
|
edgeReach: 0;
|
|
22
30
|
}>;
|
|
@@ -24,9 +32,9 @@ export declare const PENCIL_SELECTION_WASH: Readonly<{
|
|
|
24
32
|
lightStrength: 0.2;
|
|
25
33
|
darkStrength: 0.12;
|
|
26
34
|
radius: 8;
|
|
27
|
-
angle: -
|
|
28
|
-
spacing:
|
|
29
|
-
roughness: 3
|
|
35
|
+
angle: -45;
|
|
36
|
+
spacing: 2;
|
|
37
|
+
roughness: 3;
|
|
30
38
|
inset: 2;
|
|
31
39
|
edgeReach: 0;
|
|
32
40
|
}>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { pencilPaper } from './paper.js';
|
|
2
2
|
import { pencilStroke } from './stroke.js';
|
|
3
3
|
import { drawPencilWash } from './wash.js';
|
|
4
|
+
import { pencilRenderDefaults } from './renderTokens.js';
|
|
4
5
|
export const PENCIL_ACTION_WASH = Object.freeze({
|
|
5
6
|
lightStrength: 0.2,
|
|
6
7
|
darkStrength: 0.12,
|
|
@@ -28,9 +29,9 @@ const surfaceStyles = {
|
|
|
28
29
|
danger: { radius: 7, width: 0.75, passes: 2, strength: 0.18 },
|
|
29
30
|
};
|
|
30
31
|
export const PENCIL_FILL_WASH = Object.freeze({
|
|
31
|
-
angle:
|
|
32
|
-
spacing:
|
|
33
|
-
roughness:
|
|
32
|
+
angle: pencilRenderDefaults.washAngle,
|
|
33
|
+
spacing: pencilRenderDefaults.washSpacing,
|
|
34
|
+
roughness: pencilRenderDefaults.washRoughness,
|
|
34
35
|
inset: 2,
|
|
35
36
|
edgeReach: 0,
|
|
36
37
|
});
|
|
@@ -85,9 +86,10 @@ export function drawPencilSurface(canvas, borderCanvas, width, height, variant,
|
|
|
85
86
|
}
|
|
86
87
|
context.translate(bleed, bleed);
|
|
87
88
|
drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
|
|
88
|
-
strength:
|
|
89
|
-
spacing:
|
|
90
|
-
roughness:
|
|
89
|
+
strength: theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48,
|
|
90
|
+
spacing: theme.washSettings?.spacing ?? pencilRenderDefaults.washSpacing,
|
|
91
|
+
roughness: theme.washSettings?.roughness ?? pencilRenderDefaults.washRoughness,
|
|
92
|
+
angle: (theme.washSettings?.angle ?? pencilRenderDefaults.washAngle) + 35,
|
|
91
93
|
inset: 2,
|
|
92
94
|
radius: 8,
|
|
93
95
|
edgeReach: 8,
|
|
@@ -101,13 +103,13 @@ export function drawPencilSurface(canvas, borderCanvas, width, height, variant,
|
|
|
101
103
|
borderCanvas.height = canvas.height;
|
|
102
104
|
}
|
|
103
105
|
drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
|
|
104
|
-
strength: theme.dark ? PENCIL_SELECTION_WASH.darkStrength : PENCIL_SELECTION_WASH.lightStrength,
|
|
105
|
-
spacing: PENCIL_SELECTION_WASH.spacing,
|
|
106
|
-
roughness: PENCIL_SELECTION_WASH.roughness,
|
|
106
|
+
strength: (theme.dark ? PENCIL_SELECTION_WASH.darkStrength : PENCIL_SELECTION_WASH.lightStrength) * (theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48),
|
|
107
|
+
spacing: theme.washSettings?.spacing ?? PENCIL_SELECTION_WASH.spacing,
|
|
108
|
+
roughness: theme.washSettings?.roughness ?? PENCIL_SELECTION_WASH.roughness,
|
|
107
109
|
inset: PENCIL_SELECTION_WASH.inset,
|
|
108
110
|
radius: PENCIL_SELECTION_WASH.radius,
|
|
109
111
|
edgeReach: PENCIL_SELECTION_WASH.edgeReach,
|
|
110
|
-
angle: PENCIL_SELECTION_WASH.angle,
|
|
112
|
+
angle: theme.washSettings?.angle ?? PENCIL_SELECTION_WASH.angle,
|
|
111
113
|
dpr,
|
|
112
114
|
});
|
|
113
115
|
return undefined;
|
|
@@ -147,7 +149,8 @@ export function drawPencilSurface(canvas, borderCanvas, width, height, variant,
|
|
|
147
149
|
if (strength) {
|
|
148
150
|
drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
|
|
149
151
|
...PENCIL_FILL_WASH,
|
|
150
|
-
|
|
152
|
+
...theme.washSettings,
|
|
153
|
+
strength: strength * (theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48),
|
|
151
154
|
radius: resolvedRadius,
|
|
152
155
|
dpr,
|
|
153
156
|
});
|
|
@@ -164,7 +167,7 @@ export function drawPencilSurface(canvas, borderCanvas, width, height, variant,
|
|
|
164
167
|
borderContext = separateContext;
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
|
-
pencilStroke(borderContext, points, theme.ink, style.width, seed,
|
|
170
|
+
pencilStroke(borderContext, points, theme.ink, style.width * ((theme.strokeWidth ?? pencilRenderDefaults.strokeWidth) / 1.2), seed, theme.strokeRoughness ?? pencilRenderDefaults.strokeRoughness, style.passes, true, {
|
|
168
171
|
variation: 0.82,
|
|
169
172
|
breaks: variant === 'paper' ? 0.2 : 0.25,
|
|
170
173
|
grain: 0.7,
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost } from './cache.js';
|
|
1
|
+
import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable } from './cache.js';
|
|
2
2
|
import { seededRandom } from './random.js';
|
|
3
3
|
export const pencilLettering = Object.freeze({
|
|
4
|
-
variation: 0.
|
|
5
|
-
breaks: 0.
|
|
6
|
-
outline: 0.
|
|
7
|
-
fill: 0.
|
|
8
|
-
grain: 0.
|
|
4
|
+
variation: 0.2,
|
|
5
|
+
breaks: 0.5,
|
|
6
|
+
outline: 0.9,
|
|
7
|
+
fill: 0.9,
|
|
8
|
+
grain: 0.6,
|
|
9
9
|
});
|
|
10
10
|
export const pencilHandwriting = '"Trip Map Hand","HanziPen SC","Hannotate SC","Kaiti SC","STKaiti",cursive';
|
|
11
11
|
const TEXT_PIXEL_LIMIT = 524288;
|
|
12
12
|
const textCache = new BoundedCache(160, 16 * 1024 * 1024);
|
|
13
|
+
const glyphCache = new BoundedCache(512, 8 * 1024 * 1024);
|
|
13
14
|
const clamp = (value) => Math.max(0, Math.min(1, value));
|
|
14
15
|
function hash(x, y, seed) {
|
|
15
16
|
let value = Math.imul(x ^ seed, 374761393) ^ Math.imul(y, 668265263);
|
|
@@ -88,10 +89,11 @@ export function renderPencilText(text, options) {
|
|
|
88
89
|
resolution,
|
|
89
90
|
settings,
|
|
90
91
|
fontReady,
|
|
92
|
+
options.glyphSeed,
|
|
91
93
|
]) : '';
|
|
92
94
|
if (cache) {
|
|
93
95
|
const cached = textCache.get(key);
|
|
94
|
-
if (cached)
|
|
96
|
+
if (cached && canvasIsUsable(cached.ink))
|
|
95
97
|
return cached;
|
|
96
98
|
}
|
|
97
99
|
const mask = document.createElement('canvas');
|
|
@@ -120,6 +122,30 @@ export function renderPencilText(text, options) {
|
|
|
120
122
|
if (!context)
|
|
121
123
|
throw new Error('Canvas 2D is unavailable');
|
|
122
124
|
context.scale(resolution, resolution);
|
|
125
|
+
// Plain Latin labels can reuse bounded character sprites. Complex scripts and
|
|
126
|
+
// private editable text retain the original whole-line rendering path.
|
|
127
|
+
if (cache && options.glyphSeed !== undefined && /^[\x20-\x7e]+$/.test(text)) {
|
|
128
|
+
let left = 0;
|
|
129
|
+
characters.forEach((character, index) => {
|
|
130
|
+
if (character !== ' ') {
|
|
131
|
+
const characterSeed = (options.glyphSeed ^ Math.imul(character.charCodeAt(0), 16777619)
|
|
132
|
+
^ Math.imul(index % 3, 374761393)) >>> 0;
|
|
133
|
+
const glyphKey = JSON.stringify([
|
|
134
|
+
PENCIL_RENDERER_VERSION, character, size, family, color, characterSeed,
|
|
135
|
+
resolution, settings, document.fonts?.check(font, character),
|
|
136
|
+
]);
|
|
137
|
+
let glyph = glyphCache.get(glyphKey);
|
|
138
|
+
if (!glyph || !canvasIsUsable(glyph.ink)) {
|
|
139
|
+
glyph = renderPencilText(character, { ...options, seed: characterSeed, glyphSeed: undefined, cache: false });
|
|
140
|
+
glyphCache.set(glyphKey, glyph, canvasByteCost(glyph.ink));
|
|
141
|
+
}
|
|
142
|
+
context.drawImage(glyph.ink, left, 0, glyph.width, glyph.height);
|
|
143
|
+
}
|
|
144
|
+
left += widths[index];
|
|
145
|
+
});
|
|
146
|
+
const sprite = { ink: mask, width, height, advance, ascent, descent, padding };
|
|
147
|
+
return textCache.set(key, sprite, canvasByteCost(mask));
|
|
148
|
+
}
|
|
123
149
|
context.font = font;
|
|
124
150
|
context.fontKerning = 'none';
|
|
125
151
|
context.fillStyle = color;
|
|
@@ -3,6 +3,7 @@ import { PENCIL_RENDERER_VERSION, effectiveDpr } from './cache.js';
|
|
|
3
3
|
import { observeCanvasRecovery } from './recovery.js';
|
|
4
4
|
import { seedToNumber } from './random.js';
|
|
5
5
|
import { drawPencilSurface } from './surfaceRenderer.js';
|
|
6
|
+
import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from './renderTokens.js';
|
|
6
7
|
function token(style, name, fallback) {
|
|
7
8
|
return style.getPropertyValue(name).trim() || fallback;
|
|
8
9
|
}
|
|
@@ -32,6 +33,14 @@ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, c
|
|
|
32
33
|
? token(style, '--inkra-danger', '#873e36')
|
|
33
34
|
: token(style, '--inkra-border-strong', '#778575');
|
|
34
35
|
const dark = style.colorScheme.includes('dark');
|
|
36
|
+
const strokeWidth = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
|
|
37
|
+
const strokeRoughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
|
|
38
|
+
const washSettings = {
|
|
39
|
+
strength: readRenderToken(style, '--inkra-wash-strength', pencilRenderDefaults.washStrength, 0, 1) / 0.48,
|
|
40
|
+
spacing: readRenderToken(style, '--inkra-wash-spacing', pencilRenderDefaults.washSpacing, 1, 6),
|
|
41
|
+
roughness: readRenderToken(style, '--inkra-wash-roughness', pencilRenderDefaults.washRoughness, 0, 6),
|
|
42
|
+
angle: readRenderToken(style, '--inkra-wash-angle', pencilRenderDefaults.washAngle, -85, 85),
|
|
43
|
+
};
|
|
35
44
|
const wash = variant === 'selection'
|
|
36
45
|
? token(style, '--inkra-selection-pigment', dark ? '#c6b873' : '#769566')
|
|
37
46
|
: variant === 'danger'
|
|
@@ -55,11 +64,12 @@ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, c
|
|
|
55
64
|
dark,
|
|
56
65
|
clipContent,
|
|
57
66
|
radius,
|
|
67
|
+
strokeWidth, strokeRoughness, washSettings,
|
|
58
68
|
]);
|
|
59
69
|
if (paintKey === lastPaint && !canvas.getContext('2d')?.isContextLost?.())
|
|
60
70
|
return;
|
|
61
71
|
try {
|
|
62
|
-
const points = drawPencilSurface(canvas, borderRef.current, width, height, variant, numericSeed, dpr, { paper, dark, ink, wash }, radius);
|
|
72
|
+
const points = drawPencilSurface(canvas, borderRef.current, width, height, variant, numericSeed, dpr, { paper, dark, ink, wash, strokeWidth, strokeRoughness, washSettings }, radius);
|
|
63
73
|
if (clipContent && points) {
|
|
64
74
|
root.style.setProperty('--inkra-pencil-clip', `polygon(${points.map(([x, y]) => `${x / width * 100}% ${y / height * 100}%`).join(',')})`);
|
|
65
75
|
}
|
|
@@ -82,13 +92,11 @@ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, c
|
|
|
82
92
|
const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
|
|
83
93
|
resize?.observe(root);
|
|
84
94
|
window.addEventListener('resize', schedule, { passive: true });
|
|
85
|
-
const appearance = new MutationObserver(schedule);
|
|
86
|
-
appearance.observe(document.documentElement, {
|
|
87
|
-
attributes: true,
|
|
88
|
-
subtree: true,
|
|
89
|
-
attributeFilter: ['data-theme', 'data-inkra-theme'],
|
|
90
|
-
});
|
|
91
95
|
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
|
96
|
+
const inheritedStyle = new MutationObserver(schedule);
|
|
97
|
+
for (let element = root; element; element = element.parentElement) {
|
|
98
|
+
inheritedStyle.observe(element, { attributes: true, attributeFilter: ['style', 'class', 'data-theme', 'data-inkra-theme'] });
|
|
99
|
+
}
|
|
92
100
|
colorScheme.addEventListener('change', schedule);
|
|
93
101
|
const stopRecovery = observeCanvasRecovery(() => {
|
|
94
102
|
lastPaint = '';
|
|
@@ -99,7 +107,7 @@ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, c
|
|
|
99
107
|
disposed = true;
|
|
100
108
|
stopRecovery();
|
|
101
109
|
resize?.disconnect();
|
|
102
|
-
|
|
110
|
+
inheritedStyle.disconnect();
|
|
103
111
|
colorScheme.removeEventListener('change', schedule);
|
|
104
112
|
window.removeEventListener('resize', schedule);
|
|
105
113
|
cancelAnimationFrame(frame);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useRef } from 'react';
|
|
2
2
|
import { useBrowserLayoutEffect } from './useBrowserLayoutEffect.js';
|
|
3
3
|
import { PENCIL_SELECTION_WASH } from './surfaceRenderer.js';
|
|
4
|
+
import { readRenderToken } from './renderTokens.js';
|
|
4
5
|
// Reveal the cached pigment in alternating diagonal strokes, without repainting it.
|
|
5
|
-
function drawingFrames(width, height) {
|
|
6
|
-
const angle =
|
|
6
|
+
function drawingFrames(width, height, degrees) {
|
|
7
|
+
const angle = degrees * Math.PI / 180;
|
|
7
8
|
const cos = Math.cos(angle);
|
|
8
9
|
const sin = Math.sin(angle);
|
|
9
10
|
const u = (Math.abs(cos) * width + Math.abs(sin) * height) / 2 + 2;
|
|
@@ -16,14 +17,16 @@ function drawingFrames(width, height) {
|
|
|
16
17
|
const bottom = -v + (index + 1) * 2 * v / strokes;
|
|
17
18
|
// Limit travel to the visible section of this stripe, not its rotated bounds.
|
|
18
19
|
const middle = (top + bottom) / 2;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
let left = -u;
|
|
21
|
+
let right = u;
|
|
22
|
+
for (const [direction, offset, halfExtent] of [[cos, -sin * middle, width / 2], [sin, cos * middle, height / 2]]) {
|
|
23
|
+
if (Math.abs(direction) < 1e-8)
|
|
24
|
+
continue;
|
|
25
|
+
const a = (-halfExtent - offset) / direction;
|
|
26
|
+
const b = (halfExtent - offset) / direction;
|
|
27
|
+
left = Math.max(left, Math.min(a, b));
|
|
28
|
+
right = Math.min(right, Math.max(a, b));
|
|
29
|
+
}
|
|
27
30
|
for (const progress of [0, 0.5, 1]) {
|
|
28
31
|
const x = progress === 0 ? (index % 2 ? u : -u) : progress === 1 ? (index % 2 ? -u : u) : (left + right) / 2;
|
|
29
32
|
const outline = index % 2
|
|
@@ -54,7 +57,8 @@ export function useSelectionDrawing(ref, selected, animated = true) {
|
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
56
59
|
const duration = selected ? 480 : 320;
|
|
57
|
-
const
|
|
60
|
+
const angle = readRenderToken(getComputedStyle(node), '--inkra-wash-angle', PENCIL_SELECTION_WASH.angle, -85, 85);
|
|
61
|
+
const animation = node.animate(drawingFrames(node.clientWidth, node.clientHeight, angle).map(frame => ({ ...frame, opacity: 1 })), { duration, direction: selected ? 'normal' : 'reverse', easing: 'linear' });
|
|
58
62
|
animation.currentTime = (selected ? progress.current : 1 - progress.current) * duration;
|
|
59
63
|
let settled = false;
|
|
60
64
|
const finish = () => {
|
|
@@ -43,7 +43,9 @@ export function MapScale({ map }) {
|
|
|
43
43
|
draw();
|
|
44
44
|
map.on('moveend zoomend resize', draw);
|
|
45
45
|
const observer = new MutationObserver(draw);
|
|
46
|
-
|
|
46
|
+
for (let element = bar.current; element; element = element.parentElement) {
|
|
47
|
+
observer.observe(element, { attributes: true, attributeFilter: ['data-theme', 'data-inkra-theme'] });
|
|
48
|
+
}
|
|
47
49
|
const scheme = matchMedia('(prefers-color-scheme: dark)');
|
|
48
50
|
scheme.addEventListener('change', draw);
|
|
49
51
|
return () => { map.off('moveend zoomend resize', draw); observer.disconnect(); scheme.removeEventListener('change', draw); };
|
|
@@ -148,7 +148,9 @@ export function JourneyMap({ places, routes = noRoutes, geography, landCover, wa
|
|
|
148
148
|
fit(); schedule(); });
|
|
149
149
|
observer.observe(host.current);
|
|
150
150
|
const appearance = new MutationObserver(schedule);
|
|
151
|
-
|
|
151
|
+
for (let element = host.current; element; element = element.parentElement) {
|
|
152
|
+
appearance.observe(element, { attributes: true, attributeFilter: ['data-theme', 'data-inkra-theme'] });
|
|
153
|
+
}
|
|
152
154
|
const scheme = matchMedia('(prefers-color-scheme: dark)');
|
|
153
155
|
scheme.addEventListener('change', schedule);
|
|
154
156
|
fit();
|