panelui-native 0.63.0 → 0.66.1
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 +5 -0
- package/lib/module/components/badge/index.js +1 -1
- package/lib/module/components/badge/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +39 -2
- package/lib/module/components/bar-chart/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js +2 -2
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button/index.js +19 -9
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/carousel/carousel-lifecycle.js +51 -0
- package/lib/module/components/carousel/carousel-lifecycle.js.map +1 -0
- package/lib/module/components/carousel/index.js +86 -35
- package/lib/module/components/carousel/index.js.map +1 -1
- package/lib/module/components/checkbox/index.js +2 -3
- package/lib/module/components/checkbox/index.js.map +1 -1
- package/lib/module/components/context-menu/context-menu-invocation.js +23 -0
- package/lib/module/components/context-menu/context-menu-invocation.js.map +1 -0
- package/lib/module/components/context-menu/index.js +78 -9
- package/lib/module/components/context-menu/index.js.map +1 -1
- package/lib/module/components/dialog/index.js +2 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +2 -2
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/fab/index.js +4 -3
- package/lib/module/components/fab/index.js.map +1 -1
- package/lib/module/components/field/index.js +41 -6
- package/lib/module/components/field/index.js.map +1 -1
- package/lib/module/components/flow/flow-accessibility.js +100 -0
- package/lib/module/components/flow/flow-accessibility.js.map +1 -0
- package/lib/module/components/flow/flow-identifiers.js +81 -0
- package/lib/module/components/flow/flow-identifiers.js.map +1 -0
- package/lib/module/components/flow/index.js +112 -57
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/form/use-form.js +93 -66
- package/lib/module/components/form/use-form.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +2 -2
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/hex-chart/index.js +2 -2
- package/lib/module/components/hex-chart/index.js.map +1 -1
- package/lib/module/components/kpi/index.js +3 -1
- package/lib/module/components/kpi/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +3 -3
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/map/index.js +71 -8
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/map/map-accessibility.js +20 -0
- package/lib/module/components/map/map-accessibility.js.map +1 -0
- package/lib/module/components/map/maplibre.js.map +1 -1
- package/lib/module/components/menu/index.js +4 -1
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/message-scroller/index.js +17 -9
- package/lib/module/components/message-scroller/index.js.map +1 -1
- package/lib/module/components/message-scroller/message-scroller-math.js +20 -0
- package/lib/module/components/message-scroller/message-scroller-math.js.map +1 -0
- package/lib/module/components/number-input/index.js +1 -23
- package/lib/module/components/number-input/index.js.map +1 -1
- package/lib/module/components/number-input/number-input-math.js +44 -0
- package/lib/module/components/number-input/number-input-math.js.map +1 -0
- package/lib/module/components/plot/index.js +2 -2
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +1 -2
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/selection-mode/index.js +26 -31
- package/lib/module/components/selection-mode/index.js.map +1 -1
- package/lib/module/components/selection-mode/selection-mode-contracts.js +48 -0
- package/lib/module/components/selection-mode/selection-mode-contracts.js.map +1 -0
- package/lib/module/components/signature/index.js +62 -20
- package/lib/module/components/signature/index.js.map +1 -1
- package/lib/module/components/signature/signature-accessibility.js +58 -0
- package/lib/module/components/signature/signature-accessibility.js.map +1 -0
- package/lib/module/components/slider/index.js +22 -18
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/sortable/index.js +5 -55
- package/lib/module/components/sortable/index.js.map +1 -1
- package/lib/module/components/sortable/reorder.js +63 -0
- package/lib/module/components/sortable/reorder.js.map +1 -0
- package/lib/module/components/swipe/index.js +4 -4
- package/lib/module/components/swipe/index.js.map +1 -1
- package/lib/module/components/switch/index.js +36 -3
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/time-picker/accessibility.js +21 -0
- package/lib/module/components/time-picker/accessibility.js.map +1 -0
- package/lib/module/components/time-picker/index.js +34 -8
- package/lib/module/components/time-picker/index.js.map +1 -1
- package/lib/module/components/waterfall-chart/index.js +1325 -0
- package/lib/module/components/waterfall-chart/index.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/modal-isolation-store.js +29 -0
- package/lib/module/primitives/modal-isolation-store.js.map +1 -0
- package/lib/module/primitives/portal.js +36 -4
- package/lib/module/primitives/portal.js.map +1 -1
- package/lib/module/primitives/scrim.js +94 -3
- package/lib/module/primitives/scrim.js.map +1 -1
- package/lib/module/providers/panel-ui-provider.js +27 -6
- package/lib/module/providers/panel-ui-provider.js.map +1 -1
- package/lib/typescript/src/components/badge/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +3 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/carousel/carousel-lifecycle.d.ts +23 -0
- package/lib/typescript/src/components/carousel/carousel-lifecycle.d.ts.map +1 -0
- package/lib/typescript/src/components/carousel/index.d.ts +5 -2
- package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
- package/lib/typescript/src/components/checkbox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/context-menu/context-menu-invocation.d.ts +20 -0
- package/lib/typescript/src/components/context-menu/context-menu-invocation.d.ts.map +1 -0
- package/lib/typescript/src/components/context-menu/index.d.ts +18 -8
- package/lib/typescript/src/components/context-menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/dialog/index.d.ts +3 -0
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts +3 -0
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
- package/lib/typescript/src/components/field/index.d.ts +8 -1
- package/lib/typescript/src/components/field/index.d.ts.map +1 -1
- package/lib/typescript/src/components/flow/flow-accessibility.d.ts +57 -0
- package/lib/typescript/src/components/flow/flow-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/flow/flow-identifiers.d.ts +24 -0
- package/lib/typescript/src/components/flow/flow-identifiers.d.ts.map +1 -0
- package/lib/typescript/src/components/flow/index.d.ts +20 -7
- package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
- package/lib/typescript/src/components/form/use-form.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/kpi/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/index.d.ts +13 -5
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/map-accessibility.d.ts +19 -0
- package/lib/typescript/src/components/map/map-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/map/maplibre.d.ts +2 -0
- package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts +8 -0
- package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts.map +1 -0
- package/lib/typescript/src/components/number-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/number-input/number-input-math.d.ts +9 -0
- package/lib/typescript/src/components/number-input/number-input-math.d.ts.map +1 -0
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/popover/index.d.ts +3 -0
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/selection-mode/index.d.ts +6 -4
- package/lib/typescript/src/components/selection-mode/index.d.ts.map +1 -1
- package/lib/typescript/src/components/selection-mode/selection-mode-contracts.d.ts +18 -0
- package/lib/typescript/src/components/selection-mode/selection-mode-contracts.d.ts.map +1 -0
- package/lib/typescript/src/components/signature/index.d.ts +6 -0
- package/lib/typescript/src/components/signature/index.d.ts.map +1 -1
- package/lib/typescript/src/components/signature/signature-accessibility.d.ts +27 -0
- package/lib/typescript/src/components/signature/signature-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/sortable/index.d.ts.map +1 -1
- package/lib/typescript/src/components/sortable/reorder.d.ts +14 -0
- package/lib/typescript/src/components/sortable/reorder.d.ts.map +1 -0
- package/lib/typescript/src/components/swipe/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts +3 -3
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/time-picker/accessibility.d.ts +10 -0
- package/lib/typescript/src/components/time-picker/accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/waterfall-chart/index.d.ts +379 -0
- package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/modal-isolation-store.d.ts +10 -0
- package/lib/typescript/src/primitives/modal-isolation-store.d.ts.map +1 -0
- package/lib/typescript/src/primitives/portal.d.ts +6 -0
- package/lib/typescript/src/primitives/portal.d.ts.map +1 -1
- package/lib/typescript/src/primitives/scrim.d.ts +5 -3
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
- package/lib/typescript/src/providers/panel-ui-provider.d.ts.map +1 -1
- package/package.json +11 -5
- package/src/components/badge/index.tsx +4 -1
- package/src/components/bar-chart/index.tsx +40 -3
- package/src/components/bottom-sheet/index.tsx +6 -3
- package/src/components/button/index.tsx +15 -21
- package/src/components/carousel/carousel-lifecycle.ts +74 -0
- package/src/components/carousel/index.tsx +118 -41
- package/src/components/checkbox/index.tsx +1 -2
- package/src/components/context-menu/context-menu-invocation.ts +44 -0
- package/src/components/context-menu/index.tsx +113 -13
- package/src/components/dialog/index.tsx +6 -3
- package/src/components/drawer/index.tsx +6 -3
- package/src/components/fab/index.tsx +8 -12
- package/src/components/field/index.tsx +52 -10
- package/src/components/flow/flow-accessibility.ts +123 -0
- package/src/components/flow/flow-identifiers.ts +98 -0
- package/src/components/flow/index.tsx +191 -69
- package/src/components/form/use-form.ts +101 -65
- package/src/components/heatmap-chart/index.tsx +2 -3
- package/src/components/hex-chart/index.tsx +2 -3
- package/src/components/kpi/index.tsx +3 -1
- package/src/components/line-chart/index.tsx +3 -2
- package/src/components/map/index.tsx +110 -7
- package/src/components/map/map-accessibility.ts +41 -0
- package/src/components/map/maplibre.ts +2 -0
- package/src/components/menu/index.tsx +4 -1
- package/src/components/message-scroller/index.tsx +39 -7
- package/src/components/message-scroller/message-scroller-math.ts +24 -0
- package/src/components/number-input/index.tsx +1 -26
- package/src/components/number-input/number-input-math.ts +50 -0
- package/src/components/plot/index.tsx +2 -1
- package/src/components/popover/index.tsx +3 -0
- package/src/components/radio-group/index.tsx +1 -2
- package/src/components/selection-mode/index.tsx +37 -30
- package/src/components/selection-mode/selection-mode-contracts.ts +53 -0
- package/src/components/signature/index.tsx +107 -20
- package/src/components/signature/signature-accessibility.ts +67 -0
- package/src/components/slider/index.tsx +28 -18
- package/src/components/sortable/index.tsx +12 -64
- package/src/components/sortable/reorder.ts +88 -0
- package/src/components/swipe/index.tsx +7 -4
- package/src/components/switch/index.tsx +42 -6
- package/src/components/time-picker/accessibility.ts +24 -0
- package/src/components/time-picker/index.tsx +48 -2
- package/src/components/waterfall-chart/index.tsx +1589 -0
- package/src/index.ts +25 -0
- package/src/primitives/modal-isolation-store.ts +31 -0
- package/src/primitives/portal.tsx +34 -4
- package/src/primitives/scrim.tsx +127 -7
- package/src/providers/panel-ui-provider.tsx +41 -9
- package/theme.css +30 -3
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type SignatureChangeKind = 'draw' | 'undo' | 'redo' | 'clear';
|
|
2
|
+
|
|
3
|
+
export interface SignatureAccessibilityAction {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What the pad reads as when it is focused.
|
|
10
|
+
*
|
|
11
|
+
* It counts strokes and says nothing about whether the signature is finished,
|
|
12
|
+
* because the component cannot know that — one mark may be the whole signature
|
|
13
|
+
* or the first letter of it. Announcing "complete" after the first stroke tells
|
|
14
|
+
* somebody who cannot see the pad that they are done when they may not be.
|
|
15
|
+
*/
|
|
16
|
+
export function signatureAccessibilityValue(strokeCount: number) {
|
|
17
|
+
return {
|
|
18
|
+
text:
|
|
19
|
+
strokeCount === 0
|
|
20
|
+
? 'Empty signature'
|
|
21
|
+
: `Signed, ${strokeCount} ${strokeCount === 1 ? 'stroke' : 'strokes'}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function signatureAccessibilityActions(
|
|
26
|
+
strokeCount: number,
|
|
27
|
+
undoneCount: number,
|
|
28
|
+
alternative: boolean,
|
|
29
|
+
disabled: boolean
|
|
30
|
+
): SignatureAccessibilityAction[] {
|
|
31
|
+
if (disabled) return [];
|
|
32
|
+
|
|
33
|
+
const actions: SignatureAccessibilityAction[] = [];
|
|
34
|
+
if (strokeCount > 0) {
|
|
35
|
+
actions.push(
|
|
36
|
+
{ name: 'signature-undo', label: 'Undo last stroke' },
|
|
37
|
+
{ name: 'signature-clear', label: 'Clear signature' }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (undoneCount > 0) {
|
|
41
|
+
actions.push({ name: 'signature-redo', label: 'Redo last stroke' });
|
|
42
|
+
}
|
|
43
|
+
if (alternative) {
|
|
44
|
+
actions.push({ name: 'signature-alternative', label: 'Use another signature method' });
|
|
45
|
+
}
|
|
46
|
+
return actions;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Spoken when the pad crosses between empty and not, and nowhere else.
|
|
51
|
+
*
|
|
52
|
+
* Every stroke is silent on purpose: a running commentary during drawing is
|
|
53
|
+
* noise over the one gesture the reader is concentrating on. The two crossings
|
|
54
|
+
* are the ones that cannot be felt — that the pad now holds something, and that
|
|
55
|
+
* it no longer does.
|
|
56
|
+
*/
|
|
57
|
+
export function signatureAnnouncement(
|
|
58
|
+
previousCount: number,
|
|
59
|
+
strokeCount: number,
|
|
60
|
+
change: SignatureChangeKind
|
|
61
|
+
): string | null {
|
|
62
|
+
if (previousCount === strokeCount) return null;
|
|
63
|
+
if (change === 'clear') return 'Signature cleared. Signature is empty.';
|
|
64
|
+
if (strokeCount === 0) return 'Signature is empty.';
|
|
65
|
+
if (previousCount === 0) return 'Signature started.';
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
@@ -186,6 +186,12 @@ function snap(value: number, min: number, max: number, step: number) {
|
|
|
186
186
|
/** Which thumb a drag has hold of. A one-thumb slider only ever has `high`. */
|
|
187
187
|
type Thumb = 'low' | 'high';
|
|
188
188
|
|
|
189
|
+
function recordStep(last: Record<Thumb, number>, thumb: Thumb, next: number) {
|
|
190
|
+
if (next === last[thumb]) return false;
|
|
191
|
+
last[thumb] = next;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
189
195
|
export const Slider = forwardRef<View, SliderProps>(
|
|
190
196
|
(
|
|
191
197
|
{
|
|
@@ -278,9 +284,12 @@ export const Slider = forwardRef<View, SliderProps>(
|
|
|
278
284
|
rangeChangeRef.current = onRangeChange;
|
|
279
285
|
const rangeCommitRef = useRef(onRangeCommit);
|
|
280
286
|
rangeCommitRef.current = onRangeCommit;
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
const lastTick = useRef(
|
|
287
|
+
// Each thumb owns its last tick. Sharing one scalar lets the stationary
|
|
288
|
+
// endpoint suppress the thumb under the finger on a range slider.
|
|
289
|
+
const lastTick = useRef<Record<Thumb, number>>({
|
|
290
|
+
low,
|
|
291
|
+
high: isRange ? high : value,
|
|
292
|
+
});
|
|
284
293
|
/*
|
|
285
294
|
* True from the moment a finger lands until it lifts. A controlled parent
|
|
286
295
|
* echoes every change straight back as a new prop, and springing the thumb
|
|
@@ -329,8 +338,7 @@ export const Slider = forwardRef<View, SliderProps>(
|
|
|
329
338
|
(p: number, commit: boolean) => {
|
|
330
339
|
const raw = min + p * span;
|
|
331
340
|
const snapped = snap(raw, min, max, step);
|
|
332
|
-
if (haptics &&
|
|
333
|
-
lastTick.current = snapped;
|
|
341
|
+
if (haptics && recordStep(lastTick.current, 'high', snapped)) {
|
|
334
342
|
selectionTick();
|
|
335
343
|
}
|
|
336
344
|
emitChange(snapped);
|
|
@@ -340,16 +348,13 @@ export const Slider = forwardRef<View, SliderProps>(
|
|
|
340
348
|
);
|
|
341
349
|
|
|
342
350
|
const commitRangeFromProgress = useCallback(
|
|
343
|
-
(pLow: number, pHigh: number, commit: boolean) => {
|
|
351
|
+
(pLow: number, pHigh: number, commit: boolean, thumb: Thumb) => {
|
|
344
352
|
const next: [number, number] = [
|
|
345
353
|
snap(min + pLow * span, min, max, step),
|
|
346
354
|
snap(min + pHigh * span, min, max, step),
|
|
347
355
|
];
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const moved = next[0] !== lastTick.current && next[1] !== lastTick.current;
|
|
351
|
-
if (haptics && moved) {
|
|
352
|
-
lastTick.current = next[0];
|
|
356
|
+
const nextStep = thumb === 'low' ? next[0] : next[1];
|
|
357
|
+
if (haptics && recordStep(lastTick.current, thumb, nextStep)) {
|
|
353
358
|
selectionTick();
|
|
354
359
|
}
|
|
355
360
|
emitRangeChange(next);
|
|
@@ -418,12 +423,12 @@ export const Slider = forwardRef<View, SliderProps>(
|
|
|
418
423
|
const ceiling = Math.max(highProgress.value - gap, 0);
|
|
419
424
|
const next = Math.min(Math.max(dragStartLow.value + delta, 0), ceiling);
|
|
420
425
|
lowProgress.value = next;
|
|
421
|
-
runOnJS(commitRangeFromProgress)(next, highProgress.value, false);
|
|
426
|
+
runOnJS(commitRangeFromProgress)(next, highProgress.value, false, 'low');
|
|
422
427
|
} else {
|
|
423
428
|
const floor = Math.min(lowProgress.value + gap, 1);
|
|
424
429
|
const next = Math.min(Math.max(dragStartHigh.value + delta, floor), 1);
|
|
425
430
|
highProgress.value = next;
|
|
426
|
-
runOnJS(commitRangeFromProgress)(lowProgress.value, next, false);
|
|
431
|
+
runOnJS(commitRangeFromProgress)(lowProgress.value, next, false, 'high');
|
|
427
432
|
}
|
|
428
433
|
})
|
|
429
434
|
.onFinalize(() => {
|
|
@@ -431,7 +436,12 @@ export const Slider = forwardRef<View, SliderProps>(
|
|
|
431
436
|
pressedLow.value = withSpring(0, KNOB_SPRING);
|
|
432
437
|
runOnJS(setDragging)(false);
|
|
433
438
|
if (isRange) {
|
|
434
|
-
runOnJS(commitRangeFromProgress)(
|
|
439
|
+
runOnJS(commitRangeFromProgress)(
|
|
440
|
+
lowProgress.value,
|
|
441
|
+
highProgress.value,
|
|
442
|
+
true,
|
|
443
|
+
activeThumb.value
|
|
444
|
+
);
|
|
435
445
|
} else {
|
|
436
446
|
runOnJS(commitFromProgress)(highProgress.value, true);
|
|
437
447
|
}
|
|
@@ -456,11 +466,11 @@ export const Slider = forwardRef<View, SliderProps>(
|
|
|
456
466
|
if (pickThumb(event.x) === 'low') {
|
|
457
467
|
const bounded = Math.min(next, Math.max(highProgress.value - gap, 0));
|
|
458
468
|
lowProgress.value = withSpring(bounded, SPRING);
|
|
459
|
-
runOnJS(commitRangeFromProgress)(bounded, highProgress.value, true);
|
|
469
|
+
runOnJS(commitRangeFromProgress)(bounded, highProgress.value, true, 'low');
|
|
460
470
|
} else {
|
|
461
471
|
const bounded = Math.max(next, Math.min(lowProgress.value + gap, 1));
|
|
462
472
|
highProgress.value = withSpring(bounded, SPRING);
|
|
463
|
-
runOnJS(commitRangeFromProgress)(lowProgress.value, bounded, true);
|
|
473
|
+
runOnJS(commitRangeFromProgress)(lowProgress.value, bounded, true, 'high');
|
|
464
474
|
}
|
|
465
475
|
});
|
|
466
476
|
|
|
@@ -503,7 +513,7 @@ export const Slider = forwardRef<View, SliderProps>(
|
|
|
503
513
|
const next = snap(value + dir * (step || 1), min, max, step);
|
|
504
514
|
if (next === value) return;
|
|
505
515
|
if (haptics) {
|
|
506
|
-
lastTick.current
|
|
516
|
+
recordStep(lastTick.current, 'high', next);
|
|
507
517
|
selectionTick();
|
|
508
518
|
}
|
|
509
519
|
highProgress.value = withSpring(toFraction(next), SPRING);
|
|
@@ -521,7 +531,7 @@ export const Slider = forwardRef<View, SliderProps>(
|
|
|
521
531
|
const next = snap(bounded, min, max, step);
|
|
522
532
|
if (next === current) return;
|
|
523
533
|
if (haptics) {
|
|
524
|
-
lastTick.current
|
|
534
|
+
recordStep(lastTick.current, thumb, next);
|
|
525
535
|
selectionTick();
|
|
526
536
|
}
|
|
527
537
|
const pair: [number, number] = thumb === 'low' ? [next, high] : [low, next];
|
|
@@ -89,6 +89,7 @@ import { useCSSVariable } from 'uniwind';
|
|
|
89
89
|
import { GripVerticalIcon, IconColorProvider } from '../../icons';
|
|
90
90
|
import { cn } from '../../utils/cn';
|
|
91
91
|
import { impactKnock, selectionTick } from '../../utils/haptics';
|
|
92
|
+
import { moveWithPinned, stepWithPinned } from './reorder';
|
|
92
93
|
|
|
93
94
|
/**
|
|
94
95
|
* Rows getting out of the way of the one being carried. Quick, because they
|
|
@@ -228,66 +229,6 @@ function slotCenter(
|
|
|
228
229
|
return offset + (self === undefined ? 0 : (heights[self] ?? 0)) / 2;
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
/**
|
|
232
|
-
* The order after a row has moved, with pinned rows left where they were.
|
|
233
|
-
*
|
|
234
|
-
* The move is applied first, to the whole list, so a carried row can be dragged
|
|
235
|
-
* *past* a pinned one — refusing the move instead would make a pinned row a
|
|
236
|
-
* wall, and a row that holds its place is not the same as a row nothing may
|
|
237
|
-
* cross. The pinned ids are then put back at the indices they occupy in the
|
|
238
|
-
* laid-out order, and everything else falls into the slots that are left, in
|
|
239
|
-
* the order the move produced.
|
|
240
|
-
*
|
|
241
|
-
* `laid` rather than `list` is what the fixed indices are read from, because
|
|
242
|
-
* that is the one order a pinned row is guaranteed to be correctly placed in:
|
|
243
|
-
* it is where it was rendered, and holding its slot is the whole point.
|
|
244
|
-
*/
|
|
245
|
-
function moveWithPinned(
|
|
246
|
-
list: readonly string[],
|
|
247
|
-
laid: readonly string[],
|
|
248
|
-
pinned: Record<string, boolean>,
|
|
249
|
-
id: string,
|
|
250
|
-
from: number,
|
|
251
|
-
to: number
|
|
252
|
-
): string[] {
|
|
253
|
-
'worklet';
|
|
254
|
-
const moved = [...list];
|
|
255
|
-
moved.splice(from, 1);
|
|
256
|
-
moved.splice(to, 0, id);
|
|
257
|
-
|
|
258
|
-
const next: (string | undefined)[] = [];
|
|
259
|
-
let anyPinned = false;
|
|
260
|
-
for (let i = 0; i < moved.length; i += 1) next.push(undefined);
|
|
261
|
-
for (let i = 0; i < laid.length && i < next.length; i += 1) {
|
|
262
|
-
const at = laid[i];
|
|
263
|
-
if (at !== undefined && pinned[at]) {
|
|
264
|
-
next[i] = at;
|
|
265
|
-
anyPinned = true;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (!anyPinned) return moved;
|
|
269
|
-
|
|
270
|
-
const free: string[] = [];
|
|
271
|
-
for (let i = 0; i < moved.length; i += 1) {
|
|
272
|
-
const at = moved[i];
|
|
273
|
-
if (at !== undefined && !pinned[at]) free.push(at);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const result: string[] = [];
|
|
277
|
-
let f = 0;
|
|
278
|
-
for (let i = 0; i < next.length; i += 1) {
|
|
279
|
-
const held = next[i];
|
|
280
|
-
if (held !== undefined) {
|
|
281
|
-
result.push(held);
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
const take = free[f];
|
|
285
|
-
f += 1;
|
|
286
|
-
if (take !== undefined) result.push(take);
|
|
287
|
-
}
|
|
288
|
-
return result;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
232
|
/**
|
|
292
233
|
* Where the dragged row belongs now, given where its edges have reached.
|
|
293
234
|
*
|
|
@@ -760,15 +701,22 @@ function SortableRoot({
|
|
|
760
701
|
const current = rendered.value.indexOf(id);
|
|
761
702
|
if (current < 0) return;
|
|
762
703
|
|
|
763
|
-
const
|
|
764
|
-
|
|
704
|
+
const next = stepWithPinned(
|
|
705
|
+
rendered.value,
|
|
706
|
+
rendered.value,
|
|
707
|
+
pinned.value,
|
|
708
|
+
id,
|
|
709
|
+
current,
|
|
710
|
+
delta
|
|
711
|
+
);
|
|
712
|
+
const to = next.indexOf(id);
|
|
713
|
+
if (to === current) return;
|
|
765
714
|
|
|
766
|
-
const next = reorderItems(rendered.value, current, to);
|
|
767
715
|
order.value = next;
|
|
768
716
|
if (haptics) selectionTick();
|
|
769
717
|
onReorder?.(next, { id, from: current, to });
|
|
770
718
|
},
|
|
771
|
-
[haptics, onReorder, order, rendered]
|
|
719
|
+
[haptics, onReorder, order, pinned, rendered]
|
|
772
720
|
);
|
|
773
721
|
|
|
774
722
|
const context = useMemo<SortableContextValue>(
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
type PinnedItems = Readonly<Record<string, boolean>>;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Move one item, then restore pinned items to the slots in `laid`.
|
|
5
|
+
*
|
|
6
|
+
* Restoring after the move lets an item cross a pin instead of turning that
|
|
7
|
+
* slot into a wall. The laid-out order is the source of truth because it is
|
|
8
|
+
* the last arrangement the caller rendered and therefore the one whose pins
|
|
9
|
+
* are known to be in their promised positions.
|
|
10
|
+
*/
|
|
11
|
+
export function moveWithPinned(
|
|
12
|
+
list: readonly string[],
|
|
13
|
+
laid: readonly string[],
|
|
14
|
+
pinned: PinnedItems,
|
|
15
|
+
id: string,
|
|
16
|
+
from: number,
|
|
17
|
+
to: number
|
|
18
|
+
): string[] {
|
|
19
|
+
'worklet';
|
|
20
|
+
if (
|
|
21
|
+
from === to ||
|
|
22
|
+
from < 0 ||
|
|
23
|
+
from >= list.length ||
|
|
24
|
+
to < 0 ||
|
|
25
|
+
to >= list.length ||
|
|
26
|
+
list[from] !== id ||
|
|
27
|
+
pinned[id]
|
|
28
|
+
) {
|
|
29
|
+
return [...list];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const moved = [...list];
|
|
33
|
+
moved.splice(from, 1);
|
|
34
|
+
moved.splice(to, 0, id);
|
|
35
|
+
|
|
36
|
+
const next: (string | undefined)[] = [];
|
|
37
|
+
let anyPinned = false;
|
|
38
|
+
for (let i = 0; i < moved.length; i += 1) next.push(undefined);
|
|
39
|
+
for (let i = 0; i < laid.length && i < next.length; i += 1) {
|
|
40
|
+
const at = laid[i];
|
|
41
|
+
if (at !== undefined && pinned[at]) {
|
|
42
|
+
next[i] = at;
|
|
43
|
+
anyPinned = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!anyPinned) return moved;
|
|
47
|
+
|
|
48
|
+
const free: string[] = [];
|
|
49
|
+
for (let i = 0; i < moved.length; i += 1) {
|
|
50
|
+
const at = moved[i];
|
|
51
|
+
if (at !== undefined && !pinned[at]) free.push(at);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result: string[] = [];
|
|
55
|
+
let freeIndex = 0;
|
|
56
|
+
for (let i = 0; i < next.length; i += 1) {
|
|
57
|
+
const held = next[i];
|
|
58
|
+
if (held !== undefined) {
|
|
59
|
+
result.push(held);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const take = free[freeIndex];
|
|
63
|
+
freeIndex += 1;
|
|
64
|
+
if (take !== undefined) result.push(take);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Move by one available slot, stepping past pinned slots that would be a no-op. */
|
|
70
|
+
export function stepWithPinned(
|
|
71
|
+
list: readonly string[],
|
|
72
|
+
laid: readonly string[],
|
|
73
|
+
pinned: PinnedItems,
|
|
74
|
+
id: string,
|
|
75
|
+
from: number,
|
|
76
|
+
delta: number
|
|
77
|
+
): string[] {
|
|
78
|
+
'worklet';
|
|
79
|
+
const direction = Math.sign(delta);
|
|
80
|
+
if (direction === 0) return [...list];
|
|
81
|
+
|
|
82
|
+
for (let to = from + direction; to >= 0 && to < list.length; to += direction) {
|
|
83
|
+
const next = moveWithPinned(list, laid, pinned, id, from, to);
|
|
84
|
+
if (next.indexOf(id) !== from) return next;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [...list];
|
|
88
|
+
}
|
|
@@ -258,10 +258,13 @@ const actionVariants = tv({
|
|
|
258
258
|
color: {
|
|
259
259
|
default: { root: 'bg-muted-foreground', label: 'text-background' },
|
|
260
260
|
primary: { root: 'bg-primary', label: 'text-primary-foreground' },
|
|
261
|
-
success: { root: 'bg-success', label: 'text-
|
|
262
|
-
warning: { root: 'bg-warning', label: 'text-
|
|
263
|
-
info: { root: 'bg-info', label: 'text-
|
|
264
|
-
destructive: {
|
|
261
|
+
success: { root: 'bg-success', label: 'text-success-solid-foreground' },
|
|
262
|
+
warning: { root: 'bg-warning', label: 'text-warning-solid-foreground' },
|
|
263
|
+
info: { root: 'bg-info', label: 'text-info-solid-foreground' },
|
|
264
|
+
destructive: {
|
|
265
|
+
root: 'bg-destructive',
|
|
266
|
+
label: 'text-destructive-solid-foreground',
|
|
267
|
+
},
|
|
265
268
|
},
|
|
266
269
|
},
|
|
267
270
|
defaultVariants: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { forwardRef, useEffect } from 'react';
|
|
2
|
-
import { Pressable, View } from 'react-native';
|
|
2
|
+
import { Pressable, View, type PressableProps } from 'react-native';
|
|
3
3
|
import Animated, {
|
|
4
4
|
interpolate,
|
|
5
5
|
useAnimatedStyle,
|
|
@@ -10,6 +10,7 @@ import { tv, type VariantProps } from 'tailwind-variants';
|
|
|
10
10
|
import { useDirectionSign } from '../../hooks/use-direction';
|
|
11
11
|
import { getNativeUI } from '../../native';
|
|
12
12
|
import { selectionTick } from '../../utils/haptics';
|
|
13
|
+
import { useFieldLabelledBy } from '../field';
|
|
13
14
|
|
|
14
15
|
const SPRING = { damping: 18, stiffness: 250, mass: 0.5 } as const;
|
|
15
16
|
|
|
@@ -35,7 +36,22 @@ const switchVariants = tv({
|
|
|
35
36
|
|
|
36
37
|
const TRAVEL: Record<'sm' | 'md', number> = { sm: 16, md: 20 };
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Room around each track so the target reaches 48dp on both axes.
|
|
41
|
+
*
|
|
42
|
+
* Slop rather than a bigger box, because a box is layout: a switch sized to
|
|
43
|
+
* its target would stand 48dp tall in every row it has ever been placed in,
|
|
44
|
+
* and the tracks are only 24 and 28 tall. The `md` track is already 48 wide,
|
|
45
|
+
* which is why the horizontal figures differ between the two sizes.
|
|
46
|
+
*/
|
|
47
|
+
const SWITCH_HIT_SLOP: Record<'sm' | 'md', { top: number; bottom: number; left: number; right: number }> = {
|
|
48
|
+
sm: { top: 12, bottom: 12, left: 4, right: 4 },
|
|
49
|
+
md: { top: 10, bottom: 10, left: 0, right: 0 },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export interface SwitchProps
|
|
53
|
+
extends VariantProps<typeof switchVariants>,
|
|
54
|
+
Pick<PressableProps, 'accessibilityLabel' | 'accessibilityHint' | 'accessibilityLabelledBy'> {
|
|
39
55
|
className?: string;
|
|
40
56
|
value: boolean;
|
|
41
57
|
onValueChange?: (value: boolean) => void;
|
|
@@ -48,7 +64,7 @@ export interface SwitchProps extends VariantProps<typeof switchVariants> {
|
|
|
48
64
|
* `className` and `size` are ignored.
|
|
49
65
|
*/
|
|
50
66
|
native?: boolean;
|
|
51
|
-
/**
|
|
67
|
+
/** Names the control to assistive technology; native mode also draws it. */
|
|
52
68
|
label?: string;
|
|
53
69
|
/**
|
|
54
70
|
* Tick the haptic engine each time the switch is flipped — a toggle you feel
|
|
@@ -65,7 +81,19 @@ export interface SwitchProps extends VariantProps<typeof switchVariants> {
|
|
|
65
81
|
*/
|
|
66
82
|
export const Switch = forwardRef<View, SwitchProps>(
|
|
67
83
|
(
|
|
68
|
-
{
|
|
84
|
+
{
|
|
85
|
+
className,
|
|
86
|
+
value,
|
|
87
|
+
onValueChange,
|
|
88
|
+
disabled,
|
|
89
|
+
size = 'md',
|
|
90
|
+
native,
|
|
91
|
+
label,
|
|
92
|
+
haptics,
|
|
93
|
+
accessibilityLabel,
|
|
94
|
+
accessibilityHint,
|
|
95
|
+
accessibilityLabelledBy,
|
|
96
|
+
},
|
|
69
97
|
ref
|
|
70
98
|
) => {
|
|
71
99
|
const progress = useSharedValue(value ? 1 : 0);
|
|
@@ -75,6 +103,9 @@ export const Switch = forwardRef<View, SwitchProps>(
|
|
|
75
103
|
const sign = useDirectionSign();
|
|
76
104
|
const nativeUI = native ? getNativeUI() : null;
|
|
77
105
|
const slots = switchVariants({ size, disabled: !!disabled });
|
|
106
|
+
// React Native's labelled-by relationship is Android-only. `label` or an
|
|
107
|
+
// explicit accessibilityLabel remains the portable name on iOS.
|
|
108
|
+
const fieldLabelledBy = useFieldLabelledBy();
|
|
78
109
|
|
|
79
110
|
useEffect(() => {
|
|
80
111
|
progress.value = withSpring(value ? 1 : 0, SPRING);
|
|
@@ -101,7 +132,7 @@ export const Switch = forwardRef<View, SwitchProps>(
|
|
|
101
132
|
<NativeSwitch
|
|
102
133
|
value={value}
|
|
103
134
|
onValueChange={(next: boolean) => onValueChange?.(next)}
|
|
104
|
-
label={label}
|
|
135
|
+
label={label ?? accessibilityLabel}
|
|
105
136
|
disabled={disabled}
|
|
106
137
|
/>
|
|
107
138
|
</Host>
|
|
@@ -113,12 +144,17 @@ export const Switch = forwardRef<View, SwitchProps>(
|
|
|
113
144
|
ref={ref}
|
|
114
145
|
accessibilityRole="switch"
|
|
115
146
|
accessibilityState={{ checked: value, disabled: !!disabled }}
|
|
147
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
148
|
+
accessibilityHint={accessibilityHint}
|
|
149
|
+
accessibilityLabelledBy={
|
|
150
|
+
accessibilityLabelledBy ?? (accessibilityLabel || label ? undefined : fieldLabelledBy)
|
|
151
|
+
}
|
|
116
152
|
disabled={disabled}
|
|
117
153
|
onPress={() => {
|
|
118
154
|
if (haptics) selectionTick();
|
|
119
155
|
onValueChange?.(!value);
|
|
120
156
|
}}
|
|
121
|
-
hitSlop={
|
|
157
|
+
hitSlop={SWITCH_HIT_SLOP[size]}
|
|
122
158
|
>
|
|
123
159
|
<View className={slots.track({ className })}>
|
|
124
160
|
<Animated.View style={activeTrackStyle} className={slots.activeTrack()} />
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Resolve a standard adjustable action without wrapping past either bound. */
|
|
2
|
+
export function indexForAccessibilityAction(
|
|
3
|
+
index: number,
|
|
4
|
+
length: number,
|
|
5
|
+
actionName: string,
|
|
6
|
+
disabled = false
|
|
7
|
+
): number | undefined {
|
|
8
|
+
const direction = actionName === 'increment' ? 1 : actionName === 'decrement' ? -1 : 0;
|
|
9
|
+
if (disabled || direction === 0 || length <= 0) return undefined;
|
|
10
|
+
|
|
11
|
+
const next = Math.min(Math.max(index + direction, 0), length - 1);
|
|
12
|
+
return next === index ? undefined : next;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Keep the numeric adjustable range aligned with the values the face offers. */
|
|
16
|
+
export function accessibilityValueForIndex(index: number, length: number, text: string) {
|
|
17
|
+
const max = Math.max(0, length - 1);
|
|
18
|
+
return {
|
|
19
|
+
min: 0,
|
|
20
|
+
max,
|
|
21
|
+
now: Math.min(Math.max(index, 0), max),
|
|
22
|
+
text,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
import {
|
|
56
56
|
ScrollView,
|
|
57
57
|
View,
|
|
58
|
+
type AccessibilityActionEvent,
|
|
58
59
|
type NativeScrollEvent,
|
|
59
60
|
type NativeSyntheticEvent,
|
|
60
61
|
} from 'react-native';
|
|
@@ -74,6 +75,10 @@ import { ClockIcon } from '../../icons';
|
|
|
74
75
|
import { Text } from '../../primitives/text';
|
|
75
76
|
import { cn } from '../../utils/cn';
|
|
76
77
|
import { selectionTick } from '../../utils/haptics';
|
|
78
|
+
import {
|
|
79
|
+
accessibilityValueForIndex,
|
|
80
|
+
indexForAccessibilityAction,
|
|
81
|
+
} from './accessibility';
|
|
77
82
|
import {
|
|
78
83
|
clampTime,
|
|
79
84
|
displayHour,
|
|
@@ -398,6 +403,20 @@ function Column<T>({
|
|
|
398
403
|
|
|
399
404
|
const selectedItem = items[index];
|
|
400
405
|
|
|
406
|
+
const onAccessibilityAction = useCallback(
|
|
407
|
+
(event: AccessibilityActionEvent) => {
|
|
408
|
+
const next = indexForAccessibilityAction(
|
|
409
|
+
index,
|
|
410
|
+
items.length,
|
|
411
|
+
event.nativeEvent.actionName,
|
|
412
|
+
disabled
|
|
413
|
+
);
|
|
414
|
+
if (next === undefined) return;
|
|
415
|
+
onIndexChange(next);
|
|
416
|
+
},
|
|
417
|
+
[disabled, index, items.length, onIndexChange]
|
|
418
|
+
);
|
|
419
|
+
|
|
401
420
|
return (
|
|
402
421
|
<Animated.ScrollView
|
|
403
422
|
ref={ref}
|
|
@@ -426,7 +445,12 @@ function Column<T>({
|
|
|
426
445
|
removeClippedSubviews={false}
|
|
427
446
|
accessibilityRole="adjustable"
|
|
428
447
|
accessibilityLabel={accessibilityLabel}
|
|
429
|
-
|
|
448
|
+
accessibilityState={{ disabled }}
|
|
449
|
+
accessibilityValue={
|
|
450
|
+
selectedItem ? accessibilityValueForIndex(index, items.length, label(selectedItem)) : undefined
|
|
451
|
+
}
|
|
452
|
+
accessibilityActions={[{ name: 'increment' }, { name: 'decrement' }]}
|
|
453
|
+
onAccessibilityAction={onAccessibilityAction}
|
|
430
454
|
>
|
|
431
455
|
{items.map((item, i) => (
|
|
432
456
|
<ColumnRow key={i} offset={offset} index={i} selected={i === index}>
|
|
@@ -930,6 +954,21 @@ function RulerFace({
|
|
|
930
954
|
[width]
|
|
931
955
|
);
|
|
932
956
|
|
|
957
|
+
const onAccessibilityAction = useCallback(
|
|
958
|
+
(event: AccessibilityActionEvent) => {
|
|
959
|
+
const next = indexForAccessibilityAction(
|
|
960
|
+
index,
|
|
961
|
+
times.length,
|
|
962
|
+
event.nativeEvent.actionName,
|
|
963
|
+
disabled
|
|
964
|
+
);
|
|
965
|
+
if (next === undefined) return;
|
|
966
|
+
const time = times[next];
|
|
967
|
+
if (time) onValueChange(time);
|
|
968
|
+
},
|
|
969
|
+
[disabled, index, onValueChange, times]
|
|
970
|
+
);
|
|
971
|
+
|
|
933
972
|
return (
|
|
934
973
|
<View className={readout === 'none' ? undefined : 'gap-4'}>
|
|
935
974
|
{readout === 'none' ? null : (
|
|
@@ -955,7 +994,14 @@ function RulerFace({
|
|
|
955
994
|
onMomentumScrollEnd={onMomentumEnd}
|
|
956
995
|
accessibilityRole="adjustable"
|
|
957
996
|
accessibilityLabel="Time"
|
|
958
|
-
|
|
997
|
+
accessibilityState={{ disabled }}
|
|
998
|
+
accessibilityValue={accessibilityValueForIndex(
|
|
999
|
+
index,
|
|
1000
|
+
times.length,
|
|
1001
|
+
formatTime(value, { hourCycle, locale })
|
|
1002
|
+
)}
|
|
1003
|
+
accessibilityActions={[{ name: 'increment' }, { name: 'decrement' }]}
|
|
1004
|
+
onAccessibilityAction={onAccessibilityAction}
|
|
959
1005
|
>
|
|
960
1006
|
{/*
|
|
961
1007
|
No numbers under the ticks. The readout above already says the
|