panelui-native 0.65.0 → 0.68.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 +21 -1
- package/lib/module/components/area-chart/index.js +18 -1
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +18 -1
- package/lib/module/components/bar-chart/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/candlestick-chart/index.js +18 -1
- package/lib/module/components/candlestick-chart/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 +61 -24
- package/lib/module/components/carousel/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.map +1 -1
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/flow/flow-accessibility.js +18 -2
- package/lib/module/components/flow/flow-accessibility.js.map +1 -1
- 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 +12 -18
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/form/index.js +7 -5
- package/lib/module/components/form/index.js.map +1 -1
- package/lib/module/components/form/typed-form.js +11 -0
- package/lib/module/components/form/typed-form.js.map +1 -0
- package/lib/module/components/form/use-form.js +4 -0
- package/lib/module/components/form/use-form.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +27 -1
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +18 -1
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/message-scroller/index.js +187 -17
- package/lib/module/components/message-scroller/index.js.map +1 -1
- package/lib/module/components/message-scroller/message-scroller-math.js +43 -0
- package/lib/module/components/message-scroller/message-scroller-math.js.map +1 -0
- package/lib/module/components/meter/index.js +250 -0
- package/lib/module/components/meter/index.js.map +1 -0
- package/lib/module/components/meter/meter-scale.js +88 -0
- package/lib/module/components/meter/meter-scale.js.map +1 -0
- package/lib/module/components/planner/index.js +714 -0
- package/lib/module/components/planner/index.js.map +1 -0
- package/lib/module/components/planner/planner-entries.js +124 -0
- package/lib/module/components/planner/planner-entries.js.map +1 -0
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/radar-chart/index.js +18 -1
- package/lib/module/components/radar-chart/index.js.map +1 -1
- package/lib/module/components/scatter-chart/index.js +18 -1
- package/lib/module/components/scatter-chart/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/skeleton/index.js +45 -7
- package/lib/module/components/skeleton/index.js.map +1 -1
- package/lib/module/components/slider/index.js +22 -18
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/spinner/index.js +63 -14
- package/lib/module/components/spinner/index.js.map +1 -1
- package/lib/module/components/time-picker/index.js +46 -21
- package/lib/module/components/time-picker/index.js.map +1 -1
- package/lib/module/components/time-picker/ruler-window.js +25 -0
- package/lib/module/components/time-picker/ruler-window.js.map +1 -0
- package/lib/module/hooks/breakpoint-contract.js +29 -0
- package/lib/module/hooks/breakpoint-contract.js.map +1 -0
- package/lib/module/hooks/index.js +1 -1
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/use-breakpoint.js +46 -1
- package/lib/module/hooks/use-breakpoint.js.map +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/chart-accessibility.js +68 -0
- package/lib/module/primitives/chart-accessibility.js.map +1 -0
- package/lib/module/primitives/scrim.js +94 -3
- package/lib/module/primitives/scrim.js.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts +2 -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/candlestick-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/candlestick-chart/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/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/flow/flow-accessibility.d.ts +8 -0
- package/lib/typescript/src/components/flow/flow-accessibility.d.ts.map +1 -1
- 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 +6 -5
- package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
- package/lib/typescript/src/components/form/index.d.ts +18 -5
- package/lib/typescript/src/components/form/index.d.ts.map +1 -1
- package/lib/typescript/src/components/form/typed-form.d.ts +7 -0
- package/lib/typescript/src/components/form/typed-form.d.ts.map +1 -0
- package/lib/typescript/src/components/form/use-form.d.ts +4 -0
- package/lib/typescript/src/components/form/use-form.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/index.d.ts +30 -2
- 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 +18 -0
- package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts.map +1 -0
- package/lib/typescript/src/components/meter/index.d.ts +270 -0
- package/lib/typescript/src/components/meter/index.d.ts.map +1 -0
- package/lib/typescript/src/components/meter/meter-scale.d.ts +57 -0
- package/lib/typescript/src/components/meter/meter-scale.d.ts.map +1 -0
- package/lib/typescript/src/components/planner/index.d.ts +274 -0
- package/lib/typescript/src/components/planner/index.d.ts.map +1 -0
- package/lib/typescript/src/components/planner/planner-entries.d.ts +75 -0
- package/lib/typescript/src/components/planner/planner-entries.d.ts.map +1 -0
- 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/radar-chart/index.d.ts +4 -0
- package/lib/typescript/src/components/radar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/scatter-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/scatter-chart/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/skeleton/index.d.ts +25 -2
- package/lib/typescript/src/components/skeleton/index.d.ts.map +1 -1
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/spinner/index.d.ts +25 -2
- package/lib/typescript/src/components/spinner/index.d.ts.map +1 -1
- package/lib/typescript/src/components/time-picker/index.d.ts +5 -6
- package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/time-picker/ruler-window.d.ts +12 -0
- package/lib/typescript/src/components/time-picker/ruler-window.d.ts.map +1 -0
- package/lib/typescript/src/hooks/breakpoint-contract.d.ts +6 -0
- package/lib/typescript/src/hooks/breakpoint-contract.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +1 -1
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-breakpoint.d.ts +22 -0
- package/lib/typescript/src/hooks/use-breakpoint.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/chart-accessibility.d.ts +33 -0
- package/lib/typescript/src/primitives/chart-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/primitives/scrim.d.ts +5 -3
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
- package/lib/typescript/types/form-api.type-test.d.ts +2 -0
- package/lib/typescript/types/form-api.type-test.d.ts.map +1 -0
- package/package.json +34 -5
- package/src/components/area-chart/index.tsx +27 -2
- package/src/components/bar-chart/index.tsx +27 -2
- package/src/components/bottom-sheet/index.tsx +3 -0
- package/src/components/candlestick-chart/index.tsx +30 -2
- package/src/components/carousel/carousel-lifecycle.ts +74 -0
- package/src/components/carousel/index.tsx +80 -25
- 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 +3 -0
- package/src/components/drawer/index.tsx +3 -0
- package/src/components/flow/flow-accessibility.ts +16 -2
- package/src/components/flow/flow-identifiers.ts +98 -0
- package/src/components/flow/index.tsx +29 -19
- package/src/components/form/index.tsx +32 -5
- package/src/components/form/typed-form.ts +13 -0
- package/src/components/form/use-form.ts +9 -0
- package/src/components/heatmap-chart/index.tsx +49 -3
- package/src/components/line-chart/index.tsx +27 -2
- package/src/components/message-scroller/index.tsx +273 -13
- package/src/components/message-scroller/message-scroller-math.ts +62 -0
- package/src/components/meter/index.tsx +318 -0
- package/src/components/meter/meter-scale.ts +109 -0
- package/src/components/planner/index.tsx +886 -0
- package/src/components/planner/planner-entries.ts +146 -0
- package/src/components/popover/index.tsx +3 -0
- package/src/components/radar-chart/index.tsx +24 -0
- package/src/components/scatter-chart/index.tsx +27 -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/skeleton/index.tsx +59 -5
- package/src/components/slider/index.tsx +28 -18
- package/src/components/spinner/index.tsx +80 -13
- package/src/components/time-picker/index.tsx +57 -29
- package/src/components/time-picker/ruler-window.ts +24 -0
- package/src/hooks/breakpoint-contract.ts +31 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-breakpoint.ts +61 -10
- package/src/index.ts +32 -0
- package/src/primitives/chart-accessibility.ts +102 -0
- package/src/primitives/scrim.tsx +127 -7
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of a planner that are arithmetic rather than rendering: which
|
|
3
|
+
* entries fall on which day, which category an entry belongs to, what a day
|
|
4
|
+
* says to a screen reader, and what the month adds up to.
|
|
5
|
+
*
|
|
6
|
+
* Kept out of the component so they can be tested without a renderer, and so
|
|
7
|
+
* the day cell stays a function of its inputs rather than of when it ran.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here imports anything. The tests run this file through `node --test`
|
|
10
|
+
* with type stripping rather than a bundler, so an extensionless import of a
|
|
11
|
+
* sibling module — which is how the rest of the library is written — does not
|
|
12
|
+
* resolve. That is why the one line of date arithmetic below is here instead
|
|
13
|
+
* of coming from `utils/date`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The minimum an entry has to be for the grid to place it. */
|
|
17
|
+
export interface PlannerDatedEntry {
|
|
18
|
+
date: Date;
|
|
19
|
+
/** Matches a `PlannerCategory` id. An entry without one is uncategorised. */
|
|
20
|
+
category?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PlannerCountedCategory {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
count: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A day's key: local midnight, so two times on the same day agree. */
|
|
30
|
+
export function dayKey(date: Date): number {
|
|
31
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Entries grouped by the day they fall on, each day in the order it was given.
|
|
36
|
+
*
|
|
37
|
+
* A map rather than a lookup per cell: a month draws 42 cells, and asking each
|
|
38
|
+
* of them to filter the whole list is 42 passes over it every render.
|
|
39
|
+
*/
|
|
40
|
+
export function bucketByDay<T extends PlannerDatedEntry>(
|
|
41
|
+
entries: readonly T[]
|
|
42
|
+
): Map<number, T[]> {
|
|
43
|
+
const days = new Map<number, T[]>();
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const key = dayKey(entry.date);
|
|
46
|
+
const bucket = days.get(key);
|
|
47
|
+
if (bucket) bucket.push(entry);
|
|
48
|
+
else days.set(key, [entry]);
|
|
49
|
+
}
|
|
50
|
+
return days;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** What falls on one day. Always an array, so a cell has nothing to guard. */
|
|
54
|
+
export function entriesOn<T extends PlannerDatedEntry>(
|
|
55
|
+
days: Map<number, T[]>,
|
|
56
|
+
date: Date
|
|
57
|
+
): T[] {
|
|
58
|
+
return days.get(dayKey(date)) ?? EMPTY;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* One frozen array for every empty day, rather than a new `[]` each call. A
|
|
63
|
+
* month is mostly empty days, and a fresh array per cell per render defeats
|
|
64
|
+
* every memo below it for a day where nothing changed.
|
|
65
|
+
*/
|
|
66
|
+
const EMPTY: never[] = Object.freeze([]) as never[];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* How many entries a cell draws before it stops and says how many are left.
|
|
70
|
+
*
|
|
71
|
+
* `limit` of 0 means draw none of them — the day still carries its marker and
|
|
72
|
+
* its spoken label, which is the whole content of a compact planner.
|
|
73
|
+
*/
|
|
74
|
+
export function visibleEntries<T>(entries: readonly T[], limit: number): {
|
|
75
|
+
shown: T[];
|
|
76
|
+
overflow: number;
|
|
77
|
+
} {
|
|
78
|
+
const cap = Math.max(0, Math.floor(limit));
|
|
79
|
+
if (entries.length <= cap) return { shown: [...entries], overflow: 0 };
|
|
80
|
+
return { shown: entries.slice(0, cap), overflow: entries.length - cap };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* What a day says when it is read out.
|
|
85
|
+
*
|
|
86
|
+
* The date on its own is what a date picker says, and it is not enough here: a
|
|
87
|
+
* day in a planner differs from the one beside it by what is on it, and a
|
|
88
|
+
* marker that carries that difference in colour alone carries it to nobody who
|
|
89
|
+
* cannot see the colour. So the count and the categories are spoken.
|
|
90
|
+
*
|
|
91
|
+
* `dateLabel` is passed in rather than formatted here, so the grid and the
|
|
92
|
+
* label agree about the calendar system and the locale.
|
|
93
|
+
*/
|
|
94
|
+
export function dayAccessibilityLabel<T extends PlannerDatedEntry>(
|
|
95
|
+
dateLabel: string,
|
|
96
|
+
entries: readonly T[],
|
|
97
|
+
categoryLabels: ReadonlyMap<string, string>
|
|
98
|
+
): string {
|
|
99
|
+
if (entries.length === 0) return `${dateLabel}, nothing planned`;
|
|
100
|
+
|
|
101
|
+
const named: string[] = [];
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const label = entry.category ? categoryLabels.get(entry.category) : undefined;
|
|
104
|
+
if (label && !named.includes(label)) named.push(label);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const count = `${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}`;
|
|
108
|
+
return named.length > 0
|
|
109
|
+
? `${dateLabel}, ${count}: ${named.join(', ')}`
|
|
110
|
+
: `${dateLabel}, ${count}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* What the month adds up to, per category, in the order the categories were
|
|
115
|
+
* declared — so the legend and the totals beside it never disagree about which
|
|
116
|
+
* one comes first.
|
|
117
|
+
*
|
|
118
|
+
* Entries outside `month` are left out. The grid draws the days either side of
|
|
119
|
+
* the month it is showing, and counting those would make a total that changes
|
|
120
|
+
* with the week the month happens to start on.
|
|
121
|
+
*/
|
|
122
|
+
export function summariseMonth<T extends PlannerDatedEntry>(
|
|
123
|
+
entries: readonly T[],
|
|
124
|
+
categories: readonly { id: string; label: string }[],
|
|
125
|
+
isInMonth: (date: Date) => boolean
|
|
126
|
+
): { total: number; categories: PlannerCountedCategory[] } {
|
|
127
|
+
const counts = new Map<string, number>();
|
|
128
|
+
let total = 0;
|
|
129
|
+
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
if (!isInMonth(entry.date)) continue;
|
|
132
|
+
total += 1;
|
|
133
|
+
if (entry.category) {
|
|
134
|
+
counts.set(entry.category, (counts.get(entry.category) ?? 0) + 1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
total,
|
|
140
|
+
categories: categories.map(({ id, label }) => ({
|
|
141
|
+
id,
|
|
142
|
+
label,
|
|
143
|
+
count: counts.get(id) ?? 0,
|
|
144
|
+
})),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -321,6 +321,9 @@ export interface PopoverContentProps extends ViewProps {
|
|
|
321
321
|
* Frost the background behind the panel instead of dimming it. Uses
|
|
322
322
|
* `expo-blur` when installed and falls back to the dimmed scrim when it is
|
|
323
323
|
* not, so it is safe to pass either way.
|
|
324
|
+
*
|
|
325
|
+
* Someone who has Reduce Transparency switched on gets an opaque
|
|
326
|
+
* backdrop instead, which is the whole point of the setting.
|
|
324
327
|
*/
|
|
325
328
|
blur?: boolean;
|
|
326
329
|
/**
|
|
@@ -59,6 +59,7 @@ import Animated, {
|
|
|
59
59
|
} from 'react-native-reanimated';
|
|
60
60
|
import { useCSSVariable } from 'uniwind';
|
|
61
61
|
import { Text } from '../../primitives/text';
|
|
62
|
+
import { ChartAccessibilityData, type ChartAccessibilityProps } from '../../primitives/chart-accessibility';
|
|
62
63
|
import { cn } from '../../utils/cn';
|
|
63
64
|
import {
|
|
64
65
|
polarPoint,
|
|
@@ -212,6 +213,9 @@ export interface RadarChartProps extends Omit<ViewProps, 'children'> {
|
|
|
212
213
|
/** Drop the room reserved for axis labels, for a radar with none. */
|
|
213
214
|
compact?: boolean;
|
|
214
215
|
children?: ReactNode;
|
|
216
|
+
/** Accessible data labels and optional activation for each axis row. */
|
|
217
|
+
accessibilityLabelForDatum?: ChartAccessibilityProps<RadarChartDatum>['accessibilityLabelForDatum'];
|
|
218
|
+
onAccessibilityDatumPress?: ChartAccessibilityProps<RadarChartDatum>['onAccessibilityDatumPress'];
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
/** Imperative handle: re-run the reveal on demand, for a "replay" control. */
|
|
@@ -231,6 +235,11 @@ const RadarChartRoot = forwardRef<RadarChartHandle, RadarChartProps>(
|
|
|
231
235
|
domain,
|
|
232
236
|
animationDuration = REVEAL_DURATION,
|
|
233
237
|
compact = false,
|
|
238
|
+
accessible,
|
|
239
|
+
accessibilityLabel,
|
|
240
|
+
accessibilityHint,
|
|
241
|
+
accessibilityLabelForDatum,
|
|
242
|
+
onAccessibilityDatumPress,
|
|
234
243
|
children,
|
|
235
244
|
...props
|
|
236
245
|
},
|
|
@@ -393,6 +402,19 @@ const RadarChartRoot = forwardRef<RadarChartHandle, RadarChartProps>(
|
|
|
393
402
|
<RadarChartContext.Provider value={context}>
|
|
394
403
|
<View {...props} style={props.style} className={cn('w-full', className)}>
|
|
395
404
|
{header}
|
|
405
|
+
<ChartAccessibilityData
|
|
406
|
+
chart="Radar chart"
|
|
407
|
+
data={data}
|
|
408
|
+
disabled={accessible === false || status === 'loading'}
|
|
409
|
+
accessibilityLabel={accessibilityLabel}
|
|
410
|
+
accessibilityHint={accessibilityHint}
|
|
411
|
+
accessibilityLabelForDatum={accessibilityLabelForDatum}
|
|
412
|
+
onAccessibilityDatumPress={onAccessibilityDatumPress}
|
|
413
|
+
valueOf={(datum) => [
|
|
414
|
+
[axisKey, datum[axisKey]],
|
|
415
|
+
...series.map(([key]) => [key, datum[key]] as [string, unknown]),
|
|
416
|
+
]}
|
|
417
|
+
/>
|
|
396
418
|
<View
|
|
397
419
|
onLayout={onLayout}
|
|
398
420
|
style={
|
|
@@ -407,6 +429,8 @@ const RadarChartRoot = forwardRef<RadarChartHandle, RadarChartProps>(
|
|
|
407
429
|
: { aspectRatio }
|
|
408
430
|
}
|
|
409
431
|
className={fixedSize ? 'self-center' : 'w-full'}
|
|
432
|
+
accessibilityElementsHidden
|
|
433
|
+
importantForAccessibility="no-hide-descendants"
|
|
410
434
|
>
|
|
411
435
|
{radius > 0 ? (
|
|
412
436
|
<>
|
|
@@ -67,6 +67,7 @@ import Animated, {
|
|
|
67
67
|
import Svg, { Circle, G, Line as SvgLine } from 'react-native-svg';
|
|
68
68
|
import { useCSSVariable } from 'uniwind';
|
|
69
69
|
import { Text } from '../../primitives/text';
|
|
70
|
+
import { ChartAccessibilityData, type ChartAccessibilityProps } from '../../primitives/chart-accessibility';
|
|
70
71
|
import { compactNumber, useSeriesColor, xAt, yOf, type Plot } from '../../utils/chart';
|
|
71
72
|
import { cn } from '../../utils/cn';
|
|
72
73
|
|
|
@@ -191,7 +192,7 @@ export function useScatterChart() {
|
|
|
191
192
|
return { activePoint, xDataKey };
|
|
192
193
|
}
|
|
193
194
|
|
|
194
|
-
export interface ScatterChartProps extends ViewProps {
|
|
195
|
+
export interface ScatterChartProps extends ViewProps, ChartAccessibilityProps<ScatterChartDatum> {
|
|
195
196
|
className?: string;
|
|
196
197
|
/** The rows. Each one is a point, placed by two of its values. */
|
|
197
198
|
data: ScatterChartDatum[];
|
|
@@ -244,6 +245,11 @@ const ScatterChartRoot = forwardRef<ScatterChartHandle, ScatterChartProps>(
|
|
|
244
245
|
xDomain,
|
|
245
246
|
yDomain,
|
|
246
247
|
onActivePointChange,
|
|
248
|
+
accessible,
|
|
249
|
+
accessibilityLabel,
|
|
250
|
+
accessibilityHint,
|
|
251
|
+
accessibilityLabelForDatum,
|
|
252
|
+
onAccessibilityDatumPress,
|
|
247
253
|
compact = false,
|
|
248
254
|
children,
|
|
249
255
|
...props
|
|
@@ -476,7 +482,26 @@ const ScatterChartRoot = forwardRef<ScatterChartHandle, ScatterChartProps>(
|
|
|
476
482
|
<ScatterChartContext.Provider value={context}>
|
|
477
483
|
<View {...props} style={props.style} className={cn('w-full', className)}>
|
|
478
484
|
{header}
|
|
479
|
-
<
|
|
485
|
+
<ChartAccessibilityData
|
|
486
|
+
chart="Scatter chart"
|
|
487
|
+
data={data}
|
|
488
|
+
disabled={accessible === false || status === 'loading'}
|
|
489
|
+
accessibilityLabel={accessibilityLabel}
|
|
490
|
+
accessibilityHint={accessibilityHint}
|
|
491
|
+
accessibilityLabelForDatum={accessibilityLabelForDatum}
|
|
492
|
+
onAccessibilityDatumPress={onAccessibilityDatumPress}
|
|
493
|
+
valueOf={(datum) => [
|
|
494
|
+
[xDataKey, datum[xDataKey]],
|
|
495
|
+
...series.map(([key]) => [key, datum[key]] as [string, unknown]),
|
|
496
|
+
]}
|
|
497
|
+
/>
|
|
498
|
+
<View
|
|
499
|
+
onLayout={onLayout}
|
|
500
|
+
style={{ aspectRatio }}
|
|
501
|
+
className="w-full"
|
|
502
|
+
accessibilityElementsHidden
|
|
503
|
+
importantForAccessibility="no-hide-descendants"
|
|
504
|
+
>
|
|
480
505
|
{plot.width > 0 ? (
|
|
481
506
|
<>
|
|
482
507
|
<Svg width="100%" height="100%" style={StyleSheet.absoluteFill}>
|
|
@@ -88,6 +88,15 @@ import { Text, textChildren } from '../../primitives/text';
|
|
|
88
88
|
import { cn } from '../../utils/cn';
|
|
89
89
|
import { selectionTick } from '../../utils/haptics';
|
|
90
90
|
import { BottomSheet } from '../bottom-sheet';
|
|
91
|
+
import {
|
|
92
|
+
canEnterSelection,
|
|
93
|
+
handleSelectionItemPress,
|
|
94
|
+
selectAllValues,
|
|
95
|
+
selectionOwnsPress,
|
|
96
|
+
selectionTarget,
|
|
97
|
+
selectValue,
|
|
98
|
+
toggleValue,
|
|
99
|
+
} from './selection-mode-contracts';
|
|
91
100
|
|
|
92
101
|
/** How long the circle takes to come and go, in milliseconds. */
|
|
93
102
|
const REVEAL_DURATION = 180;
|
|
@@ -293,11 +302,12 @@ function SelectionModeRoot({
|
|
|
293
302
|
// Entering with the row that was pressed already picked. Entering with
|
|
294
303
|
// nothing picked leaves the reader in a list that has changed under them
|
|
295
304
|
// with nothing to show for it.
|
|
296
|
-
if (value !== undefined
|
|
297
|
-
|
|
305
|
+
if (value !== undefined) {
|
|
306
|
+
const next = selectValue(selectedRef.current, value, max);
|
|
307
|
+
if (next !== selectedRef.current) setSelected(next);
|
|
298
308
|
}
|
|
299
309
|
},
|
|
300
|
-
[haptics, setActive, setSelected]
|
|
310
|
+
[haptics, max, setActive, setSelected]
|
|
301
311
|
);
|
|
302
312
|
|
|
303
313
|
/*
|
|
@@ -321,13 +331,10 @@ function SelectionModeRoot({
|
|
|
321
331
|
const toggle = useCallback(
|
|
322
332
|
(value: string) => {
|
|
323
333
|
const current = selectedRef.current;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (haptics) selectionTick();
|
|
329
|
-
setSelected([...current, value]);
|
|
330
|
-
}
|
|
334
|
+
const next = toggleValue(current, value, max);
|
|
335
|
+
if (next === current) return;
|
|
336
|
+
if (!current.includes(value) && haptics) selectionTick();
|
|
337
|
+
setSelected(next);
|
|
331
338
|
},
|
|
332
339
|
[max, haptics, setSelected]
|
|
333
340
|
);
|
|
@@ -336,15 +343,15 @@ function SelectionModeRoot({
|
|
|
336
343
|
if (!values) return;
|
|
337
344
|
// At the limit rather than refusing: somebody who asked for all of them and
|
|
338
345
|
// can only have twenty wants the twenty, not an error.
|
|
339
|
-
setSelected(
|
|
346
|
+
setSelected(selectAllValues(values, max));
|
|
340
347
|
}, [values, max, setSelected]);
|
|
341
348
|
|
|
342
349
|
const clear = useCallback(() => setSelected([]), [setSelected]);
|
|
343
350
|
|
|
344
351
|
const total = values?.length ?? 0;
|
|
345
352
|
const count = selected.length;
|
|
346
|
-
const
|
|
347
|
-
|
|
353
|
+
const target = selectionTarget(total, max);
|
|
354
|
+
const allSelected = target > 0 && count >= target;
|
|
348
355
|
|
|
349
356
|
// An open mode owns the back button: back should leave the mode, not the
|
|
350
357
|
// screen the list is on.
|
|
@@ -459,10 +466,11 @@ export interface SelectionModeItemProps extends Omit<ViewProps, 'children'> {
|
|
|
459
466
|
className?: string;
|
|
460
467
|
/** This row's id. What ends up in `selected`. */
|
|
461
468
|
value: string;
|
|
462
|
-
/** What the row does
|
|
469
|
+
/** What the row does whenever selection does not own its press. */
|
|
463
470
|
onPress?: () => void;
|
|
464
471
|
/**
|
|
465
|
-
* Stop this row entering selection mode, and being picked once in it.
|
|
472
|
+
* Stop this row entering selection mode, and being picked once in it. Its
|
|
473
|
+
* ordinary `onPress` still runs, including while selection is active. For a
|
|
466
474
|
* header row, an advert, a "load more" — anything in the list that is not one
|
|
467
475
|
* of the things being chosen between.
|
|
468
476
|
*/
|
|
@@ -487,8 +495,9 @@ export interface SelectionModeItemProps extends Omit<ViewProps, 'children'> {
|
|
|
487
495
|
*
|
|
488
496
|
* The press behaviour is the whole component: off mode, a press is the row's
|
|
489
497
|
* own and a long press turns the mode on with this row picked; in it, a press
|
|
490
|
-
* picks and unpicks
|
|
491
|
-
* one gesture is exactly why the mode has to be
|
|
498
|
+
* picks and unpicks. A row excluded from selection keeps its ordinary press in
|
|
499
|
+
* both states. Two meanings for one gesture is exactly why the mode has to be
|
|
500
|
+
* visible from the header.
|
|
492
501
|
*/
|
|
493
502
|
function SelectionModeItem({
|
|
494
503
|
className,
|
|
@@ -503,6 +512,7 @@ function SelectionModeItem({
|
|
|
503
512
|
const { active, enter, toggle, isSelected, sheet } = useSelectionMode();
|
|
504
513
|
const selected = isSelected(value);
|
|
505
514
|
const showing = alwaysShowIndicator || active;
|
|
515
|
+
const selecting = selectionOwnsPress(active, disabled);
|
|
506
516
|
const reducedMotion = useReducedMotion();
|
|
507
517
|
|
|
508
518
|
const context = useMemo(() => ({ value }), [value]);
|
|
@@ -537,23 +547,20 @@ function SelectionModeItem({
|
|
|
537
547
|
return (
|
|
538
548
|
<SelectionModeItemContext.Provider value={context}>
|
|
539
549
|
<Pressable
|
|
540
|
-
accessibilityRole={
|
|
541
|
-
accessibilityState={
|
|
542
|
-
//
|
|
543
|
-
//
|
|
550
|
+
accessibilityRole={selecting ? 'checkbox' : 'button'}
|
|
551
|
+
accessibilityState={selecting ? { checked: selected } : undefined}
|
|
552
|
+
// Eligible rows become checkboxes only while selection owns their
|
|
553
|
+
// press. Excluded rows remain ordinary buttons because their action is
|
|
554
|
+
// still available.
|
|
544
555
|
accessibilityHint={
|
|
545
|
-
|
|
556
|
+
active || disabled ? undefined : 'Long press to select'
|
|
546
557
|
}
|
|
547
|
-
disabled={disabled && showing}
|
|
548
558
|
onPress={() => {
|
|
549
|
-
|
|
550
|
-
if (showing) toggle(value);
|
|
551
|
-
else onPress?.();
|
|
552
|
-
}}
|
|
553
|
-
onLongPress={sheet ? undefined : () => {
|
|
554
|
-
if (disabled || showing) return;
|
|
555
|
-
enter(value);
|
|
559
|
+
handleSelectionItemPress(active, disabled, () => toggle(value), onPress);
|
|
556
560
|
}}
|
|
561
|
+
onLongPress={
|
|
562
|
+
canEnterSelection(active, disabled, sheet) ? () => enter(value) : undefined
|
|
563
|
+
}
|
|
557
564
|
className={cn(
|
|
558
565
|
indicator === 'leading' ? 'flex-row items-center gap-3 px-4 py-2.5' : '',
|
|
559
566
|
className
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** The effective selection cap. Invalid and negative caps allow no additions. */
|
|
2
|
+
function selectionLimit(max?: number): number {
|
|
3
|
+
if (max === undefined || max === Infinity) return Infinity;
|
|
4
|
+
return Number.isFinite(max) ? Math.max(0, Math.floor(max)) : 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Add one value without duplicating it or crossing the configured cap. */
|
|
8
|
+
export function selectValue(selected: string[], value: string, max?: number): string[] {
|
|
9
|
+
if (selected.includes(value) || !(selected.length < selectionLimit(max))) return selected;
|
|
10
|
+
return [...selected, value];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Remove an existing value, or add it under the same contract used by entry. */
|
|
14
|
+
export function toggleValue(selected: string[], value: string, max?: number): string[] {
|
|
15
|
+
return selected.includes(value)
|
|
16
|
+
? selected.filter((entry) => entry !== value)
|
|
17
|
+
: selectValue(selected, value, max);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The values produced by select-all, capped by the same normalized limit. */
|
|
21
|
+
export function selectAllValues(values: string[], max?: number): string[] {
|
|
22
|
+
const limit = selectionLimit(max);
|
|
23
|
+
return limit === Infinity ? [...values] : values.slice(0, limit);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** How many selections constitute all selectable values. */
|
|
27
|
+
export function selectionTarget(total: number, max?: number): number {
|
|
28
|
+
return Math.min(total, selectionLimit(max));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A selection-ineligible row keeps its ordinary action in every mode. Only an
|
|
33
|
+
* active, eligible row gives its press to selection.
|
|
34
|
+
*/
|
|
35
|
+
export function selectionOwnsPress(active: boolean, disabled: boolean): boolean {
|
|
36
|
+
return active && !disabled;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Execute the one action owned by a row press under the interaction contract. */
|
|
40
|
+
export function handleSelectionItemPress(
|
|
41
|
+
active: boolean,
|
|
42
|
+
disabled: boolean,
|
|
43
|
+
toggle: () => void,
|
|
44
|
+
action?: () => void
|
|
45
|
+
): void {
|
|
46
|
+
if (selectionOwnsPress(active, disabled)) toggle();
|
|
47
|
+
else action?.();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Long press enters only from an inactive screen row that can be selected. */
|
|
51
|
+
export function canEnterSelection(active: boolean, disabled: boolean, sheet: boolean): boolean {
|
|
52
|
+
return !active && !disabled && !sheet;
|
|
53
|
+
}
|
|
@@ -58,7 +58,12 @@ import {
|
|
|
58
58
|
type ReactNode,
|
|
59
59
|
type Ref,
|
|
60
60
|
} from 'react';
|
|
61
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
AccessibilityInfo,
|
|
63
|
+
View,
|
|
64
|
+
type AccessibilityActionEvent,
|
|
65
|
+
type ViewProps,
|
|
66
|
+
} from 'react-native';
|
|
62
67
|
import { useCSSVariable } from 'uniwind';
|
|
63
68
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
64
69
|
import Animated, {
|
|
@@ -81,6 +86,12 @@ import {
|
|
|
81
86
|
} from '../../primitives/animated-pressable';
|
|
82
87
|
import { Text } from '../../primitives/text';
|
|
83
88
|
import { cn } from '../../utils/cn';
|
|
89
|
+
import {
|
|
90
|
+
signatureAccessibilityActions,
|
|
91
|
+
signatureAccessibilityValue,
|
|
92
|
+
signatureAnnouncement,
|
|
93
|
+
type SignatureChangeKind,
|
|
94
|
+
} from './signature-accessibility';
|
|
84
95
|
|
|
85
96
|
const AnimatedPath = Animated.createAnimatedComponent(Path);
|
|
86
97
|
|
|
@@ -175,6 +186,12 @@ export interface SignatureProps
|
|
|
175
186
|
placeholder?: ReactNode;
|
|
176
187
|
/** Take no input. The strokes already there stay visible. */
|
|
177
188
|
disabled?: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* Opens a product-provided non-drawing method, such as typing a legal name,
|
|
191
|
+
* uploading an image, or asking for assisted signing. Exposed as a screen
|
|
192
|
+
* reader action; provide the same choice as a visible control too.
|
|
193
|
+
*/
|
|
194
|
+
onRequestAlternative?: () => void;
|
|
178
195
|
/** A stroke has started. */
|
|
179
196
|
onBegin?: () => void;
|
|
180
197
|
/** A stroke has finished. */
|
|
@@ -292,12 +309,17 @@ function SignatureRoot(
|
|
|
292
309
|
guidelineLabel,
|
|
293
310
|
placeholder,
|
|
294
311
|
disabled = false,
|
|
312
|
+
onRequestAlternative,
|
|
295
313
|
onBegin,
|
|
296
314
|
onEnd,
|
|
297
315
|
onChange,
|
|
298
316
|
padClassName,
|
|
299
317
|
placeholderClassName,
|
|
300
318
|
guideClassName,
|
|
319
|
+
accessibilityLabel,
|
|
320
|
+
accessibilityHint,
|
|
321
|
+
accessibilityActions,
|
|
322
|
+
onAccessibilityAction,
|
|
301
323
|
...props
|
|
302
324
|
}: SignatureProps,
|
|
303
325
|
ref: Ref<SignatureHandle>
|
|
@@ -325,19 +347,30 @@ function SignatureRoot(
|
|
|
325
347
|
beginRef.current = onBegin;
|
|
326
348
|
const endRef = useRef(onEnd);
|
|
327
349
|
endRef.current = onEnd;
|
|
350
|
+
const alternativeRef = useRef(onRequestAlternative);
|
|
351
|
+
alternativeRef.current = onRequestAlternative;
|
|
328
352
|
|
|
329
353
|
// `onChange` fires from an effect rather than from inside a state updater.
|
|
330
354
|
// An updater can be replayed during a render, and calling a parent's setState
|
|
331
355
|
// from there is the "cannot update a component while rendering a different
|
|
332
356
|
// component" warning — earned, not spurious.
|
|
333
357
|
const reported = useRef(0);
|
|
358
|
+
const changeKind = useRef<SignatureChangeKind>('draw');
|
|
334
359
|
useEffect(() => {
|
|
335
360
|
if (reported.current === strokes.length) return;
|
|
361
|
+
const previous = reported.current;
|
|
336
362
|
reported.current = strokes.length;
|
|
337
363
|
changeRef.current?.(strokes.length);
|
|
364
|
+
const announcement = signatureAnnouncement(
|
|
365
|
+
previous,
|
|
366
|
+
strokes.length,
|
|
367
|
+
changeKind.current
|
|
368
|
+
);
|
|
369
|
+
if (announcement) AccessibilityInfo.announceForAccessibility(announcement);
|
|
338
370
|
}, [strokes.length]);
|
|
339
371
|
|
|
340
372
|
const commit = useCallback((d: string) => {
|
|
373
|
+
changeKind.current = 'draw';
|
|
341
374
|
setStrokes((current) => [...current, d]);
|
|
342
375
|
// Drawing again is a new branch of history, so what was undone is gone.
|
|
343
376
|
setUndone([]);
|
|
@@ -419,28 +452,37 @@ function SignatureRoot(
|
|
|
419
452
|
[layout.height, layout.width]
|
|
420
453
|
);
|
|
421
454
|
|
|
455
|
+
const clear = useCallback(() => {
|
|
456
|
+
changeKind.current = 'clear';
|
|
457
|
+
setStrokes([]);
|
|
458
|
+
setUndone([]);
|
|
459
|
+
}, []);
|
|
460
|
+
|
|
461
|
+
const undo = useCallback(() => {
|
|
462
|
+
const last = strokes[strokes.length - 1];
|
|
463
|
+
if (last === undefined) return;
|
|
464
|
+
changeKind.current = 'undo';
|
|
465
|
+
setUndone((stack) => [...stack, last]);
|
|
466
|
+
setStrokes(strokes.slice(0, -1));
|
|
467
|
+
}, [strokes]);
|
|
468
|
+
|
|
469
|
+
const redo = useCallback(() => {
|
|
470
|
+
const restored = undone[undone.length - 1];
|
|
471
|
+
if (restored === undefined) return;
|
|
472
|
+
changeKind.current = 'redo';
|
|
473
|
+
setUndone(undone.slice(0, -1));
|
|
474
|
+
setStrokes([...strokes, restored]);
|
|
475
|
+
}, [strokes, undone]);
|
|
476
|
+
|
|
422
477
|
useImperativeHandle(
|
|
423
478
|
ref,
|
|
424
479
|
(): SignatureHandle => ({
|
|
425
480
|
// These read the current arrays and set flat values rather than nesting
|
|
426
481
|
// one updater inside another — a nested updater runs during the render
|
|
427
482
|
// pass, which is not a safe place to schedule another component's update.
|
|
428
|
-
clear
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
},
|
|
432
|
-
undo() {
|
|
433
|
-
const last = strokes[strokes.length - 1];
|
|
434
|
-
if (last === undefined) return;
|
|
435
|
-
setUndone((stack) => [...stack, last]);
|
|
436
|
-
setStrokes(strokes.slice(0, -1));
|
|
437
|
-
},
|
|
438
|
-
redo() {
|
|
439
|
-
const restored = undone[undone.length - 1];
|
|
440
|
-
if (restored === undefined) return;
|
|
441
|
-
setUndone(undone.slice(0, -1));
|
|
442
|
-
setStrokes([...strokes, restored]);
|
|
443
|
-
},
|
|
483
|
+
clear,
|
|
484
|
+
undo,
|
|
485
|
+
redo,
|
|
444
486
|
isEmpty: () => strokes.length === 0,
|
|
445
487
|
strokeCount: () => strokes.length,
|
|
446
488
|
toSVG: svgDocument,
|
|
@@ -524,10 +566,47 @@ function SignatureRoot(
|
|
|
524
566
|
};
|
|
525
567
|
},
|
|
526
568
|
}),
|
|
527
|
-
[layout.height, layout.width, rasterise, strokes,
|
|
569
|
+
[clear, layout.height, layout.width, rasterise, redo, strokes, svgDocument, undo]
|
|
528
570
|
);
|
|
529
571
|
|
|
530
572
|
const empty = strokes.length === 0;
|
|
573
|
+
const padActions = useMemo(
|
|
574
|
+
() =>
|
|
575
|
+
disabled
|
|
576
|
+
? []
|
|
577
|
+
: [
|
|
578
|
+
...(accessibilityActions ?? []),
|
|
579
|
+
...signatureAccessibilityActions(
|
|
580
|
+
strokes.length,
|
|
581
|
+
undone.length,
|
|
582
|
+
Boolean(onRequestAlternative),
|
|
583
|
+
false
|
|
584
|
+
),
|
|
585
|
+
],
|
|
586
|
+
[accessibilityActions, disabled, onRequestAlternative, strokes.length, undone.length]
|
|
587
|
+
);
|
|
588
|
+
const handleAccessibilityAction = useCallback(
|
|
589
|
+
(event: AccessibilityActionEvent) => {
|
|
590
|
+
if (disabled) return;
|
|
591
|
+
switch (event.nativeEvent.actionName) {
|
|
592
|
+
case 'signature-undo':
|
|
593
|
+
undo();
|
|
594
|
+
return;
|
|
595
|
+
case 'signature-redo':
|
|
596
|
+
redo();
|
|
597
|
+
return;
|
|
598
|
+
case 'signature-clear':
|
|
599
|
+
clear();
|
|
600
|
+
return;
|
|
601
|
+
case 'signature-alternative':
|
|
602
|
+
alternativeRef.current?.();
|
|
603
|
+
return;
|
|
604
|
+
default:
|
|
605
|
+
onAccessibilityAction?.(event);
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
[clear, disabled, onAccessibilityAction, redo, undo]
|
|
609
|
+
);
|
|
531
610
|
|
|
532
611
|
return (
|
|
533
612
|
<View className={slots.root({ className })} {...props}>
|
|
@@ -541,9 +620,17 @@ function SignatureRoot(
|
|
|
541
620
|
onLayout={(event) => setLayout(event.nativeEvent.layout)}
|
|
542
621
|
accessible
|
|
543
622
|
accessibilityRole="image"
|
|
544
|
-
accessibilityLabel=
|
|
545
|
-
accessibilityHint=
|
|
623
|
+
accessibilityLabel={accessibilityLabel ?? 'Signature pad'}
|
|
624
|
+
accessibilityHint={
|
|
625
|
+
accessibilityHint ??
|
|
626
|
+
(onRequestAlternative
|
|
627
|
+
? 'Direct touch drawing requires tracing a path. Use actions to edit or choose another signature method.'
|
|
628
|
+
: 'Direct touch drawing requires tracing a path. Use actions to undo, redo, or clear strokes.')
|
|
629
|
+
}
|
|
546
630
|
accessibilityState={{ disabled }}
|
|
631
|
+
accessibilityValue={signatureAccessibilityValue(strokes.length)}
|
|
632
|
+
accessibilityActions={padActions}
|
|
633
|
+
onAccessibilityAction={handleAccessibilityAction}
|
|
547
634
|
>
|
|
548
635
|
{guideline && layout.height > 0 ? (
|
|
549
636
|
<View
|