panelui-native 0.71.0 → 0.73.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.
Files changed (146) hide show
  1. package/README.md +2 -1
  2. package/lib/module/components/accordion/index.js +10 -4
  3. package/lib/module/components/accordion/index.js.map +1 -1
  4. package/lib/module/components/area-chart/index.js +1 -0
  5. package/lib/module/components/area-chart/index.js.map +1 -1
  6. package/lib/module/components/breadcrumb/index.js +2 -1
  7. package/lib/module/components/breadcrumb/index.js.map +1 -1
  8. package/lib/module/components/button/index.js +7 -5
  9. package/lib/module/components/button/index.js.map +1 -1
  10. package/lib/module/components/carousel/index.js +1 -1
  11. package/lib/module/components/carousel/index.js.map +1 -1
  12. package/lib/module/components/chip/index.js +2 -2
  13. package/lib/module/components/chip/index.js.map +1 -1
  14. package/lib/module/components/code-block/index.js.map +1 -1
  15. package/lib/module/components/collapsible/index.js +5 -1
  16. package/lib/module/components/collapsible/index.js.map +1 -1
  17. package/lib/module/components/color-picker/index.js.map +1 -1
  18. package/lib/module/components/fab/index.js +7 -6
  19. package/lib/module/components/fab/index.js.map +1 -1
  20. package/lib/module/components/flow/index.js +1 -1
  21. package/lib/module/components/flow/index.js.map +1 -1
  22. package/lib/module/components/frame/index.js +2 -2
  23. package/lib/module/components/frame/index.js.map +1 -1
  24. package/lib/module/components/grid-item/index.js +2 -2
  25. package/lib/module/components/grid-item/index.js.map +1 -1
  26. package/lib/module/components/heatmap-chart/index.js +1 -0
  27. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  28. package/lib/module/components/hex-chart/index.js +1 -0
  29. package/lib/module/components/hex-chart/index.js.map +1 -1
  30. package/lib/module/components/input-group/index.js +7 -2
  31. package/lib/module/components/input-group/index.js.map +1 -1
  32. package/lib/module/components/item/index.js +2 -2
  33. package/lib/module/components/item/index.js.map +1 -1
  34. package/lib/module/components/line-chart/index.js +1 -0
  35. package/lib/module/components/line-chart/index.js.map +1 -1
  36. package/lib/module/components/marker/index.js +2 -2
  37. package/lib/module/components/marker/index.js.map +1 -1
  38. package/lib/module/components/marquee/index.js +9 -12
  39. package/lib/module/components/marquee/index.js.map +1 -1
  40. package/lib/module/components/marquee/marquee-math.js +39 -0
  41. package/lib/module/components/marquee/marquee-math.js.map +1 -0
  42. package/lib/module/components/menu/index.js +11 -3
  43. package/lib/module/components/menu/index.js.map +1 -1
  44. package/lib/module/components/pagination/index.js +27 -11
  45. package/lib/module/components/pagination/index.js.map +1 -1
  46. package/lib/module/components/panelside/index.js +7 -2
  47. package/lib/module/components/panelside/index.js.map +1 -1
  48. package/lib/module/components/planner/index.js +26 -5
  49. package/lib/module/components/planner/index.js.map +1 -1
  50. package/lib/module/components/plot/index.js +105 -42
  51. package/lib/module/components/plot/index.js.map +1 -1
  52. package/lib/module/components/post/index.js +4 -4
  53. package/lib/module/components/post/index.js.map +1 -1
  54. package/lib/module/components/search-bar/index.js +331 -0
  55. package/lib/module/components/search-bar/index.js.map +1 -0
  56. package/lib/module/components/table/index.js +3 -4
  57. package/lib/module/components/table/index.js.map +1 -1
  58. package/lib/module/components/timeline/index.js +288 -39
  59. package/lib/module/components/timeline/index.js.map +1 -1
  60. package/lib/module/components/toggle-button/index.js +1 -1
  61. package/lib/module/components/toggle-button/index.js.map +1 -1
  62. package/lib/module/components/tree/index.js +13 -8
  63. package/lib/module/components/tree/index.js.map +1 -1
  64. package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -1
  65. package/lib/module/index.js +1 -0
  66. package/lib/module/index.js.map +1 -1
  67. package/lib/module/utils/chart.js +50 -7
  68. package/lib/module/utils/chart.js.map +1 -1
  69. package/lib/typescript/src/components/accordion/index.d.ts +2 -2
  70. package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
  71. package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
  72. package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
  73. package/lib/typescript/src/components/button/index.d.ts.map +1 -1
  74. package/lib/typescript/src/components/code-block/index.d.ts +3 -3
  75. package/lib/typescript/src/components/code-block/index.d.ts.map +1 -1
  76. package/lib/typescript/src/components/collapsible/index.d.ts +2 -2
  77. package/lib/typescript/src/components/collapsible/index.d.ts.map +1 -1
  78. package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -1
  79. package/lib/typescript/src/components/fab/index.d.ts +4 -2
  80. package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
  81. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  82. package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
  83. package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
  84. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  85. package/lib/typescript/src/components/marquee/index.d.ts +1 -1
  86. package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
  87. package/lib/typescript/src/components/marquee/marquee-math.d.ts +21 -0
  88. package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -0
  89. package/lib/typescript/src/components/menu/index.d.ts +1 -1
  90. package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
  91. package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
  92. package/lib/typescript/src/components/panelside/index.d.ts +1 -1
  93. package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
  94. package/lib/typescript/src/components/plan/index.d.ts +2 -2
  95. package/lib/typescript/src/components/planner/index.d.ts.map +1 -1
  96. package/lib/typescript/src/components/plot/index.d.ts +59 -5
  97. package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
  98. package/lib/typescript/src/components/search-bar/index.d.ts +216 -0
  99. package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -0
  100. package/lib/typescript/src/components/timeline/index.d.ts +35 -2
  101. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  102. package/lib/typescript/src/components/tree/index.d.ts +3 -4
  103. package/lib/typescript/src/components/tree/index.d.ts.map +1 -1
  104. package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +2 -2
  105. package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -1
  106. package/lib/typescript/src/index.d.ts +2 -1
  107. package/lib/typescript/src/index.d.ts.map +1 -1
  108. package/lib/typescript/src/utils/chart.d.ts +28 -3
  109. package/lib/typescript/src/utils/chart.d.ts.map +1 -1
  110. package/package.json +1 -1
  111. package/src/components/accordion/index.tsx +17 -7
  112. package/src/components/area-chart/index.tsx +8 -0
  113. package/src/components/breadcrumb/index.tsx +2 -1
  114. package/src/components/button/index.tsx +3 -2
  115. package/src/components/carousel/index.tsx +1 -1
  116. package/src/components/chip/index.tsx +2 -2
  117. package/src/components/code-block/index.tsx +3 -3
  118. package/src/components/collapsible/index.tsx +13 -4
  119. package/src/components/color-picker/index.tsx +3 -2
  120. package/src/components/fab/index.tsx +21 -11
  121. package/src/components/flow/index.tsx +1 -1
  122. package/src/components/frame/index.tsx +2 -2
  123. package/src/components/grid-item/index.tsx +2 -2
  124. package/src/components/heatmap-chart/index.tsx +7 -1
  125. package/src/components/hex-chart/index.tsx +7 -1
  126. package/src/components/input-group/index.tsx +7 -2
  127. package/src/components/item/index.tsx +2 -2
  128. package/src/components/line-chart/index.tsx +9 -0
  129. package/src/components/marker/index.tsx +2 -2
  130. package/src/components/marquee/index.tsx +14 -12
  131. package/src/components/marquee/marquee-math.ts +37 -0
  132. package/src/components/menu/index.tsx +11 -3
  133. package/src/components/pagination/index.tsx +26 -13
  134. package/src/components/panelside/index.tsx +7 -2
  135. package/src/components/plan/index.tsx +2 -2
  136. package/src/components/planner/index.tsx +20 -5
  137. package/src/components/plot/index.tsx +183 -39
  138. package/src/components/post/index.tsx +4 -4
  139. package/src/components/search-bar/index.tsx +436 -0
  140. package/src/components/table/index.tsx +2 -2
  141. package/src/components/timeline/index.tsx +335 -39
  142. package/src/components/toggle-button/index.tsx +1 -1
  143. package/src/components/tree/index.tsx +17 -12
  144. package/src/hooks/use-keyboard-avoidance.ts +7 -2
  145. package/src/index.ts +2 -0
  146. package/src/utils/chart.ts +66 -7
@@ -328,6 +328,7 @@ const BreadcrumbEllipsis = forwardRef<View, BreadcrumbEllipsisProps>(
328
328
  return (
329
329
  <View
330
330
  ref={ref}
331
+ {...(props as ViewProps)}
331
332
  accessibilityLabel="More"
332
333
  className={cn('h-5 items-center justify-center px-0.5', className)}
333
334
  >
@@ -339,12 +340,12 @@ const BreadcrumbEllipsis = forwardRef<View, BreadcrumbEllipsisProps>(
339
340
  return (
340
341
  <AnimatedPressable
341
342
  ref={ref}
343
+ {...props}
342
344
  accessibilityRole="button"
343
345
  accessibilityLabel="Show more"
344
346
  pressScale={0.92}
345
347
  onPress={onPress}
346
348
  className={cn('h-5 items-center justify-center px-0.5', className)}
347
- {...props}
348
349
  >
349
350
  {glyph}
350
351
  </AnimatedPressable>
@@ -300,6 +300,7 @@ export const Button = forwardRef<View, ButtonProps>(
300
300
  endContent,
301
301
  native,
302
302
  glass = false,
303
+ accessibilityState,
303
304
  ...props
304
305
  },
305
306
  ref
@@ -441,8 +442,6 @@ export const Button = forwardRef<View, ButtonProps>(
441
442
  <IconColorProvider color={contentColor}>
442
443
  <AnimatedPressable
443
444
  ref={ref}
444
- accessibilityRole="button"
445
- accessibilityState={{ disabled: isDisabled, busy: loading }}
446
445
  disabled={isDisabled}
447
446
  // Joined segments cannot borrow room from each other without making
448
447
  // two actions claim the same pixels. ButtonGroup owns that layout.
@@ -451,6 +450,8 @@ export const Button = forwardRef<View, ButtonProps>(
451
450
  pressScale={attached ? 1 : undefined}
452
451
  className={root({ className: cn(attached && ATTACHED, className) })}
453
452
  {...props}
453
+ accessibilityRole="button"
454
+ accessibilityState={{ ...accessibilityState, disabled: isDisabled, busy: loading }}
454
455
  >
455
456
  {loading ? (
456
457
  <Spinner size={SPINNER_SIZE[resolvedSize ?? 'md']} className={spinner()} />
@@ -799,6 +799,7 @@ function makeArrow(direction: 'previous' | 'next') {
799
799
 
800
800
  return (
801
801
  <Pressable
802
+ {...props}
802
803
  disabled={disabled}
803
804
  onPress={direction === 'next' ? next : previous}
804
805
  accessibilityRole="button"
@@ -809,7 +810,6 @@ function makeArrow(direction: 'previous' | 'next') {
809
810
  disabled ? 'opacity-30' : 'active:bg-foreground/10',
810
811
  className
811
812
  )}
812
- {...props}
813
813
  >
814
814
  {children ??
815
815
  (direction === 'next' ? (
@@ -285,9 +285,9 @@ const ChipRoot = forwardRef<View, ChipProps>(
285
285
  {pressable ? (
286
286
  <AnimatedPressable
287
287
  ref={ref}
288
- onPress={handlePress}
289
- {...sharedProps}
290
288
  {...props}
289
+ {...sharedProps}
290
+ onPress={handlePress}
291
291
  >
292
292
  {body}
293
293
  </AnimatedPressable>
@@ -32,7 +32,7 @@
32
32
  * block in a text part, or the fields of a tool call that wrote a file.
33
33
  */
34
34
  import { createContext, useContext, useMemo, type ReactNode } from 'react';
35
- import { ScrollView, View, type ViewProps } from 'react-native';
35
+ import { ScrollView, View, type TextProps, type ViewProps } from 'react-native';
36
36
  import { tv } from 'tailwind-variants';
37
37
  import { useCSSVariable } from 'uniwind';
38
38
  import { useCopyToClipboard } from '../../hooks/use-copy-to-clipboard';
@@ -240,7 +240,7 @@ function CodeBlockHeader({ className, children, ...props }: CodeBlockHeaderProps
240
240
  );
241
241
  }
242
242
 
243
- export interface CodeBlockFilenameProps extends ViewProps {
243
+ export interface CodeBlockFilenameProps extends TextProps {
244
244
  className?: string;
245
245
  children?: ReactNode;
246
246
  }
@@ -254,7 +254,7 @@ function CodeBlockFilename({ className, children, ...props }: CodeBlockFilenameP
254
254
  );
255
255
  }
256
256
 
257
- export interface CodeBlockLanguageProps extends ViewProps {
257
+ export interface CodeBlockLanguageProps extends TextProps {
258
258
  className?: string;
259
259
  children?: ReactNode;
260
260
  }
@@ -49,7 +49,13 @@ import {
49
49
  useState,
50
50
  type ReactNode,
51
51
  } from 'react';
52
- import { Pressable, View, type Text as RNText, type ViewProps } from 'react-native';
52
+ import {
53
+ Pressable,
54
+ View,
55
+ type PressableProps,
56
+ type Text as RNText,
57
+ type ViewProps,
58
+ } from 'react-native';
53
59
  import Animated, {
54
60
  useAnimatedStyle,
55
61
  useReducedMotion,
@@ -160,14 +166,14 @@ const CollapsibleRoot = forwardRef<View, CollapsibleProps>(
160
166
  );
161
167
  CollapsibleRoot.displayName = 'Collapsible';
162
168
 
163
- export interface CollapsibleTriggerProps extends ViewProps {
169
+ export interface CollapsibleTriggerProps extends Omit<PressableProps, 'children'> {
164
170
  className?: string;
165
171
  children?: ReactNode;
166
172
  }
167
173
 
168
174
  /** The pressable header row. Bare strings are wrapped in the title style. */
169
175
  const CollapsibleTrigger = forwardRef<View, CollapsibleTriggerProps>(
170
- ({ className, children, ...props }, ref) => {
176
+ ({ className, children, onPress, ...props }, ref) => {
171
177
  const { open, toggle, isDisabled, variant } = useCollapsible('Collapsible.Trigger');
172
178
  const { trigger, title } = collapsibleVariants({ variant });
173
179
 
@@ -177,9 +183,12 @@ const CollapsibleTrigger = forwardRef<View, CollapsibleTriggerProps>(
177
183
  accessibilityRole="button"
178
184
  accessibilityState={{ expanded: open, disabled: isDisabled }}
179
185
  disabled={isDisabled}
180
- onPress={toggle}
181
186
  className={trigger({ className })}
182
187
  {...props}
188
+ onPress={(event) => {
189
+ onPress?.(event);
190
+ toggle();
191
+ }}
183
192
  >
184
193
  {textChildren(children, (text) => (
185
194
  <Text className={title()}>{text}</Text>
@@ -71,6 +71,7 @@ import {
71
71
  View,
72
72
  type AccessibilityActionEvent,
73
73
  type LayoutChangeEvent,
74
+ type ViewStyle,
74
75
  } from 'react-native';
75
76
  import MaskedView from '@react-native-masked-view/masked-view';
76
77
  import { LinearGradient } from 'expo-linear-gradient';
@@ -663,8 +664,8 @@ interface ChannelTrackProps {
663
664
  /** What fills the track — a hue ramp, or a masked translucency ramp. */
664
665
  children: ReactNode;
665
666
  gesture: ReturnType<typeof Gesture.Race>;
666
- thumbStyle: ReturnType<typeof useAnimatedStyle>;
667
- thumbFillStyle: ReturnType<typeof useAnimatedStyle>;
667
+ thumbStyle: ReturnType<typeof useAnimatedStyle<ViewStyle>>;
668
+ thumbFillStyle: ReturnType<typeof useAnimatedStyle<ViewStyle>>;
668
669
  onLayout: (event: LayoutChangeEvent) => void;
669
670
  size: ColorPickerSize;
670
671
  className?: string;
@@ -85,7 +85,10 @@ import { tv, type VariantProps } from 'tailwind-variants';
85
85
  import { useCSSVariable } from 'uniwind';
86
86
  import { IconColorProvider } from '../../icons';
87
87
  import { useBackHandler } from '../../hooks/use-back-handler';
88
- import { AnimatedPressable } from '../../primitives/animated-pressable';
88
+ import {
89
+ AnimatedPressable,
90
+ type AnimatedPressableProps,
91
+ } from '../../primitives/animated-pressable';
89
92
  import { Scrim } from '../../primitives/scrim';
90
93
  import { Text } from '../../primitives/text';
91
94
  import { cn } from '../../utils/cn';
@@ -180,7 +183,9 @@ function anchor(placement: FabPlacement, offset: number) {
180
183
  * Fab
181
184
  * -------------------------------------------------------------------------- */
182
185
 
183
- export interface FabProps extends Omit<ViewProps, 'children'>, FabVariantProps {
186
+ export interface FabProps
187
+ extends Omit<AnimatedPressableProps, 'children' | 'style' | 'disabled'>,
188
+ Omit<FabVariantProps, 'disabled'> {
184
189
  className?: string;
185
190
  /** The glyph. Sized by you — this is the one thing that should not guess. */
186
191
  icon?: ReactNode;
@@ -201,7 +206,8 @@ export interface FabProps extends Omit<ViewProps, 'children'>, FabVariantProps {
201
206
  placement?: FabPlacement;
202
207
  /** Distance from the edges when `placement` is set, in points. */
203
208
  offset?: number;
204
- onPress?: () => void;
209
+ /** Placement-aware view style. Press-state styling belongs in `className`. */
210
+ style?: ViewProps['style'];
205
211
  disabled?: boolean;
206
212
  /**
207
213
  * A tick on press. Off by default — needs the optional `expo-haptics`, and
@@ -231,33 +237,37 @@ const FabRoot = forwardRef<View, FabProps>(
231
237
  },
232
238
  ref
233
239
  ) => {
240
+ const isDisabled = Boolean(disabled);
234
241
  const { root, label } = fabVariants({
235
242
  size,
236
243
  variant,
237
244
  extended: extended && !!children,
238
- disabled,
245
+ disabled: isDisabled,
239
246
  });
240
247
 
241
248
  const themed = useCSSVariable(CONTENT_COLOR_VAR[variant ?? 'primary']);
242
249
  const contentColor =
243
250
  typeof themed === 'string' ? themed : undefined;
244
251
 
245
- const handlePress = useCallback(() => {
246
- if (haptics) selectionTick();
247
- onPress?.();
248
- }, [haptics, onPress]);
252
+ const handlePress = useCallback<NonNullable<AnimatedPressableProps['onPress']>>(
253
+ (event) => {
254
+ if (haptics) selectionTick();
255
+ onPress?.(event);
256
+ },
257
+ [haptics, onPress]
258
+ );
249
259
 
250
260
  return (
251
261
  <IconColorProvider color={contentColor}>
252
262
  <AnimatedPressable
253
263
  ref={ref}
264
+ {...props}
254
265
  accessibilityRole="button"
255
- accessibilityState={{ disabled }}
256
- disabled={disabled}
266
+ accessibilityState={{ disabled: isDisabled }}
267
+ disabled={isDisabled}
257
268
  onPress={handlePress}
258
269
  className={root({ className })}
259
270
  style={[placement ? anchor(placement, offset) : null, style]}
260
- {...props}
261
271
  >
262
272
  {icon}
263
273
  {extended && children ? (
@@ -1254,6 +1254,7 @@ function FlowNode({
1254
1254
  <FlowNodeContext.Provider value={id}>
1255
1255
  <GestureDetector gesture={gesture}>
1256
1256
  <Animated.View
1257
+ {...props}
1257
1258
  collapsable={false}
1258
1259
  onLayout={onLayout}
1259
1260
  style={[{ position: 'absolute', top: 0, left: 0 }, style]}
@@ -1267,7 +1268,6 @@ function FlowNode({
1267
1268
  accessibilityState={{ selected }}
1268
1269
  accessibilityActions={nodeActions}
1269
1270
  onAccessibilityAction={handleAccessibilityAction}
1270
- {...props}
1271
1271
  >
1272
1272
  {textChildren(children)}
1273
1273
  </Animated.View>
@@ -398,7 +398,7 @@ const FrameRow = forwardRef<View, FrameRowProps>(
398
398
 
399
399
  if (!onPress) {
400
400
  return (
401
- <View ref={ref} className={classes} {...(props as ViewProps)}>
401
+ <View ref={ref} {...(props as ViewProps)} className={classes}>
402
402
  {body}
403
403
  </View>
404
404
  );
@@ -407,10 +407,10 @@ const FrameRow = forwardRef<View, FrameRowProps>(
407
407
  return (
408
408
  <Pressable
409
409
  ref={ref}
410
+ {...props}
410
411
  accessibilityRole="button"
411
412
  onPress={onPress}
412
413
  className={classes}
413
- {...props}
414
414
  >
415
415
  {body}
416
416
  </Pressable>
@@ -334,9 +334,9 @@ const GridItemRoot = forwardRef<View, GridItemProps>(
334
334
  return (
335
335
  <View
336
336
  ref={ref}
337
+ {...(props as ViewProps)}
337
338
  accessibilityState={{ disabled: !!disabled }}
338
339
  className={root({ className })}
339
- {...(props as ViewProps)}
340
340
  >
341
341
  {textChildren(children)}
342
342
  </View>
@@ -346,12 +346,12 @@ const GridItemRoot = forwardRef<View, GridItemProps>(
346
346
  return (
347
347
  <AnimatedPressable
348
348
  ref={ref}
349
+ {...props}
349
350
  accessibilityRole="button"
350
351
  accessibilityState={{ disabled: !!disabled }}
351
352
  disabled={disabled}
352
353
  onPress={onPress}
353
354
  className={root({ className })}
354
- {...props}
355
355
  >
356
356
  {textChildren(children)}
357
357
  </AnimatedPressable>
@@ -628,7 +628,13 @@ const HeatmapChartRoot = forwardRef<View, HeatmapChartProps>(function HeatmapCha
628
628
  <Svg width={grid.width} height={grid.height} style={StyleSheet.absoluteFill}>
629
629
  <Defs>
630
630
  <ClipPath id={clipId}>
631
- <AnimatedRect x={0} y={0} height={grid.height} animatedProps={clipProps} />
631
+ <AnimatedRect
632
+ x={0}
633
+ y={0}
634
+ width={grid.width}
635
+ height={grid.height}
636
+ animatedProps={clipProps}
637
+ />
632
638
  </ClipPath>
633
639
  </Defs>
634
640
  <G clipPath={`url(#${clipId})`}>
@@ -534,7 +534,13 @@ const HexChartRoot = forwardRef<HexChartHandle, HexChartProps>(function HexChart
534
534
  animatedProps={ellipseProps}
535
535
  />
536
536
  ) : (
537
- <AnimatedRect x={0} y={0} height={height} animatedProps={rectProps} />
537
+ <AnimatedRect
538
+ x={0}
539
+ y={0}
540
+ width={width}
541
+ height={height}
542
+ animatedProps={rectProps}
543
+ />
538
544
  )}
539
545
  </ClipPath>
540
546
  </Defs>
@@ -146,8 +146,13 @@ const InputGroupInput = forwardRef<TextInput, InputGroupInputProps>(
146
146
  style={[
147
147
  // Only override padding on the side that actually has a decorator —
148
148
  // a 0 here would wipe out the field's default horizontal padding.
149
- group?.prefixWidth ? { paddingLeft: group.prefixWidth } : null,
150
- group?.suffixWidth ? { paddingRight: group.suffixWidth } : null,
149
+ //
150
+ // `paddingStart`/`paddingEnd`, not left/right: the decorators are
151
+ // inset with `start-0`/`end-0`, so under `Direction dir="rtl"` Yoga
152
+ // moves them to the other edge. Physical padding stays where it was
153
+ // written and leaves the value running underneath them.
154
+ group?.prefixWidth ? { paddingStart: group.prefixWidth } : null,
155
+ group?.suffixWidth ? { paddingEnd: group.suffixWidth } : null,
151
156
  style,
152
157
  ]}
153
158
  {...props}
@@ -148,21 +148,21 @@ const ItemRoot = forwardRef<View, ItemProps>(
148
148
  const body = !onPress ? (
149
149
  <View
150
150
  ref={ref}
151
+ {...(props as ViewProps)}
151
152
  accessibilityState={{ disabled: !!disabled }}
152
153
  className={root({ className })}
153
- {...(props as ViewProps)}
154
154
  >
155
155
  {textChildren(children)}
156
156
  </View>
157
157
  ) : (
158
158
  <AnimatedPressable
159
159
  ref={ref}
160
+ {...props}
160
161
  accessibilityRole="button"
161
162
  accessibilityState={{ disabled: !!disabled }}
162
163
  disabled={disabled}
163
164
  onPress={onPress}
164
165
  className={root({ className })}
165
- {...props}
166
166
  >
167
167
  {textChildren(children)}
168
168
  </AnimatedPressable>
@@ -477,11 +477,20 @@ const LineChartRoot = forwardRef<LineChartHandle, LineChartProps>(function LineC
477
477
  * One clip for everything in the plot. Sharing it is what
478
478
  * makes the reveal read as the chart arriving, rather than as
479
479
  * three separate things animating in at once.
480
+ *
481
+ * `width` is set statically as well as animated, and it has
482
+ * to be. Animated props on an element inside `Defs` do not
483
+ * reach the native clip on every platform, and with the width
484
+ * coming only from the animation there is no width at all
485
+ * when they do not — an empty clip, and a chart whose marks
486
+ * are all invisible while its axes draw normally. Declared,
487
+ * the worst case is the reveal not playing.
480
488
  */}
481
489
  <ClipPath id={clipId}>
482
490
  <AnimatedRect
483
491
  x={plot.left}
484
492
  y={0}
493
+ width={plot.width}
485
494
  height={size.height}
486
495
  animatedProps={clipProps}
487
496
  />
@@ -100,21 +100,21 @@ const MarkerRoot = forwardRef<View, MarkerProps>(
100
100
  const body = !onPress ? (
101
101
  <View
102
102
  ref={ref}
103
+ {...(props as ViewProps)}
103
104
  accessibilityState={{ disabled: !!disabled }}
104
105
  className={root({ className })}
105
- {...(props as ViewProps)}
106
106
  >
107
107
  {inner}
108
108
  </View>
109
109
  ) : (
110
110
  <AnimatedPressable
111
111
  ref={ref}
112
+ {...props}
112
113
  accessibilityRole="button"
113
114
  accessibilityState={{ disabled: !!disabled }}
114
115
  disabled={disabled}
115
116
  onPress={onPress}
116
117
  className={root({ className })}
117
- {...props}
118
118
  >
119
119
  {inner}
120
120
  </AnimatedPressable>
@@ -62,12 +62,11 @@ import Animated, {
62
62
  } from 'react-native-reanimated';
63
63
  import { useDirectionSign } from '../../hooks/use-direction';
64
64
  import { cn } from '../../utils/cn';
65
-
66
- /**
67
- * Points per second. Slow enough that a word stays readable as it crosses,
68
- * which is the speed a ticker is actually for.
69
- */
70
- const DEFAULT_SPEED = 40;
65
+ import {
66
+ DEFAULT_MARQUEE_SPEED,
67
+ marqueeCopyCount,
68
+ normalizeMarqueeSpeed,
69
+ } from './marquee-math';
71
70
 
72
71
  export type MarqueeDirection = 'horizontal' | 'vertical';
73
72
 
@@ -98,7 +97,7 @@ export interface MarqueeProps extends Omit<ViewProps, 'children'> {
98
97
  export function Marquee({
99
98
  className,
100
99
  children,
101
- speed = DEFAULT_SPEED,
100
+ speed: speedProp = DEFAULT_MARQUEE_SPEED,
102
101
  spacing = 0,
103
102
  direction = 'horizontal',
104
103
  reverse = false,
@@ -115,19 +114,22 @@ export function Marquee({
115
114
 
116
115
  const [viewport, setViewport] = useState(0);
117
116
  const [content, setContent] = useState(0);
117
+ const speed = normalizeMarqueeSpeed(speedProp);
118
118
 
119
119
  // The distance from one copy to the same point on the next, and therefore
120
120
  // both the layout step and the exact loop length. They are the same number
121
121
  // on purpose: taking the gap from anywhere else is how a seam appears.
122
- const period = content > 0 ? content + spacing : 0;
122
+ const layout = useMemo(
123
+ () => marqueeCopyCount(viewport, content, spacing),
124
+ [viewport, content, spacing]
125
+ );
126
+ const { period } = layout;
123
127
 
124
128
  const copies = useMemo(() => {
125
- if (period <= 0 || viewport <= 0) return [];
126
129
  // Enough to span the container, plus one trailing into view and one
127
130
  // already past it — the two the travel consumes before the loop restarts.
128
- const count = Math.ceil(viewport / period) + 2;
129
- return Array.from({ length: count }, (_, index) => index);
130
- }, [period, viewport]);
131
+ return Array.from({ length: layout.count }, (_, index) => index);
132
+ }, [layout.count]);
131
133
 
132
134
  const offset = useSharedValue(0);
133
135
 
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The arithmetic behind the loop, kept out of the component so it can be tested
3
+ * without rendering one.
4
+ *
5
+ * Every value here reaches a Reanimated timing or a layout offset, and both
6
+ * treat `NaN` and `Infinity` as a frame that never resolves rather than as an
7
+ * error. A marquee handed a bad number should hold still, not wedge — so the
8
+ * guards live at the boundary, once, instead of at each use.
9
+ */
10
+
11
+ /**
12
+ * Points per second. Slow enough that a word stays readable as it crosses,
13
+ * which is the speed a ticker is actually for.
14
+ */
15
+ export const DEFAULT_MARQUEE_SPEED = 40;
16
+
17
+ export function normalizeMarqueeSpeed(value: number): number {
18
+ if (!Number.isFinite(value)) return DEFAULT_MARQUEE_SPEED;
19
+ return Math.max(value, 0);
20
+ }
21
+
22
+ export function normalizeMarqueeSpacing(value: number): number {
23
+ if (!Number.isFinite(value)) return 0;
24
+ return Math.max(value, 0);
25
+ }
26
+
27
+ export function marqueeCopyCount(
28
+ viewport: number,
29
+ content: number,
30
+ spacing: number
31
+ ): { period: number; count: number } {
32
+ const safeViewport = Number.isFinite(viewport) ? Math.max(viewport, 0) : 0;
33
+ const safeContent = Number.isFinite(content) ? Math.max(content, 0) : 0;
34
+ const period = safeContent > 0 ? safeContent + normalizeMarqueeSpacing(spacing) : 0;
35
+ if (period <= 0 || safeViewport <= 0) return { period, count: 0 };
36
+ return { period, count: Math.ceil(safeViewport / period) + 2 };
37
+ }
@@ -451,6 +451,8 @@ export function MenuItem({
451
451
  closeOnSelect = true,
452
452
  onSelect,
453
453
  onPress,
454
+ onPressIn,
455
+ onPressOut,
454
456
  ...props
455
457
  }: MenuItemProps) {
456
458
  const { close, haptics } = useMenu('Menu.Item');
@@ -467,15 +469,21 @@ export function MenuItem({
467
469
 
468
470
  return (
469
471
  <AnimatedPressable
472
+ {...props}
470
473
  accessibilityRole="menuitem"
471
474
  accessibilityState={{ disabled }}
472
475
  disabled={disabled}
473
476
  onPress={handlePress}
474
- onPressIn={press.onPressIn}
475
- onPressOut={press.onPressOut}
477
+ onPressIn={(event) => {
478
+ onPressIn?.(event);
479
+ press.onPressIn();
480
+ }}
481
+ onPressOut={(event) => {
482
+ onPressOut?.(event);
483
+ press.onPressOut();
484
+ }}
476
485
  style={press.rowStyle}
477
486
  className={cn(slots.item(), 'overflow-hidden', className)}
478
- {...props}
479
487
  >
480
488
  <Animated.View
481
489
  pointerEvents="none"
@@ -367,7 +367,7 @@ export interface PaginationItemProps
367
367
  * are on, so the current page is spoken as state rather than only painted.
368
368
  */
369
369
  const PaginationItem = forwardRef<View, PaginationItemProps>(
370
- ({ className, labelClassName, page, children, ...props }, ref) => {
370
+ ({ className, labelClassName, page, children, onPress, ...props }, ref) => {
371
371
  const { page: current, size, disabled, goTo } = usePagination('Pagination.Item');
372
372
  const isCurrent = page === current;
373
373
  const { item, itemLabel } = paginationVariants({ size, current: isCurrent, disabled });
@@ -375,14 +375,17 @@ const PaginationItem = forwardRef<View, PaginationItemProps>(
375
375
  return (
376
376
  <AnimatedPressable
377
377
  ref={ref}
378
+ {...props}
378
379
  accessibilityRole="button"
379
380
  accessibilityLabel={`Page ${page}`}
380
381
  accessibilityState={{ selected: isCurrent, disabled }}
381
382
  disabled={disabled}
382
383
  hitSlop={size === 'sm' ? SMALL_HIT_SLOP : undefined}
383
- onPress={() => goTo(page)}
384
+ onPress={(event) => {
385
+ onPress?.(event);
386
+ goTo(page);
387
+ }}
384
388
  className={item({ className })}
385
- {...props}
386
389
  >
387
390
  {textChildren(children ?? String(page), (text) => (
388
391
  <Text className={itemLabel({ className: labelClassName })}>{text}</Text>
@@ -437,23 +440,29 @@ function PaginationArrow({
437
440
  word,
438
441
  className,
439
442
  label,
443
+ disabled,
444
+ onPress,
440
445
  ...props
441
446
  }: PaginationArrowProps & { step: -1 | 1; part: string; word: string }) {
442
447
  const { Glyph, size, spent, color, press } = useArrow(step, part);
443
- const { item, itemLabel } = paginationVariants({ size, disabled: spent });
448
+ const isDisabled = Boolean(spent || disabled);
449
+ const { item, itemLabel } = paginationVariants({ size, disabled: isDisabled });
444
450
 
445
451
  return (
446
452
  <AnimatedPressable
453
+ {...props}
447
454
  accessibilityRole="button"
448
455
  accessibilityLabel={word}
449
- accessibilityState={{ disabled: spent }}
450
- disabled={spent}
456
+ accessibilityState={{ disabled: isDisabled }}
457
+ disabled={isDisabled}
451
458
  hitSlop={size === 'sm' ? SMALL_HIT_SLOP : undefined}
452
- onPress={press}
459
+ onPress={(event) => {
460
+ onPress?.(event);
461
+ press();
462
+ }}
453
463
  className={item({
454
464
  className: cn('flex-row gap-1', label && (size === 'sm' ? 'px-2' : 'px-3'), className),
455
465
  })}
456
- {...props}
457
466
  >
458
467
  {step === -1 ? <Glyph size={iconSize[size]} color={color} /> : null}
459
468
  {label ? <Text className={itemLabel()}>{word}</Text> : null}
@@ -489,24 +498,28 @@ export interface PaginationEllipsisProps
489
498
  * that always went forwards would strand anyone reading the row right to left.
490
499
  */
491
500
  const PaginationEllipsis = forwardRef<View, PaginationEllipsisProps>(
492
- ({ className, direction = 1, jump = 5, ...props }, ref) => {
501
+ ({ className, direction = 1, jump = 5, disabled: disabledProp, onPress, ...props }, ref) => {
493
502
  const { page, size, disabled, goTo } = usePagination('Pagination.Ellipsis');
494
503
  const color = useCSSVariable('--color-muted-foreground');
495
504
  const { ellipsis } = paginationVariants({ size });
505
+ const isDisabled = Boolean(disabled || disabledProp);
496
506
 
497
507
  return (
498
508
  <AnimatedPressable
499
509
  ref={ref}
510
+ {...props}
500
511
  accessibilityRole="button"
501
512
  accessibilityLabel={
502
513
  direction === -1 ? `Back ${jump} pages` : `Forward ${jump} pages`
503
514
  }
504
- accessibilityState={{ disabled }}
505
- disabled={disabled}
515
+ accessibilityState={{ disabled: isDisabled }}
516
+ disabled={isDisabled}
506
517
  hitSlop={size === 'sm' ? SMALL_HIT_SLOP : undefined}
507
- onPress={() => goTo(page + direction * jump)}
518
+ onPress={(event) => {
519
+ onPress?.(event);
520
+ goTo(page + direction * jump);
521
+ }}
508
522
  className={ellipsis({ className })}
509
- {...props}
510
523
  >
511
524
  <EllipsisIcon
512
525
  size={iconSize[size]}