panelui-native 0.75.1 → 0.76.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/lib/module/components/ai-input/ai-input-growth.js +62 -0
- package/lib/module/components/ai-input/ai-input-growth.js.map +1 -0
- package/lib/module/components/ai-input/index.js +1568 -0
- package/lib/module/components/ai-input/index.js.map +1 -0
- package/lib/module/components/alert/index.js +1 -1
- package/lib/module/components/alert/index.js.map +1 -1
- package/lib/module/components/avatar/avatar-source.js +15 -0
- package/lib/module/components/avatar/avatar-source.js.map +1 -0
- package/lib/module/components/avatar/index.js +32 -31
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js +3 -2
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button-group/index.js +2 -3
- package/lib/module/components/button-group/index.js.map +1 -1
- package/lib/module/components/carousel/index.js +10 -7
- package/lib/module/components/carousel/index.js.map +1 -1
- package/lib/module/components/collapsible/index.js +1 -1
- package/lib/module/components/collapsible/index.js.map +1 -1
- package/lib/module/components/field/index.js +7 -8
- package/lib/module/components/field/index.js.map +1 -1
- package/lib/module/components/label/index.js +1 -1
- package/lib/module/components/label/index.js.map +1 -1
- package/lib/module/components/marquee/index.js +36 -11
- package/lib/module/components/marquee/index.js.map +1 -1
- package/lib/module/components/marquee/marquee-math.js +22 -6
- package/lib/module/components/marquee/marquee-math.js.map +1 -1
- package/lib/module/components/plan/index.js +2 -1
- package/lib/module/components/plan/index.js.map +1 -1
- package/lib/module/components/plot/index.js +27 -9
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/search-bar/index.js +11 -3
- package/lib/module/components/search-bar/index.js.map +1 -1
- package/lib/module/components/split-view/index.js +26 -27
- package/lib/module/components/split-view/index.js.map +1 -1
- package/lib/module/components/split-view/split-view-lifecycle.js +32 -0
- package/lib/module/components/split-view/split-view-lifecycle.js.map +1 -0
- package/lib/module/components/splitter/index.js +7 -13
- package/lib/module/components/splitter/index.js.map +1 -1
- package/lib/module/components/splitter/splitter-math.js +14 -0
- package/lib/module/components/splitter/splitter-math.js.map +1 -1
- package/lib/module/components/tabs/index.js +1 -1
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/components/timeline/index.js +156 -42
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/timeline/timeline-geometry.js +11 -0
- package/lib/module/components/timeline/timeline-geometry.js.map +1 -1
- package/lib/module/components/toast/index.js +1 -1
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/tooltip/index.js +2 -2
- package/lib/module/components/tooltip/index.js.map +1 -1
- package/lib/module/components/tree/index.js +2 -2
- package/lib/module/components/tree/index.js.map +1 -1
- package/lib/module/components/typography/index.js +2 -1
- package/lib/module/components/typography/index.js.map +1 -1
- package/lib/module/components/waterfall-chart/index.js +22 -3
- package/lib/module/components/waterfall-chart/index.js.map +1 -1
- package/lib/module/hooks/use-disclosure.js +10 -8
- package/lib/module/hooks/use-disclosure.js.map +1 -1
- package/lib/module/icons/index.js +135 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +5 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/controllable-state.js +61 -0
- package/lib/module/primitives/controllable-state.js.map +1 -0
- package/lib/module/primitives/finite-chart.js +15 -0
- package/lib/module/primitives/finite-chart.js.map +1 -0
- package/lib/module/primitives/glass.js +136 -0
- package/lib/module/primitives/glass.js.map +1 -0
- package/lib/module/primitives/scrim.js +8 -1
- package/lib/module/primitives/scrim.js.map +1 -1
- package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts +59 -0
- package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts.map +1 -0
- package/lib/typescript/src/components/ai-input/index.d.ts +423 -0
- package/lib/typescript/src/components/ai-input/index.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar/avatar-source.d.ts +4 -0
- package/lib/typescript/src/components/avatar/avatar-source.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +10 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/index.d.ts +15 -6
- package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/marquee-math.d.ts +6 -0
- package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -1
- package/lib/typescript/src/components/plan/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plot/index.d.ts +2 -1
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/split-view/index.d.ts.map +1 -1
- package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts +14 -0
- package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts.map +1 -0
- package/lib/typescript/src/components/splitter/index.d.ts +2 -2
- package/lib/typescript/src/components/splitter/index.d.ts.map +1 -1
- package/lib/typescript/src/components/splitter/splitter-math.d.ts +2 -0
- package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/index.d.ts +24 -2
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +2 -0
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/components/waterfall-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-disclosure.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts +18 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/controllable-state.d.ts +33 -0
- package/lib/typescript/src/primitives/controllable-state.d.ts.map +1 -0
- package/lib/typescript/src/primitives/finite-chart.d.ts +5 -0
- package/lib/typescript/src/primitives/finite-chart.d.ts.map +1 -0
- package/lib/typescript/src/primitives/glass.d.ts +43 -0
- package/lib/typescript/src/primitives/glass.d.ts.map +1 -0
- package/lib/typescript/src/primitives/scrim.d.ts +7 -0
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/components/ai-input/ai-input-growth.ts +65 -0
- package/src/components/ai-input/index.tsx +1937 -0
- package/src/components/alert/index.tsx +1 -1
- package/src/components/avatar/avatar-source.ts +18 -0
- package/src/components/avatar/index.tsx +35 -27
- package/src/components/bottom-sheet/index.tsx +15 -3
- package/src/components/button-group/index.tsx +1 -1
- package/src/components/carousel/index.tsx +10 -11
- package/src/components/collapsible/index.tsx +1 -1
- package/src/components/field/index.tsx +4 -4
- package/src/components/label/index.tsx +1 -1
- package/src/components/marquee/index.tsx +56 -12
- package/src/components/marquee/marquee-math.ts +26 -3
- package/src/components/plan/index.tsx +6 -2
- package/src/components/plot/index.tsx +37 -11
- package/src/components/search-bar/index.tsx +9 -3
- package/src/components/split-view/index.tsx +24 -33
- package/src/components/split-view/split-view-lifecycle.ts +43 -0
- package/src/components/splitter/index.tsx +7 -11
- package/src/components/splitter/splitter-math.ts +28 -0
- package/src/components/tabs/index.tsx +1 -1
- package/src/components/timeline/index.tsx +189 -34
- package/src/components/timeline/timeline-geometry.ts +11 -0
- package/src/components/toast/index.tsx +6 -1
- package/src/components/tooltip/index.tsx +1 -1
- package/src/components/tree/index.tsx +2 -2
- package/src/components/typography/index.tsx +2 -2
- package/src/components/waterfall-chart/index.tsx +37 -4
- package/src/hooks/use-disclosure.ts +7 -12
- package/src/icons/index.tsx +106 -0
- package/src/index.ts +27 -0
- package/src/primitives/controllable-state.ts +112 -0
- package/src/primitives/finite-chart.ts +14 -0
- package/src/primitives/glass.tsx +171 -0
- package/src/primitives/scrim.tsx +7 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ImageSourcePropType } from 'react-native';
|
|
2
|
+
|
|
3
|
+
function stableValue(value: unknown): string {
|
|
4
|
+
if (Array.isArray(value)) return `[${value.map(stableValue).join(',')}]`;
|
|
5
|
+
if (value && typeof value === 'object') {
|
|
6
|
+
return `{${Object.entries(value)
|
|
7
|
+
.filter(([, entry]) => entry !== undefined)
|
|
8
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
9
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableValue(entry)}`)
|
|
10
|
+
.join(',')}}`;
|
|
11
|
+
}
|
|
12
|
+
return JSON.stringify(value) ?? String(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Stable across equivalent inline source objects, but changes with request inputs. */
|
|
16
|
+
export function avatarSourceIdentity(source: ImageSourcePropType | undefined) {
|
|
17
|
+
return source === undefined ? undefined : stableValue(source);
|
|
18
|
+
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
avatarGroupOverlap,
|
|
23
23
|
type AvatarSizeName,
|
|
24
24
|
} from './avatar-group';
|
|
25
|
+
import { avatarSourceIdentity } from './avatar-source';
|
|
25
26
|
|
|
26
27
|
const avatarVariants = tv({
|
|
27
28
|
slots: {
|
|
@@ -53,16 +54,20 @@ export interface AvatarProps extends ViewProps, VariantProps<typeof avatarVarian
|
|
|
53
54
|
|
|
54
55
|
const AvatarRoot = forwardRef<View, AvatarProps>(
|
|
55
56
|
({ className, size, source, fallback, imageProps, children, ...props }, ref) => {
|
|
56
|
-
const [
|
|
57
|
+
const [failedSource, setFailedSource] = useState<string>();
|
|
57
58
|
const { root, image, fallback: fallbackSlot } = avatarVariants({ size });
|
|
58
|
-
const
|
|
59
|
+
const sourceIdentity = avatarSourceIdentity(source);
|
|
60
|
+
const showImage = !!source && failedSource !== sourceIdentity;
|
|
59
61
|
|
|
60
62
|
const face = showImage ? (
|
|
61
63
|
<Image
|
|
62
|
-
source={source}
|
|
63
|
-
onError={() => setErrored(true)}
|
|
64
64
|
className={image()}
|
|
65
65
|
{...imageProps}
|
|
66
|
+
source={source}
|
|
67
|
+
onError={(event) => {
|
|
68
|
+
setFailedSource(sourceIdentity);
|
|
69
|
+
imageProps?.onError?.(event);
|
|
70
|
+
}}
|
|
66
71
|
/>
|
|
67
72
|
) : (
|
|
68
73
|
<Text className={fallbackSlot()}>{fallback ?? '?'}</Text>
|
|
@@ -149,11 +154,9 @@ export interface AvatarGroupProps extends ViewProps {
|
|
|
149
154
|
* A row of avatars, each overlapping the one after it, with the people who did
|
|
150
155
|
* not fit counted at the end.
|
|
151
156
|
*
|
|
152
|
-
* The faces
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* them has to be flipped. Flipping the layout keeps the children in the order
|
|
156
|
-
* they were written.
|
|
157
|
+
* The faces stay in logical child order for assistive traversal. Explicit
|
|
158
|
+
* z-indices, rather than a reversed render/layout order, put the first face on
|
|
159
|
+
* top while the overflow count remains the final list item.
|
|
157
160
|
*
|
|
158
161
|
* Each face gets a ring in the page background so it separates from the one
|
|
159
162
|
* underneath whatever surface the stack sits on.
|
|
@@ -172,10 +175,21 @@ const AvatarGroup = forwardRef<View, AvatarGroupProps>(
|
|
|
172
175
|
const { visible, overflow } = avatarGroupCount(faces.length, max, total);
|
|
173
176
|
const slide = avatarGroupOverlap(size, overlap);
|
|
174
177
|
|
|
175
|
-
//
|
|
176
|
-
//
|
|
178
|
+
// Logical reading order: visible people first, then the overflow summary.
|
|
179
|
+
// Painting order is independent and applied to each wrapper below.
|
|
177
180
|
const stack: { key: string; node: ReactNode }[] = [];
|
|
178
181
|
|
|
182
|
+
for (let index = 0; index < visible; index += 1) {
|
|
183
|
+
const face = faces[index]!;
|
|
184
|
+
stack.push({
|
|
185
|
+
key: String(face.key ?? index),
|
|
186
|
+
node: cloneElement(face, {
|
|
187
|
+
size: face.props.size ?? size,
|
|
188
|
+
className: cn('border-2 border-background', face.props.className),
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
179
193
|
if (overflow > 0) {
|
|
180
194
|
stack.push({
|
|
181
195
|
key: 'overflow',
|
|
@@ -190,28 +204,22 @@ const AvatarGroup = forwardRef<View, AvatarGroupProps>(
|
|
|
190
204
|
});
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
for (let index = visible - 1; index >= 0; index -= 1) {
|
|
194
|
-
const face = faces[index]!;
|
|
195
|
-
stack.push({
|
|
196
|
-
key: String(face.key ?? index),
|
|
197
|
-
node: cloneElement(face, {
|
|
198
|
-
size: face.props.size ?? size,
|
|
199
|
-
className: cn('border-2 border-background', face.props.className),
|
|
200
|
-
}),
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
207
|
return (
|
|
205
208
|
<View
|
|
206
209
|
ref={ref}
|
|
207
|
-
|
|
208
|
-
className={cn('flex-row-reverse self-start', className)}
|
|
210
|
+
className={cn('flex-row self-start', className)}
|
|
209
211
|
{...props}
|
|
212
|
+
accessibilityRole="list"
|
|
210
213
|
>
|
|
211
214
|
{stack.map((entry, index) => (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
<View
|
|
216
|
+
key={entry.key}
|
|
217
|
+
role="listitem"
|
|
218
|
+
style={{
|
|
219
|
+
zIndex: stack.length - index,
|
|
220
|
+
marginStart: index === 0 ? 0 : -slide,
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
215
223
|
{entry.node}
|
|
216
224
|
</View>
|
|
217
225
|
))}
|
|
@@ -224,6 +224,15 @@ export interface BottomSheetContentProps extends ViewProps {
|
|
|
224
224
|
* affordances.
|
|
225
225
|
*/
|
|
226
226
|
showClose?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Show the drag handle at the top of the sheet. On by default, because a
|
|
229
|
+
* sheet that can be dragged should say so.
|
|
230
|
+
*
|
|
231
|
+
* Turn it off when the sheet draws its own — a component wrapping this one
|
|
232
|
+
* to give the surface a material of its own has to put the handle on that
|
|
233
|
+
* material, and a handle floating above it belongs to nothing.
|
|
234
|
+
*/
|
|
235
|
+
showGrabber?: boolean;
|
|
227
236
|
/**
|
|
228
237
|
* Float the sheet clear of the screen edges instead of docking it to the
|
|
229
238
|
* bottom, so it reads as a card laid over the app rather than a drawer
|
|
@@ -267,6 +276,7 @@ function BottomSheetContent({
|
|
|
267
276
|
className,
|
|
268
277
|
dismissible = true,
|
|
269
278
|
showClose = true,
|
|
279
|
+
showGrabber = true,
|
|
270
280
|
detached = false,
|
|
271
281
|
blur = false,
|
|
272
282
|
size = 'auto',
|
|
@@ -475,9 +485,11 @@ function BottomSheetContent({
|
|
|
475
485
|
]}
|
|
476
486
|
>
|
|
477
487
|
<SheetSurfaceContext.Provider value={surface}>
|
|
478
|
-
|
|
479
|
-
<View className="
|
|
480
|
-
|
|
488
|
+
{showGrabber ? (
|
|
489
|
+
<View className="mb-3 self-center">
|
|
490
|
+
<View className="h-1 w-10 rounded-full bg-muted-foreground/30" />
|
|
491
|
+
</View>
|
|
492
|
+
) : null}
|
|
481
493
|
{textChildren(children)}
|
|
482
494
|
{/*
|
|
483
495
|
* Last, and lifted above the content.
|
|
@@ -171,11 +171,11 @@ const ButtonGroupRoot = forwardRef<View, ButtonGroupProps>(
|
|
|
171
171
|
<ButtonGroupProvider value={context}>
|
|
172
172
|
<View
|
|
173
173
|
ref={ref}
|
|
174
|
+
{...props}
|
|
174
175
|
// A run of related actions is a toolbar; a screen reader announcing
|
|
175
176
|
// the group is what tells someone the buttons in it belong together.
|
|
176
177
|
accessibilityRole="toolbar"
|
|
177
178
|
className={root({ className })}
|
|
178
|
-
{...props}
|
|
179
179
|
>
|
|
180
180
|
{items.map((child, index) => (
|
|
181
181
|
// Index keys: these fragments have no identity of their own, and
|
|
@@ -74,6 +74,7 @@ import Animated, {
|
|
|
74
74
|
type SharedValue,
|
|
75
75
|
} from 'react-native-reanimated';
|
|
76
76
|
import { ChevronLeftIcon, ChevronRightIcon } from '../../icons';
|
|
77
|
+
import { useControllableState } from '../../primitives/controllable-state';
|
|
77
78
|
import { Text, textChildren } from '../../primitives/text';
|
|
78
79
|
import { cn } from '../../utils/cn';
|
|
79
80
|
import {
|
|
@@ -264,14 +265,20 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
264
265
|
ref
|
|
265
266
|
) {
|
|
266
267
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
267
|
-
const [internalIndex, setInternalIndex] = useState(defaultIndex);
|
|
268
268
|
const [count, setCountValue] = useState(0);
|
|
269
269
|
const [countKnown, setCountKnown] = useState(false);
|
|
270
270
|
const [touched, setTouched] = useState(false);
|
|
271
271
|
const reducedMotion = useReducedMotion();
|
|
272
272
|
|
|
273
|
-
const
|
|
274
|
-
|
|
273
|
+
const {
|
|
274
|
+
value: requestedIndex,
|
|
275
|
+
setValue: setIndex,
|
|
276
|
+
isControlled,
|
|
277
|
+
} = useControllableState({
|
|
278
|
+
value: indexProp,
|
|
279
|
+
defaultValue: defaultIndex,
|
|
280
|
+
onChange: onIndexChange,
|
|
281
|
+
});
|
|
275
282
|
|
|
276
283
|
const progress = useSharedValue(indexProp ?? defaultIndex);
|
|
277
284
|
const engaged = useSharedValue(0);
|
|
@@ -287,14 +294,6 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
287
294
|
setCountKnown(true);
|
|
288
295
|
}, []);
|
|
289
296
|
|
|
290
|
-
const setIndex = useCallback(
|
|
291
|
-
(next: number) => {
|
|
292
|
-
if (!isControlled) setInternalIndex(next);
|
|
293
|
-
onIndexChange?.(next);
|
|
294
|
-
},
|
|
295
|
-
[isControlled, onIndexChange]
|
|
296
|
-
);
|
|
297
|
-
|
|
298
297
|
/*
|
|
299
298
|
* The slide the run is currently travelling to.
|
|
300
299
|
*
|
|
@@ -180,11 +180,11 @@ const CollapsibleTrigger = forwardRef<View, CollapsibleTriggerProps>(
|
|
|
180
180
|
return (
|
|
181
181
|
<Pressable
|
|
182
182
|
ref={ref}
|
|
183
|
+
{...props}
|
|
183
184
|
accessibilityRole="button"
|
|
184
185
|
accessibilityState={{ expanded: open, disabled: isDisabled }}
|
|
185
186
|
disabled={isDisabled}
|
|
186
187
|
className={trigger({ className })}
|
|
187
|
-
{...props}
|
|
188
188
|
onPress={(event) => {
|
|
189
189
|
onPress?.(event);
|
|
190
190
|
toggle();
|
|
@@ -140,10 +140,10 @@ const FieldRoot = forwardRef<View, FieldProps>(
|
|
|
140
140
|
<FieldContext.Provider value={state}>
|
|
141
141
|
<View
|
|
142
142
|
ref={ref}
|
|
143
|
+
{...props}
|
|
143
144
|
role="group"
|
|
144
145
|
accessibilityState={{ disabled }}
|
|
145
146
|
className={root({ className })}
|
|
146
|
-
{...props}
|
|
147
147
|
>
|
|
148
148
|
{textChildren(children)}
|
|
149
149
|
</View>
|
|
@@ -242,13 +242,13 @@ const FieldError = forwardRef<View, FieldErrorProps>(
|
|
|
242
242
|
return (
|
|
243
243
|
<View
|
|
244
244
|
ref={ref}
|
|
245
|
+
{...props}
|
|
245
246
|
// Web-aligned `role`, same as `Separator` already uses — RN's older
|
|
246
247
|
// accessibilityRole list has no alert, the ARIA-aligned one does.
|
|
247
248
|
role="alert"
|
|
248
249
|
accessibilityLiveRegion="polite"
|
|
249
250
|
nativeID={nativeID ?? relationshipIds[3]}
|
|
250
251
|
className={error({ className })}
|
|
251
|
-
{...props}
|
|
252
252
|
>
|
|
253
253
|
{messages ? (
|
|
254
254
|
messages.length === 1 ? (
|
|
@@ -278,7 +278,7 @@ export interface FieldSetProps extends ViewProps {
|
|
|
278
278
|
const FieldSet = forwardRef<View, FieldSetProps>(
|
|
279
279
|
({ className, ...props }, ref) => {
|
|
280
280
|
const { set } = fieldVariants();
|
|
281
|
-
return <View ref={ref} role="group" className={set({ className })}
|
|
281
|
+
return <View ref={ref} {...props} role="group" className={set({ className })} />;
|
|
282
282
|
}
|
|
283
283
|
);
|
|
284
284
|
FieldSet.displayName = 'Field.Set';
|
|
@@ -293,7 +293,7 @@ const FieldLegend = forwardRef<RNText, FieldLegendProps>(
|
|
|
293
293
|
({ className, variant = 'legend', ...props }, ref) => {
|
|
294
294
|
const { legend } = fieldVariants({ legendVariant: variant });
|
|
295
295
|
return (
|
|
296
|
-
<Text ref={ref} role="heading" className={legend({ className })}
|
|
296
|
+
<Text ref={ref} {...props} role="heading" className={legend({ className })} />
|
|
297
297
|
);
|
|
298
298
|
}
|
|
299
299
|
);
|
|
@@ -81,9 +81,9 @@ const LabelRoot = forwardRef<View, LabelProps>(
|
|
|
81
81
|
<LabelContext.Provider value={state}>
|
|
82
82
|
<View
|
|
83
83
|
ref={ref}
|
|
84
|
+
{...props}
|
|
84
85
|
className={root({ className })}
|
|
85
86
|
accessibilityState={{ disabled: isDisabled }}
|
|
86
|
-
{...props}
|
|
87
87
|
>
|
|
88
88
|
{/* Bare strings are the common case — wrap them so callers don't have to. */}
|
|
89
89
|
{textChildren(children, (text) => <LabelText>{text}</LabelText>)}
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
* ## How it loops
|
|
16
16
|
*
|
|
17
17
|
* The content is measured once, then laid out end to end enough times to cover
|
|
18
|
-
* the container
|
|
19
|
-
*
|
|
20
|
-
* content
|
|
21
|
-
*
|
|
18
|
+
* the container. One track holds every copy and it is that track, not the
|
|
19
|
+
* copies, that moves. The track has a fixed copy budget: exceptionally short
|
|
20
|
+
* content gets extra whitespace instead of multiplying its React subtree
|
|
21
|
+
* hundreds of times. It travels exactly one layout period and starts over, so
|
|
22
|
+
* the state it ends on is the state it began on and the seam never shows.
|
|
22
23
|
*
|
|
23
24
|
* The travel is a linear timing driven on the UI thread, so it costs nothing
|
|
24
25
|
* per frame in JavaScript and keeps running while the thread is busy.
|
|
@@ -43,8 +44,10 @@
|
|
|
43
44
|
* Screen readers get one copy. The rest are duplicates of content already
|
|
44
45
|
* announced, and hearing a sponsor list four times over is not thoroughness.
|
|
45
46
|
*/
|
|
46
|
-
import { useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
47
|
+
import { useEffect, useMemo, useState, type ComponentType, type ReactNode } from 'react';
|
|
47
48
|
import {
|
|
49
|
+
Platform,
|
|
50
|
+
Pressable,
|
|
48
51
|
ScrollView,
|
|
49
52
|
StyleSheet,
|
|
50
53
|
View,
|
|
@@ -61,6 +64,7 @@ import Animated, {
|
|
|
61
64
|
withTiming,
|
|
62
65
|
} from 'react-native-reanimated';
|
|
63
66
|
import { useDirectionSign } from '../../hooks/use-direction';
|
|
67
|
+
import { Text } from '../../primitives/text';
|
|
64
68
|
import { cn } from '../../utils/cn';
|
|
65
69
|
import {
|
|
66
70
|
DEFAULT_MARQUEE_SPEED,
|
|
@@ -70,6 +74,9 @@ import {
|
|
|
70
74
|
|
|
71
75
|
export type MarqueeDirection = 'horizontal' | 'vertical';
|
|
72
76
|
|
|
77
|
+
type InertViewProps = ViewProps & { inert?: boolean };
|
|
78
|
+
const InertView = View as ComponentType<InertViewProps>;
|
|
79
|
+
|
|
73
80
|
export interface MarqueeProps extends Omit<ViewProps, 'children'> {
|
|
74
81
|
className?: string;
|
|
75
82
|
/** The content to repeat. Measured once, then tiled along the axis. */
|
|
@@ -81,7 +88,7 @@ export interface MarqueeProps extends Omit<ViewProps, 'children'> {
|
|
|
81
88
|
* rather than moving faster.
|
|
82
89
|
*/
|
|
83
90
|
speed?: number;
|
|
84
|
-
/**
|
|
91
|
+
/** Minimum gap between the end of one copy and the start of the next. */
|
|
85
92
|
spacing?: number;
|
|
86
93
|
/** Axis the content travels along. */
|
|
87
94
|
direction?: MarqueeDirection;
|
|
@@ -92,6 +99,14 @@ export interface MarqueeProps extends Omit<ViewProps, 'children'> {
|
|
|
92
99
|
reverse?: boolean;
|
|
93
100
|
/** Set false to hold the content where it is. */
|
|
94
101
|
playing?: boolean;
|
|
102
|
+
/** Show the built-in user pause/play control while motion is enabled. */
|
|
103
|
+
showPauseControl?: boolean;
|
|
104
|
+
/** Visible and spoken label for the moving state. */
|
|
105
|
+
pauseLabel?: string;
|
|
106
|
+
/** Visible and spoken label for the user-paused state. */
|
|
107
|
+
playLabel?: string;
|
|
108
|
+
/** Reports changes made by the built-in control. */
|
|
109
|
+
onPlayingChange?: (playing: boolean) => void;
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
export function Marquee({
|
|
@@ -102,6 +117,10 @@ export function Marquee({
|
|
|
102
117
|
direction = 'horizontal',
|
|
103
118
|
reverse = false,
|
|
104
119
|
playing = true,
|
|
120
|
+
showPauseControl = true,
|
|
121
|
+
pauseLabel = 'Pause',
|
|
122
|
+
playLabel = 'Play',
|
|
123
|
+
onPlayingChange,
|
|
105
124
|
style,
|
|
106
125
|
onLayout,
|
|
107
126
|
...props
|
|
@@ -114,7 +133,9 @@ export function Marquee({
|
|
|
114
133
|
|
|
115
134
|
const [viewport, setViewport] = useState(0);
|
|
116
135
|
const [content, setContent] = useState(0);
|
|
136
|
+
const [userPaused, setUserPaused] = useState(false);
|
|
117
137
|
const speed = normalizeMarqueeSpeed(speedProp);
|
|
138
|
+
const moving = playing && !userPaused;
|
|
118
139
|
|
|
119
140
|
// The distance from one copy to the same point on the next, and therefore
|
|
120
141
|
// both the layout step and the exact loop length. They are the same number
|
|
@@ -136,7 +157,7 @@ export function Marquee({
|
|
|
136
157
|
useEffect(() => {
|
|
137
158
|
cancelAnimation(offset);
|
|
138
159
|
offset.value = 0;
|
|
139
|
-
if (reducedMotion || !
|
|
160
|
+
if (reducedMotion || !moving || period <= 0 || speed <= 0) return undefined;
|
|
140
161
|
offset.value = withRepeat(
|
|
141
162
|
withTiming(period, {
|
|
142
163
|
duration: (period / speed) * 1000,
|
|
@@ -146,7 +167,7 @@ export function Marquee({
|
|
|
146
167
|
false
|
|
147
168
|
);
|
|
148
169
|
return () => cancelAnimation(offset);
|
|
149
|
-
}, [offset, period,
|
|
170
|
+
}, [offset, period, moving, reducedMotion, speed]);
|
|
150
171
|
|
|
151
172
|
const trackStyle = useAnimatedStyle(() => {
|
|
152
173
|
const travel = (reverse ? offset.value : -offset.value) * flip;
|
|
@@ -200,7 +221,12 @@ export function Marquee({
|
|
|
200
221
|
accessibilityElementsHidden
|
|
201
222
|
importantForAccessibility="no-hide-descendants"
|
|
202
223
|
>
|
|
203
|
-
<
|
|
224
|
+
<InertView
|
|
225
|
+
inert={Platform.OS === 'web' ? true : undefined}
|
|
226
|
+
onLayout={onContentLayout}
|
|
227
|
+
>
|
|
228
|
+
{children}
|
|
229
|
+
</InertView>
|
|
204
230
|
</ScrollView>
|
|
205
231
|
|
|
206
232
|
<Animated.View style={[StyleSheet.absoluteFill, trackStyle]} pointerEvents="box-none">
|
|
@@ -210,18 +236,36 @@ export function Marquee({
|
|
|
210
236
|
const spoken = index === 1;
|
|
211
237
|
const at = (index - 1) * period;
|
|
212
238
|
return (
|
|
213
|
-
<
|
|
239
|
+
<InertView
|
|
214
240
|
key={index}
|
|
215
241
|
style={[styles.copy, horizontal ? { left: at } : { top: at }]}
|
|
216
|
-
pointerEvents=
|
|
242
|
+
pointerEvents={spoken ? 'box-none' : 'none'}
|
|
243
|
+
inert={Platform.OS === 'web' && !spoken ? true : undefined}
|
|
244
|
+
aria-hidden={!spoken}
|
|
217
245
|
accessibilityElementsHidden={!spoken}
|
|
218
246
|
importantForAccessibility={spoken ? 'auto' : 'no-hide-descendants'}
|
|
219
247
|
>
|
|
220
248
|
{children}
|
|
221
|
-
</
|
|
249
|
+
</InertView>
|
|
222
250
|
);
|
|
223
251
|
})}
|
|
224
252
|
</Animated.View>
|
|
253
|
+
{showPauseControl && playing && period > 0 && speed > 0 ? (
|
|
254
|
+
<Pressable
|
|
255
|
+
accessibilityRole="button"
|
|
256
|
+
accessibilityLabel={userPaused ? playLabel : pauseLabel}
|
|
257
|
+
className="absolute bottom-2 end-2 min-h-12 min-w-12 items-center justify-center rounded-full border border-border bg-background/95 px-3"
|
|
258
|
+
onPress={() => {
|
|
259
|
+
const next = !userPaused;
|
|
260
|
+
setUserPaused(next);
|
|
261
|
+
onPlayingChange?.(!next);
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
<Text className="text-xs font-medium text-foreground">
|
|
265
|
+
{userPaused ? playLabel : pauseLabel}
|
|
266
|
+
</Text>
|
|
267
|
+
</Pressable>
|
|
268
|
+
) : null}
|
|
225
269
|
</View>
|
|
226
270
|
);
|
|
227
271
|
}
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export const DEFAULT_MARQUEE_SPEED = 40;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Includes the copy just before and just after the viewport. Keeping this
|
|
19
|
+
* fixed prevents tiny content (for example a one-point separator) from
|
|
20
|
+
* multiplying an arbitrary React subtree hundreds of times.
|
|
21
|
+
*/
|
|
22
|
+
export const MAX_MARQUEE_COPIES = 32;
|
|
23
|
+
|
|
17
24
|
export function normalizeMarqueeSpeed(value: number): number {
|
|
18
25
|
if (!Number.isFinite(value)) return DEFAULT_MARQUEE_SPEED;
|
|
19
26
|
return Math.max(value, 0);
|
|
@@ -31,7 +38,23 @@ export function marqueeCopyCount(
|
|
|
31
38
|
): { period: number; count: number } {
|
|
32
39
|
const safeViewport = Number.isFinite(viewport) ? Math.max(viewport, 0) : 0;
|
|
33
40
|
const safeContent = Number.isFinite(content) ? Math.max(content, 0) : 0;
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const naturalPeriod =
|
|
42
|
+
safeContent > 0 ? safeContent + normalizeMarqueeSpacing(spacing) : 0;
|
|
43
|
+
if (naturalPeriod <= 0 || safeViewport <= 0) {
|
|
44
|
+
return { period: naturalPeriod, count: 0 };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Two copies sit outside the viewport to cover one complete loop. If the
|
|
48
|
+
// requested content-and-gap period would need more than the remaining copy
|
|
49
|
+
// budget, widen the layout period (effectively adding whitespace) instead
|
|
50
|
+
// of mounting an unbounded number of identical subtrees.
|
|
51
|
+
const minimumPeriod = safeViewport / (MAX_MARQUEE_COPIES - 2);
|
|
52
|
+
const period = Math.max(naturalPeriod, minimumPeriod);
|
|
53
|
+
return {
|
|
54
|
+
period,
|
|
55
|
+
count: Math.min(
|
|
56
|
+
MAX_MARQUEE_COPIES,
|
|
57
|
+
Math.ceil(safeViewport / period) + 2
|
|
58
|
+
),
|
|
59
|
+
};
|
|
37
60
|
}
|
|
@@ -296,11 +296,15 @@ function PlanTitle({ className, children, ...props }: PlanTitleProps) {
|
|
|
296
296
|
const { title } = planVariants();
|
|
297
297
|
|
|
298
298
|
if (isStreaming && typeof children === 'string') {
|
|
299
|
-
return
|
|
299
|
+
return (
|
|
300
|
+
<Shimmer accessibilityRole="header" textClassName={cn(title(), className)}>
|
|
301
|
+
{children}
|
|
302
|
+
</Shimmer>
|
|
303
|
+
);
|
|
300
304
|
}
|
|
301
305
|
|
|
302
306
|
return (
|
|
303
|
-
<Text accessibilityRole="header" className={cn(title(), className)}
|
|
307
|
+
<Text {...props} accessibilityRole="header" className={cn(title(), className)}>
|
|
304
308
|
{children}
|
|
305
309
|
</Text>
|
|
306
310
|
);
|
|
@@ -97,7 +97,9 @@ import Svg, {
|
|
|
97
97
|
Rect,
|
|
98
98
|
} from 'react-native-svg';
|
|
99
99
|
import { useCSSVariable } from 'uniwind';
|
|
100
|
+
import { ChartAccessibilityData, type ChartAccessibilityProps } from '../../primitives/chart-accessibility';
|
|
100
101
|
import { Text } from '../../primitives/text';
|
|
102
|
+
import { finiteChartNumber } from '../../primitives/finite-chart';
|
|
101
103
|
import {
|
|
102
104
|
areaPath,
|
|
103
105
|
bandOf,
|
|
@@ -260,7 +262,7 @@ export function usePlotCursor(): { activeIndex: number; activePoint: PlotDatum |
|
|
|
260
262
|
};
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
export interface PlotProps extends ViewProps {
|
|
265
|
+
export interface PlotProps extends ViewProps, ChartAccessibilityProps<PlotDatum> {
|
|
264
266
|
className?: string;
|
|
265
267
|
/** The rows. Each one is a position along the x-axis. */
|
|
266
268
|
data: PlotDatum[];
|
|
@@ -339,6 +341,11 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
339
341
|
xScale: xScaleProp,
|
|
340
342
|
curve = 'monotone',
|
|
341
343
|
onActiveIndexChange,
|
|
344
|
+
accessible,
|
|
345
|
+
accessibilityLabel,
|
|
346
|
+
accessibilityHint,
|
|
347
|
+
accessibilityLabelForDatum,
|
|
348
|
+
onAccessibilityDatumPress,
|
|
342
349
|
compact = false,
|
|
343
350
|
children,
|
|
344
351
|
...props
|
|
@@ -411,9 +418,9 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
411
418
|
*/
|
|
412
419
|
const seriesKeys = useMemo(() => series.map(([key]) => key), [series]);
|
|
413
420
|
const extent = useMemo<[number, number]>(() => {
|
|
414
|
-
const lowPin = yDomain?.[0];
|
|
415
|
-
const highPin = yDomain?.[1];
|
|
416
|
-
if (
|
|
421
|
+
const lowPin = finiteChartNumber(yDomain?.[0]);
|
|
422
|
+
const highPin = finiteChartNumber(yDomain?.[1]);
|
|
423
|
+
if (lowPin !== undefined && highPin !== undefined) {
|
|
417
424
|
return [lowPin, highPin];
|
|
418
425
|
}
|
|
419
426
|
|
|
@@ -421,8 +428,8 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
421
428
|
let max = -Infinity;
|
|
422
429
|
for (const row of data) {
|
|
423
430
|
for (const key of seriesKeys) {
|
|
424
|
-
const value = row[key];
|
|
425
|
-
if (
|
|
431
|
+
const value = finiteChartNumber(row[key]);
|
|
432
|
+
if (value === undefined) continue;
|
|
426
433
|
if (value < min) min = value;
|
|
427
434
|
if (value > max) max = value;
|
|
428
435
|
}
|
|
@@ -432,8 +439,8 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
432
439
|
// Nothing to measure. Fall back to whatever was pinned, and to a unit
|
|
433
440
|
// domain when nothing was — dividing by a span of zero is how a mark
|
|
434
441
|
// ends up drawn at infinity.
|
|
435
|
-
const low =
|
|
436
|
-
const high =
|
|
442
|
+
const low = lowPin ?? 0;
|
|
443
|
+
const high = highPin ?? low + 1;
|
|
437
444
|
return [low, high];
|
|
438
445
|
}
|
|
439
446
|
|
|
@@ -477,8 +484,8 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
477
484
|
const derivedHigh = nice ? niceHigh : max + headroom * highRoom;
|
|
478
485
|
|
|
479
486
|
return [
|
|
480
|
-
|
|
481
|
-
|
|
487
|
+
lowPin ?? derivedLow,
|
|
488
|
+
highPin ?? derivedHigh,
|
|
482
489
|
];
|
|
483
490
|
}, [data, yDomain, seriesKeys, hasBars, nice]);
|
|
484
491
|
|
|
@@ -619,7 +626,26 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
619
626
|
<PlotContext.Provider value={context}>
|
|
620
627
|
<View {...props} style={props.style} className={cn('w-full', className)}>
|
|
621
628
|
{header}
|
|
622
|
-
<
|
|
629
|
+
<ChartAccessibilityData
|
|
630
|
+
chart="Plot"
|
|
631
|
+
data={data}
|
|
632
|
+
disabled={accessible === false || status === 'loading'}
|
|
633
|
+
accessibilityLabel={accessibilityLabel}
|
|
634
|
+
accessibilityHint={accessibilityHint}
|
|
635
|
+
accessibilityLabelForDatum={accessibilityLabelForDatum}
|
|
636
|
+
onAccessibilityDatumPress={onAccessibilityDatumPress}
|
|
637
|
+
valueOf={(datum) => [
|
|
638
|
+
[xDataKey, datum[xDataKey]],
|
|
639
|
+
...seriesKeys.map((key) => [key, datum[key]] as [string, unknown]),
|
|
640
|
+
]}
|
|
641
|
+
/>
|
|
642
|
+
<View
|
|
643
|
+
onLayout={onLayout}
|
|
644
|
+
style={{ aspectRatio }}
|
|
645
|
+
className="w-full"
|
|
646
|
+
accessibilityElementsHidden
|
|
647
|
+
importantForAccessibility="no-hide-descendants"
|
|
648
|
+
>
|
|
623
649
|
{plot.width > 0 ? (
|
|
624
650
|
<>
|
|
625
651
|
<Svg width="100%" height="100%" style={StyleSheet.absoluteFill}>
|