panelui-native 0.53.0 → 0.56.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 +4 -1
- package/lib/module/components/combobox/index.js +61 -7
- package/lib/module/components/combobox/index.js.map +1 -1
- package/lib/module/components/hex-chart/index.js +765 -0
- package/lib/module/components/hex-chart/index.js.map +1 -0
- package/lib/module/components/sortable/index.js +74 -12
- package/lib/module/components/sortable/index.js.map +1 -1
- package/lib/module/components/steps/index.js +95 -12
- package/lib/module/components/steps/index.js.map +1 -1
- package/lib/module/components/tag-input/index.js +435 -0
- package/lib/module/components/tag-input/index.js.map +1 -0
- package/lib/module/components/tour/index.js +661 -0
- package/lib/module/components/tour/index.js.map +1 -0
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/chart.js +209 -0
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/hex-chart/index.d.ts +277 -0
- package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/sortable/index.d.ts.map +1 -1
- package/lib/typescript/src/components/steps/index.d.ts +19 -0
- package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tag-input/index.d.ts +240 -0
- package/lib/typescript/src/components/tag-input/index.d.ts.map +1 -0
- package/lib/typescript/src/components/tour/index.d.ts +168 -0
- package/lib/typescript/src/components/tour/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +98 -0
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/combobox/index.tsx +65 -12
- package/src/components/hex-chart/index.tsx +1009 -0
- package/src/components/sortable/index.tsx +72 -16
- package/src/components/steps/index.tsx +103 -8
- package/src/components/tag-input/index.tsx +619 -0
- package/src/components/tour/index.tsx +933 -0
- package/src/index.ts +30 -0
- package/src/utils/chart.ts +236 -0
|
@@ -583,18 +583,32 @@ function SortableRoot({
|
|
|
583
583
|
|
|
584
584
|
const indexOf = useCallback((id: string) => indices.get(id) ?? -1, [indices]);
|
|
585
585
|
|
|
586
|
+
/*
|
|
587
|
+
* Both maps are accumulated in a ref and then published, rather than built by
|
|
588
|
+
* reading the shared value back and spreading it. Every row reports its
|
|
589
|
+
* layout in the same batch on mount, and a write to `.value` is not visible
|
|
590
|
+
* to the next read in that batch — so a read-modify-write there has all the
|
|
591
|
+
* rows spreading the same empty map and only the last one surviving. A list
|
|
592
|
+
* that knows one row's height puts every slot a gap apart, and the first
|
|
593
|
+
* drag drops the row at the end of the list.
|
|
594
|
+
*/
|
|
595
|
+
const measuredHeights = useRef<Record<string, number>>({});
|
|
596
|
+
const pinnedFlags = useRef<Record<string, boolean>>({});
|
|
597
|
+
|
|
586
598
|
const measured = useCallback(
|
|
587
599
|
(id: string, height: number) => {
|
|
588
|
-
if (
|
|
589
|
-
|
|
600
|
+
if (measuredHeights.current[id] === height) return;
|
|
601
|
+
measuredHeights.current = { ...measuredHeights.current, [id]: height };
|
|
602
|
+
heights.value = measuredHeights.current;
|
|
590
603
|
},
|
|
591
604
|
[heights]
|
|
592
605
|
);
|
|
593
606
|
|
|
594
607
|
const setPinned = useCallback(
|
|
595
608
|
(id: string, next: boolean) => {
|
|
596
|
-
if (Boolean(
|
|
597
|
-
|
|
609
|
+
if (Boolean(pinnedFlags.current[id]) === next) return;
|
|
610
|
+
pinnedFlags.current = { ...pinnedFlags.current, [id]: next };
|
|
611
|
+
pinned.value = pinnedFlags.current;
|
|
598
612
|
},
|
|
599
613
|
[pinned]
|
|
600
614
|
);
|
|
@@ -1031,10 +1045,15 @@ function SortableItem({
|
|
|
1031
1045
|
* drop unreported; `seq` is what tells the two cases apart, because
|
|
1032
1046
|
* the only interruption that should be ignored is the row being picked
|
|
1033
1047
|
* up again.
|
|
1048
|
+
*
|
|
1049
|
+
* `activeId` is checked as well as `seq` because reporting the drop is
|
|
1050
|
+
* itself what interrupts the spring: the caller applies the reorder,
|
|
1051
|
+
* and the reset that follows puts `translate` back to rest, which ends
|
|
1052
|
+
* the animation and calls this a second time under the same `seq`.
|
|
1034
1053
|
*/
|
|
1035
1054
|
const land = () => {
|
|
1036
1055
|
'worklet';
|
|
1037
|
-
if (dragSeq.value !== seq) return;
|
|
1056
|
+
if (dragSeq.value !== seq || activeId.value === null) return;
|
|
1038
1057
|
activeId.value = null;
|
|
1039
1058
|
runOnJS(notifySettled)(id);
|
|
1040
1059
|
};
|
|
@@ -1142,17 +1161,27 @@ function SortableItem({
|
|
|
1142
1161
|
* no way to discover it from the row. Moving by whole slots is published as
|
|
1143
1162
|
* an accessibility action instead, which is the only path to reordering for
|
|
1144
1163
|
* someone who is not dragging anything.
|
|
1164
|
+
*
|
|
1165
|
+
* The actions sit wherever the drag does. A handle list keeps them on the
|
|
1166
|
+
* grip, which is an element in its own right; put here they would never be
|
|
1167
|
+
* offered, because the actions of a view that is not itself an accessibility
|
|
1168
|
+
* element are not reachable, and a row full of text is not one. A long-press
|
|
1169
|
+
* list has no grip and gives the whole row to the drag, so the row becomes
|
|
1170
|
+
* the element — which is what a screen reader wants from a row in any case.
|
|
1145
1171
|
*/
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1172
|
+
const carriesActions = activation === 'longPress' && !locked;
|
|
1173
|
+
|
|
1174
|
+
const a11y = carriesActions
|
|
1175
|
+
? [
|
|
1149
1176
|
{ name: 'moveUp', label: 'Move up' },
|
|
1150
1177
|
{ name: 'moveDown', label: 'Move down' },
|
|
1151
|
-
]
|
|
1178
|
+
]
|
|
1179
|
+
: undefined;
|
|
1152
1180
|
|
|
1153
1181
|
const row = (
|
|
1154
1182
|
<Animated.View
|
|
1155
1183
|
onLayout={onLayout}
|
|
1184
|
+
accessible={carriesActions}
|
|
1156
1185
|
accessibilityActions={a11y}
|
|
1157
1186
|
onAccessibilityAction={(event) => {
|
|
1158
1187
|
if (event.nativeEvent.actionName === 'moveUp') step(id, -1);
|
|
@@ -1224,7 +1253,7 @@ function SortableHandle({
|
|
|
1224
1253
|
accessibilityLabel = 'Drag to reorder',
|
|
1225
1254
|
...props
|
|
1226
1255
|
}: SortableHandleProps) {
|
|
1227
|
-
const { activation, disabled: rootDisabled } = useSortableRoot('Sortable.Handle');
|
|
1256
|
+
const { activation, disabled: rootDisabled, step } = useSortableRoot('Sortable.Handle');
|
|
1228
1257
|
const item = useContext(SortableItemContext);
|
|
1229
1258
|
|
|
1230
1259
|
/*
|
|
@@ -1235,17 +1264,44 @@ function SortableHandle({
|
|
|
1235
1264
|
const muted = useCSSVariable('--color-muted-foreground');
|
|
1236
1265
|
const tint = typeof muted === 'string' ? muted : undefined;
|
|
1237
1266
|
|
|
1267
|
+
const locked = rootDisabled || item?.disabled;
|
|
1268
|
+
|
|
1269
|
+
/*
|
|
1270
|
+
* `adjustable` promises an element that answers a swipe up or down, and the
|
|
1271
|
+
* promise was never kept: the grip published the role and nothing else, so
|
|
1272
|
+
* the one part of a row a screen reader could reach did nothing at all.
|
|
1273
|
+
* Moving by whole slots is what it was always meant to do. The same move is
|
|
1274
|
+
* offered as a named action too, because a swipe says nothing about which
|
|
1275
|
+
* way the row is going to travel.
|
|
1276
|
+
*/
|
|
1277
|
+
const move = (delta: number) => {
|
|
1278
|
+
if (locked || !item) return;
|
|
1279
|
+
step(item.id, delta);
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1238
1282
|
const glyph = (
|
|
1239
1283
|
<View
|
|
1240
1284
|
accessible
|
|
1241
1285
|
accessibilityRole="adjustable"
|
|
1242
1286
|
accessibilityLabel={accessibilityLabel}
|
|
1243
|
-
accessibilityState={{ disabled:
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1287
|
+
accessibilityState={{ disabled: locked }}
|
|
1288
|
+
accessibilityValue={item ? { text: `Position ${item.index + 1}` } : undefined}
|
|
1289
|
+
accessibilityActions={
|
|
1290
|
+
locked
|
|
1291
|
+
? undefined
|
|
1292
|
+
: [
|
|
1293
|
+
{ name: 'increment' },
|
|
1294
|
+
{ name: 'decrement' },
|
|
1295
|
+
{ name: 'moveUp', label: 'Move up' },
|
|
1296
|
+
{ name: 'moveDown', label: 'Move down' },
|
|
1297
|
+
]
|
|
1298
|
+
}
|
|
1299
|
+
onAccessibilityAction={(event) => {
|
|
1300
|
+
const action = event.nativeEvent.actionName;
|
|
1301
|
+
if (action === 'increment' || action === 'moveUp') move(-1);
|
|
1302
|
+
if (action === 'decrement' || action === 'moveDown') move(1);
|
|
1303
|
+
}}
|
|
1304
|
+
className={cn('items-center justify-center px-2 py-1.5', locked && 'opacity-40', className)}
|
|
1249
1305
|
{...props}
|
|
1250
1306
|
>
|
|
1251
1307
|
<IconColorProvider color={tint}>
|
|
@@ -14,10 +14,24 @@
|
|
|
14
14
|
* Steps does not own your flow: it reflects whatever step your app says is
|
|
15
15
|
* active. Pass `value` to control it, or `defaultValue` to let it manage
|
|
16
16
|
* its own.
|
|
17
|
+
*
|
|
18
|
+
* The connectors are the component's job, not yours. The root counts the items
|
|
19
|
+
* it holds and each one draws the connector to the next, so a stepper is just
|
|
20
|
+
* its steps — there is no separator to forget, mis-order, or leave dangling
|
|
21
|
+
* past the last stop. An item that contains its own `Steps.Separator` keeps it
|
|
22
|
+
* and gets no second one, so hand-placed connectors still work; `separators`
|
|
23
|
+
* turns the automatic ones off wholesale.
|
|
24
|
+
*
|
|
25
|
+
* Knowing the count is also what lets a step say where it sits. A screen reader
|
|
26
|
+
* reaching the middle of a wizard hears "Payment, step 2 of 3, completed" —
|
|
27
|
+
* the position and the state, which are the two things the circle and its fill
|
|
28
|
+
* convey to everyone else.
|
|
17
29
|
*/
|
|
18
30
|
import {
|
|
31
|
+
Children,
|
|
19
32
|
createContext,
|
|
20
33
|
forwardRef,
|
|
34
|
+
isValidElement,
|
|
21
35
|
useCallback,
|
|
22
36
|
useContext,
|
|
23
37
|
useMemo,
|
|
@@ -91,6 +105,7 @@ interface StepsContextValue {
|
|
|
91
105
|
activeStep: number;
|
|
92
106
|
setActiveStep: (step: number) => void;
|
|
93
107
|
orientation: StepsOrientation;
|
|
108
|
+
separators: boolean;
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
interface StepItemContextValue {
|
|
@@ -100,8 +115,23 @@ interface StepItemContextValue {
|
|
|
100
115
|
isLoading: boolean;
|
|
101
116
|
}
|
|
102
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Where an item sits among its siblings, published by the root.
|
|
120
|
+
*
|
|
121
|
+
* Deliberately not the item's own `step` prop: that is the author's numbering
|
|
122
|
+
* of the flow and may skip, repeat or start anywhere, while the connector and
|
|
123
|
+
* the "2 of 3" announcement both need the position in the row as rendered.
|
|
124
|
+
* Absent when an item is used outside a root that maps its children, which is
|
|
125
|
+
* why every reader of it has a fallback.
|
|
126
|
+
*/
|
|
127
|
+
interface StepPositionContextValue {
|
|
128
|
+
position: number;
|
|
129
|
+
total: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
103
132
|
const StepsContext = createContext<StepsContextValue | null>(null);
|
|
104
133
|
const StepItemContext = createContext<StepItemContextValue | null>(null);
|
|
134
|
+
const StepPositionContext = createContext<StepPositionContextValue | null>(null);
|
|
105
135
|
|
|
106
136
|
function useSteps(component: string): StepsContextValue {
|
|
107
137
|
const context = useContext(StepsContext);
|
|
@@ -127,6 +157,13 @@ export interface StepsProps extends ViewProps {
|
|
|
127
157
|
value?: number;
|
|
128
158
|
onValueChange?: (value: number) => void;
|
|
129
159
|
orientation?: StepsOrientation;
|
|
160
|
+
/**
|
|
161
|
+
* Draw the connector between one item and the next. On by default — an item
|
|
162
|
+
* that holds its own `Steps.Separator` is left alone either way, so this is
|
|
163
|
+
* for a stepper that wants no connectors at all rather than for one that
|
|
164
|
+
* places them by hand.
|
|
165
|
+
*/
|
|
166
|
+
separators?: boolean;
|
|
130
167
|
children?: ReactNode;
|
|
131
168
|
}
|
|
132
169
|
|
|
@@ -138,6 +175,7 @@ const StepsRoot = forwardRef<View, StepsProps>(
|
|
|
138
175
|
value,
|
|
139
176
|
onValueChange,
|
|
140
177
|
orientation = 'horizontal',
|
|
178
|
+
separators = true,
|
|
141
179
|
children,
|
|
142
180
|
...props
|
|
143
181
|
},
|
|
@@ -156,16 +194,37 @@ const StepsRoot = forwardRef<View, StepsProps>(
|
|
|
156
194
|
);
|
|
157
195
|
|
|
158
196
|
const context = useMemo(
|
|
159
|
-
() => ({ activeStep, setActiveStep, orientation }),
|
|
160
|
-
[activeStep, setActiveStep, orientation]
|
|
197
|
+
() => ({ activeStep, setActiveStep, orientation, separators }),
|
|
198
|
+
[activeStep, setActiveStep, orientation, separators]
|
|
161
199
|
);
|
|
162
200
|
|
|
163
201
|
const { root } = stepsVariants({ orientation });
|
|
164
202
|
|
|
203
|
+
/*
|
|
204
|
+
* Items are counted here rather than registered by each one on mount,
|
|
205
|
+
* because the count has to be right on the first frame: a connector that
|
|
206
|
+
* appears after the last item and disappears once the registrations land
|
|
207
|
+
* is a visible flicker on every mount. Reading the children gives the whole
|
|
208
|
+
* row at once, and a Provider adds no host view, so wrapping an item in one
|
|
209
|
+
* leaves the flex layout exactly as it was.
|
|
210
|
+
*/
|
|
211
|
+
const nodes = Children.toArray(textChildren(children));
|
|
212
|
+
const total = nodes.filter((node) => isValidElement(node) && node.type === StepsItem).length;
|
|
213
|
+
let position = -1;
|
|
214
|
+
|
|
165
215
|
return (
|
|
166
216
|
<StepsContext.Provider value={context}>
|
|
167
217
|
<View ref={ref} className={root({ className })} {...props}>
|
|
168
|
-
{
|
|
218
|
+
{nodes.map((node) => {
|
|
219
|
+
if (!isValidElement(node) || node.type !== StepsItem) return node;
|
|
220
|
+
position += 1;
|
|
221
|
+
const placement = { position, total };
|
|
222
|
+
return (
|
|
223
|
+
<StepPositionContext.Provider key={node.key ?? position} value={placement}>
|
|
224
|
+
{node}
|
|
225
|
+
</StepPositionContext.Provider>
|
|
226
|
+
);
|
|
227
|
+
})}
|
|
169
228
|
</View>
|
|
170
229
|
</StepsContext.Provider>
|
|
171
230
|
);
|
|
@@ -190,7 +249,8 @@ const StepsItem = forwardRef<View, StepsItemProps>(
|
|
|
190
249
|
{ className, step, completed = false, disabled = false, loading = false, children, ...props },
|
|
191
250
|
ref
|
|
192
251
|
) => {
|
|
193
|
-
const { activeStep, orientation } = useSteps('Steps.Item');
|
|
252
|
+
const { activeStep, orientation, separators } = useSteps('Steps.Item');
|
|
253
|
+
const placement = useContext(StepPositionContext);
|
|
194
254
|
|
|
195
255
|
const isLoading = loading && step === activeStep;
|
|
196
256
|
const state: StepState =
|
|
@@ -209,10 +269,23 @@ const StepsItem = forwardRef<View, StepsItemProps>(
|
|
|
209
269
|
|
|
210
270
|
const { item } = stepsVariants({ orientation, state });
|
|
211
271
|
|
|
272
|
+
/*
|
|
273
|
+
* The last item has nothing to connect to, and one placed by hand is the
|
|
274
|
+
* author's — adding a second beside it would double the line rather than
|
|
275
|
+
* replace it. Only the top level is inspected, which is where a connector
|
|
276
|
+
* has to be anyway: it is a sibling of the trigger, not something buried
|
|
277
|
+
* inside it.
|
|
278
|
+
*/
|
|
279
|
+
const isLast = placement ? placement.position === placement.total - 1 : true;
|
|
280
|
+
const hasOwnSeparator = Children.toArray(children).some(
|
|
281
|
+
(child) => isValidElement(child) && child.type === StepsSeparator
|
|
282
|
+
);
|
|
283
|
+
|
|
212
284
|
return (
|
|
213
285
|
<StepItemContext.Provider value={context}>
|
|
214
286
|
<View ref={ref} className={item({ className })} {...props}>
|
|
215
287
|
{textChildren(children)}
|
|
288
|
+
{separators && !isLast && !hasOwnSeparator ? <StepsSeparator /> : null}
|
|
216
289
|
</View>
|
|
217
290
|
</StepItemContext.Provider>
|
|
218
291
|
);
|
|
@@ -225,18 +298,38 @@ export interface StepsTriggerProps extends ViewProps {
|
|
|
225
298
|
children?: ReactNode;
|
|
226
299
|
}
|
|
227
300
|
|
|
301
|
+
/** What each state is called when a screen reader reaches the step. */
|
|
302
|
+
const STATE_WORDS: Record<StepState, string> = {
|
|
303
|
+
completed: 'completed',
|
|
304
|
+
active: 'current step',
|
|
305
|
+
loading: 'in progress',
|
|
306
|
+
inactive: 'not started',
|
|
307
|
+
};
|
|
308
|
+
|
|
228
309
|
/** Makes its item selectable. Omit it for a read-only stepper. */
|
|
229
310
|
const StepsTrigger = forwardRef<View, StepsTriggerProps>(
|
|
230
311
|
({ className, children, ...props }, ref) => {
|
|
231
312
|
const { setActiveStep, orientation } = useSteps('Steps.Trigger');
|
|
232
313
|
const { step, state, isDisabled } = useStepItem('Steps.Trigger');
|
|
314
|
+
const placement = useContext(StepPositionContext);
|
|
233
315
|
const { trigger } = stepsVariants({ orientation, state, isDisabled });
|
|
234
316
|
|
|
317
|
+
/*
|
|
318
|
+
* Said as a value rather than a label, because the label is the step's own
|
|
319
|
+
* title and the circle beside it — text the trigger already merges. An
|
|
320
|
+
* `accessibilityLabel` here would replace all of that with the position,
|
|
321
|
+
* trading the name of the step for its number; a value is read after it.
|
|
322
|
+
*/
|
|
323
|
+
const position = placement
|
|
324
|
+
? `step ${placement.position + 1} of ${placement.total}, ${STATE_WORDS[state]}`
|
|
325
|
+
: STATE_WORDS[state];
|
|
326
|
+
|
|
235
327
|
return (
|
|
236
328
|
<Pressable
|
|
237
329
|
ref={ref}
|
|
238
330
|
accessibilityRole="button"
|
|
239
331
|
accessibilityState={{ disabled: isDisabled, selected: state === 'active' }}
|
|
332
|
+
accessibilityValue={{ text: position }}
|
|
240
333
|
disabled={isDisabled}
|
|
241
334
|
onPress={() => setActiveStep(step)}
|
|
242
335
|
className={trigger({ className })}
|
|
@@ -310,11 +403,13 @@ export interface StepsSeparatorProps extends ViewProps {
|
|
|
310
403
|
}
|
|
311
404
|
|
|
312
405
|
/**
|
|
313
|
-
* The connector between two steps. Fills with the primary colour once the
|
|
314
|
-
*
|
|
406
|
+
* The connector between two steps. Fills with the primary colour once the step
|
|
407
|
+
* before it is complete.
|
|
315
408
|
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
409
|
+
* Every item draws one automatically, so this is only worth writing to dress a
|
|
410
|
+
* particular connector — an item that holds its own keeps it and gets no
|
|
411
|
+
* second. It belongs inside a `Steps.Item` either way, so it can read that
|
|
412
|
+
* item's state: the connector after step 1 goes solid when step 1 is done.
|
|
318
413
|
*/
|
|
319
414
|
const StepsSeparator = forwardRef<View, StepsSeparatorProps>(
|
|
320
415
|
({ className, ...props }, ref) => {
|