panelui-native 0.75.0 → 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.
Files changed (155) hide show
  1. package/lib/module/components/ai-input/ai-input-growth.js +62 -0
  2. package/lib/module/components/ai-input/ai-input-growth.js.map +1 -0
  3. package/lib/module/components/ai-input/index.js +1568 -0
  4. package/lib/module/components/ai-input/index.js.map +1 -0
  5. package/lib/module/components/alert/index.js +1 -1
  6. package/lib/module/components/alert/index.js.map +1 -1
  7. package/lib/module/components/avatar/avatar-source.js +15 -0
  8. package/lib/module/components/avatar/avatar-source.js.map +1 -0
  9. package/lib/module/components/avatar/index.js +32 -31
  10. package/lib/module/components/avatar/index.js.map +1 -1
  11. package/lib/module/components/bottom-sheet/index.js +3 -2
  12. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  13. package/lib/module/components/button-group/index.js +2 -3
  14. package/lib/module/components/button-group/index.js.map +1 -1
  15. package/lib/module/components/carousel/index.js +10 -7
  16. package/lib/module/components/carousel/index.js.map +1 -1
  17. package/lib/module/components/collapsible/index.js +1 -1
  18. package/lib/module/components/collapsible/index.js.map +1 -1
  19. package/lib/module/components/drawer/index.js +18 -14
  20. package/lib/module/components/drawer/index.js.map +1 -1
  21. package/lib/module/components/field/index.js +7 -8
  22. package/lib/module/components/field/index.js.map +1 -1
  23. package/lib/module/components/label/index.js +1 -1
  24. package/lib/module/components/label/index.js.map +1 -1
  25. package/lib/module/components/marquee/index.js +36 -11
  26. package/lib/module/components/marquee/index.js.map +1 -1
  27. package/lib/module/components/marquee/marquee-math.js +22 -6
  28. package/lib/module/components/marquee/marquee-math.js.map +1 -1
  29. package/lib/module/components/plan/index.js +2 -1
  30. package/lib/module/components/plan/index.js.map +1 -1
  31. package/lib/module/components/plot/index.js +27 -9
  32. package/lib/module/components/plot/index.js.map +1 -1
  33. package/lib/module/components/search-bar/index.js +11 -3
  34. package/lib/module/components/search-bar/index.js.map +1 -1
  35. package/lib/module/components/split-view/index.js +26 -27
  36. package/lib/module/components/split-view/index.js.map +1 -1
  37. package/lib/module/components/split-view/split-view-lifecycle.js +32 -0
  38. package/lib/module/components/split-view/split-view-lifecycle.js.map +1 -0
  39. package/lib/module/components/splitter/index.js +7 -13
  40. package/lib/module/components/splitter/index.js.map +1 -1
  41. package/lib/module/components/splitter/splitter-math.js +14 -0
  42. package/lib/module/components/splitter/splitter-math.js.map +1 -1
  43. package/lib/module/components/tabs/index.js +1 -1
  44. package/lib/module/components/tabs/index.js.map +1 -1
  45. package/lib/module/components/timeline/index.js +156 -42
  46. package/lib/module/components/timeline/index.js.map +1 -1
  47. package/lib/module/components/timeline/timeline-geometry.js +11 -0
  48. package/lib/module/components/timeline/timeline-geometry.js.map +1 -1
  49. package/lib/module/components/toast/index.js +1 -1
  50. package/lib/module/components/toast/index.js.map +1 -1
  51. package/lib/module/components/tooltip/index.js +2 -2
  52. package/lib/module/components/tooltip/index.js.map +1 -1
  53. package/lib/module/components/tree/index.js +2 -2
  54. package/lib/module/components/tree/index.js.map +1 -1
  55. package/lib/module/components/typography/index.js +2 -1
  56. package/lib/module/components/typography/index.js.map +1 -1
  57. package/lib/module/components/waterfall-chart/index.js +22 -3
  58. package/lib/module/components/waterfall-chart/index.js.map +1 -1
  59. package/lib/module/hooks/use-disclosure.js +10 -8
  60. package/lib/module/hooks/use-disclosure.js.map +1 -1
  61. package/lib/module/icons/index.js +135 -0
  62. package/lib/module/icons/index.js.map +1 -1
  63. package/lib/module/index.js +5 -1
  64. package/lib/module/index.js.map +1 -1
  65. package/lib/module/primitives/controllable-state.js +61 -0
  66. package/lib/module/primitives/controllable-state.js.map +1 -0
  67. package/lib/module/primitives/finite-chart.js +15 -0
  68. package/lib/module/primitives/finite-chart.js.map +1 -0
  69. package/lib/module/primitives/glass.js +136 -0
  70. package/lib/module/primitives/glass.js.map +1 -0
  71. package/lib/module/primitives/scrim.js +8 -1
  72. package/lib/module/primitives/scrim.js.map +1 -1
  73. package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts +59 -0
  74. package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts.map +1 -0
  75. package/lib/typescript/src/components/ai-input/index.d.ts +423 -0
  76. package/lib/typescript/src/components/ai-input/index.d.ts.map +1 -0
  77. package/lib/typescript/src/components/avatar/avatar-source.d.ts +4 -0
  78. package/lib/typescript/src/components/avatar/avatar-source.d.ts.map +1 -0
  79. package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
  80. package/lib/typescript/src/components/bottom-sheet/index.d.ts +10 -1
  81. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  82. package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
  83. package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
  84. package/lib/typescript/src/components/marquee/index.d.ts +15 -6
  85. package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
  86. package/lib/typescript/src/components/marquee/marquee-math.d.ts +6 -0
  87. package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -1
  88. package/lib/typescript/src/components/plan/index.d.ts.map +1 -1
  89. package/lib/typescript/src/components/plot/index.d.ts +2 -1
  90. package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
  91. package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -1
  92. package/lib/typescript/src/components/split-view/index.d.ts.map +1 -1
  93. package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts +14 -0
  94. package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts.map +1 -0
  95. package/lib/typescript/src/components/splitter/index.d.ts +2 -2
  96. package/lib/typescript/src/components/splitter/index.d.ts.map +1 -1
  97. package/lib/typescript/src/components/splitter/splitter-math.d.ts +2 -0
  98. package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -1
  99. package/lib/typescript/src/components/timeline/index.d.ts +24 -2
  100. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  101. package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +2 -0
  102. package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -1
  103. package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
  104. package/lib/typescript/src/components/waterfall-chart/index.d.ts +2 -1
  105. package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -1
  106. package/lib/typescript/src/hooks/use-disclosure.d.ts.map +1 -1
  107. package/lib/typescript/src/icons/index.d.ts +18 -0
  108. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  109. package/lib/typescript/src/index.d.ts +3 -1
  110. package/lib/typescript/src/index.d.ts.map +1 -1
  111. package/lib/typescript/src/primitives/controllable-state.d.ts +33 -0
  112. package/lib/typescript/src/primitives/controllable-state.d.ts.map +1 -0
  113. package/lib/typescript/src/primitives/finite-chart.d.ts +5 -0
  114. package/lib/typescript/src/primitives/finite-chart.d.ts.map +1 -0
  115. package/lib/typescript/src/primitives/glass.d.ts +43 -0
  116. package/lib/typescript/src/primitives/glass.d.ts.map +1 -0
  117. package/lib/typescript/src/primitives/scrim.d.ts +7 -0
  118. package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
  119. package/package.json +7 -2
  120. package/src/components/ai-input/ai-input-growth.ts +65 -0
  121. package/src/components/ai-input/index.tsx +1937 -0
  122. package/src/components/alert/index.tsx +1 -1
  123. package/src/components/avatar/avatar-source.ts +18 -0
  124. package/src/components/avatar/index.tsx +35 -27
  125. package/src/components/bottom-sheet/index.tsx +15 -3
  126. package/src/components/button-group/index.tsx +1 -1
  127. package/src/components/carousel/index.tsx +10 -11
  128. package/src/components/collapsible/index.tsx +1 -1
  129. package/src/components/drawer/index.tsx +24 -20
  130. package/src/components/field/index.tsx +4 -4
  131. package/src/components/label/index.tsx +1 -1
  132. package/src/components/marquee/index.tsx +56 -12
  133. package/src/components/marquee/marquee-math.ts +26 -3
  134. package/src/components/plan/index.tsx +6 -2
  135. package/src/components/plot/index.tsx +37 -11
  136. package/src/components/search-bar/index.tsx +9 -3
  137. package/src/components/split-view/index.tsx +24 -33
  138. package/src/components/split-view/split-view-lifecycle.ts +43 -0
  139. package/src/components/splitter/index.tsx +7 -11
  140. package/src/components/splitter/splitter-math.ts +28 -0
  141. package/src/components/tabs/index.tsx +1 -1
  142. package/src/components/timeline/index.tsx +189 -34
  143. package/src/components/timeline/timeline-geometry.ts +11 -0
  144. package/src/components/toast/index.tsx +6 -1
  145. package/src/components/tooltip/index.tsx +1 -1
  146. package/src/components/tree/index.tsx +2 -2
  147. package/src/components/typography/index.tsx +2 -2
  148. package/src/components/waterfall-chart/index.tsx +37 -4
  149. package/src/hooks/use-disclosure.ts +7 -12
  150. package/src/icons/index.tsx +106 -0
  151. package/src/index.ts +27 -0
  152. package/src/primitives/controllable-state.ts +112 -0
  153. package/src/primitives/finite-chart.ts +14 -0
  154. package/src/primitives/glass.tsx +171 -0
  155. package/src/primitives/scrim.tsx +7 -1
@@ -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 (typeof lowPin === 'number' && typeof highPin === 'number') {
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 (typeof value !== 'number' || Number.isNaN(value)) continue;
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 = typeof lowPin === 'number' ? lowPin : 0;
436
- const high = typeof highPin === 'number' ? highPin : low + 1;
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
- typeof lowPin === 'number' ? lowPin : derivedLow,
481
- typeof highPin === 'number' ? highPin : derivedHigh,
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
- <View onLayout={onLayout} style={{ aspectRatio }} className="w-full">
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}>
@@ -294,28 +294,31 @@ export const SearchBar = forwardRef<TextInput, SearchBarProps>(
294
294
 
295
295
  const handleSubmit = useCallback(
296
296
  (event: NativeSyntheticEvent<TextInputSubmitEditingEventData>) => {
297
+ if (disabled) return;
297
298
  // A return key is somebody saying they are done waiting, so the
298
299
  // pending pause is spent rather than waited out.
299
300
  flushSearchBarDebounce(debounceTimerRef, debouncedRef.current, text);
300
301
  onSubmit?.(text);
301
302
  onSubmitEditing?.(event);
302
303
  },
303
- [onSubmit, onSubmitEditing, text]
304
+ [disabled, onSubmit, onSubmitEditing, text]
304
305
  );
305
306
 
306
307
  const handleClear = useCallback(() => {
308
+ if (disabled) return;
307
309
  setText('');
308
310
  onClear?.();
309
311
  // The keyboard stays: clearing a query is the start of the next one.
310
312
  // Android takes focus away with the press, so it is asked back.
311
313
  inputRef.current?.focus();
312
- }, [onClear, setText]);
314
+ }, [disabled, onClear, setText]);
313
315
 
314
316
  const handleCancel = useCallback(() => {
317
+ if (disabled) return;
315
318
  setText('');
316
319
  inputRef.current?.blur();
317
320
  onCancel?.();
318
- }, [onCancel, setText]);
321
+ }, [disabled, onCancel, setText]);
319
322
 
320
323
  /*
321
324
  * Cancel's width is measured once and driven from a shared value, so the
@@ -426,8 +429,11 @@ export const SearchBar = forwardRef<TextInput, SearchBarProps>(
426
429
  <AnimatedPressable
427
430
  onLayout={handleCancelLayout}
428
431
  onPress={handleCancel}
432
+ disabled={disabled}
433
+ focusable={!disabled}
429
434
  hitSlop={8}
430
435
  accessibilityRole="button"
436
+ accessibilityState={{ disabled: !!disabled }}
431
437
  className={slots.cancelButton()}
432
438
  >
433
439
  <Text className={slots.cancelLabel()}>{cancelLabel}</Text>
@@ -56,10 +56,10 @@ import {
56
56
  DEFAULT_MIN_HEIGHT,
57
57
  clamp,
58
58
  nearestSnapIndex,
59
- normalizeSnapIndex,
60
59
  resolveLength,
61
60
  resolveSnapPoints,
62
61
  } from './split-view-math';
62
+ import { useSplitViewIndexLifecycle } from './split-view-lifecycle';
63
63
 
64
64
  /** Settles the pane onto a snap point without overshooting past its limits. */
65
65
  const SPRING = {
@@ -189,14 +189,16 @@ function SplitViewRoot({
189
189
  [pointsKey]
190
190
  );
191
191
 
192
- const controlled = snapIndexProp !== undefined;
193
- const [internalIndex, setInternalIndex] = useState(() =>
194
- normalizeSnapIndex(defaultSnapIndex, 1)
195
- );
196
- const snapIndex = normalizeSnapIndex(
197
- controlled ? snapIndexProp : internalIndex,
198
- points.length || 1
199
- );
192
+ const {
193
+ index: snapIndex,
194
+ requestIndex,
195
+ requestToken,
196
+ } = useSplitViewIndexLifecycle({
197
+ snapIndex: snapIndexProp,
198
+ defaultSnapIndex,
199
+ count: points.length,
200
+ onSnapIndexChange,
201
+ });
200
202
 
201
203
  const onSnapRef = useRef(onSnap);
202
204
  onSnapRef.current = onSnap;
@@ -207,30 +209,15 @@ function SplitViewRoot({
207
209
  }, []);
208
210
 
209
211
  /*
210
- * Moving the seam is one function whether a drag, a press or a screen reader
211
- * asked for it — the alternative is three places that each have to remember
212
- * to spring, to report, and to leave a controlled caller's index alone.
212
+ * Every modality makes the same request. The synchronization effect below
213
+ * moves only to the index the owner accepts, then reports that settlement.
213
214
  */
214
215
  const moveTo = useCallback(
215
- (index: number, options?: { animated?: boolean }) => {
216
- const target = points[index];
217
- if (target === undefined) return;
218
-
219
- if (!controlled) setInternalIndex(index);
220
- onSnapIndexChange?.(index);
221
-
222
- const finish = () => report(index, target);
223
- if (options?.animated === false || !animate) {
224
- topHeight.value = target;
225
- finish();
226
- return;
227
- }
228
- topHeight.value = withSpring(target, SPRING, (finished) => {
229
- 'worklet';
230
- if (finished) runOnJS(finish)();
231
- });
216
+ (index: number) => {
217
+ if (points[index] === undefined) return;
218
+ requestIndex(index);
232
219
  },
233
- [animate, controlled, onSnapIndexChange, points, report, topHeight]
220
+ [points, requestIndex]
234
221
  );
235
222
 
236
223
  const snapTo = useCallback((index: number) => moveTo(index), [moveTo]);
@@ -260,11 +247,15 @@ function SplitViewRoot({
260
247
  report(snapIndex, target);
261
248
  return;
262
249
  }
263
- topHeight.value = withSpring(target, SPRING);
250
+ const finish = () => report(snapIndex, target);
251
+ topHeight.value = withSpring(target, SPRING, (finished) => {
252
+ 'worklet';
253
+ if (finished) runOnJS(finish)();
254
+ });
264
255
  // Only when the resolved layout moves — a re-render that changes nothing
265
256
  // must not restart a spring the reader is watching.
266
257
  // eslint-disable-next-line react-hooks/exhaustive-deps
267
- }, [points, snapIndex, animate, animateOnMount]);
258
+ }, [points, snapIndex, animate, animateOnMount, requestToken]);
268
259
 
269
260
  const handleLayout = useCallback(
270
261
  (event: LayoutChangeEvent) => {
@@ -471,6 +462,7 @@ function SplitViewDragArea({
471
462
  return (
472
463
  <GestureDetector gesture={pan}>
473
464
  <View
465
+ {...props}
474
466
  accessible
475
467
  accessibilityRole="adjustable"
476
468
  accessibilityLabel={accessibilityLabel}
@@ -493,7 +485,6 @@ function SplitViewDragArea({
493
485
  }
494
486
  className={dragArea({ className })}
495
487
  onLayout={handleLayout}
496
- {...props}
497
488
  >
498
489
  {children}
499
490
  </View>
@@ -0,0 +1,43 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ function normalizeIndex(index: number, count: number): number {
4
+ if (!Number.isFinite(index) || count <= 0) return 0;
5
+ return Math.max(0, Math.min(Math.round(index), count - 1));
6
+ }
7
+
8
+ interface SplitViewIndexLifecycleOptions {
9
+ snapIndex: number | undefined;
10
+ defaultSnapIndex: number;
11
+ count: number;
12
+ onSnapIndexChange?: (index: number) => void;
13
+ }
14
+
15
+ /** Keeps requested indices intact until the measured snap-point list exists. */
16
+ export function useSplitViewIndexLifecycle({
17
+ snapIndex,
18
+ defaultSnapIndex,
19
+ count,
20
+ onSnapIndexChange,
21
+ }: SplitViewIndexLifecycleOptions) {
22
+ const controlled = snapIndex !== undefined;
23
+ const [internalIndex, setInternalIndex] = useState(defaultSnapIndex);
24
+ // Every request must settle, including one that resolves to the current index
25
+ // after a drag and a controlled request its owner rejects.
26
+ const [requestToken, setRequestToken] = useState(0);
27
+
28
+ const index = normalizeIndex(
29
+ controlled ? snapIndex : internalIndex,
30
+ count || 1
31
+ );
32
+
33
+ const requestIndex = useCallback(
34
+ (next: number) => {
35
+ if (!controlled) setInternalIndex(next);
36
+ setRequestToken((token) => token + 1);
37
+ onSnapIndexChange?.(next);
38
+ },
39
+ [controlled, onSnapIndexChange]
40
+ );
41
+
42
+ return { index, requestIndex, requestToken };
43
+ }
@@ -58,6 +58,7 @@ import {
58
58
  isCollapsed,
59
59
  layoutOffset,
60
60
  normalizeConstraint,
61
+ resetLayout,
61
62
  resizeLayout,
62
63
  resolveLayout,
63
64
  type SplitterConstraint,
@@ -388,6 +389,7 @@ function SplitterHandle({
388
389
  disabled,
389
390
  withGrip = true,
390
391
  accessibilityLabel = 'Resize panels',
392
+ style,
391
393
  ...props
392
394
  }: SplitterHandleProps) {
393
395
  const context = useSplitter('Splitter.Handle');
@@ -468,17 +470,10 @@ function SplitterHandle({
468
470
  * they already have between them.
469
471
  */
470
472
  const reset = useCallback(() => {
471
- const pair = boundary >= 0 ? boundary : 0;
472
- const next = layout.value.slice();
473
- const room = (next[pair] ?? 0) + (next[pair + 1] ?? 0);
474
- const first = initial[pair] ?? 0;
475
- const span = first + (initial[pair + 1] ?? 0);
476
- if (span <= 0) return;
477
- next[pair] = (first / span) * room;
478
- next[pair + 1] = room - next[pair]!;
473
+ const next = resetLayout(layout.value, initial, boundary, constraints);
479
474
  layout.value = next;
480
475
  commit(next);
481
- }, [boundary, commit, initial, layout]);
476
+ }, [boundary, commit, constraints, initial, layout]);
482
477
 
483
478
  const doubleTap = useMemo(
484
479
  () =>
@@ -548,6 +543,7 @@ function SplitterHandle({
548
543
  return (
549
544
  <GestureDetector gesture={gesture}>
550
545
  <Animated.View
546
+ {...props}
551
547
  accessible
552
548
  accessibilityRole="adjustable"
553
549
  accessibilityLabel={accessibilityLabel}
@@ -556,8 +552,7 @@ function SplitterHandle({
556
552
  accessibilityActions={frozen ? undefined : [{ name: 'increment' }, { name: 'decrement' }]}
557
553
  onAccessibilityAction={frozen ? undefined : onAccessibilityAction}
558
554
  className={handle({ className })}
559
- style={animatedStyle}
560
- {...props}
555
+ style={[style, animatedStyle]}
561
556
  >
562
557
  <View className={line()} />
563
558
  {withGrip ? <View className={grip()} /> : null}
@@ -582,6 +577,7 @@ export {
582
577
  layoutOffset,
583
578
  normalizeConstraint,
584
579
  panelFloor,
580
+ resetLayout,
585
581
  resizeLayout,
586
582
  resolveLayout,
587
583
  type SplitterConstraint,
@@ -227,6 +227,34 @@ export function resizeLayout(
227
227
  return next;
228
228
  }
229
229
 
230
+ /** Restores one pair's initial ratio without escaping its current constraints. */
231
+ export function resetLayout(
232
+ layout: number[],
233
+ initial: number[],
234
+ boundary: number,
235
+ constraints: SplitterConstraint[]
236
+ ): number[] {
237
+ const next = layout.slice();
238
+ if (boundary < 0 || boundary + 1 >= next.length) return next;
239
+
240
+ const pair = next[boundary]! + next[boundary + 1]!;
241
+ const first = initial[boundary];
242
+ const second = initial[boundary + 1];
243
+ if (
244
+ !Number.isFinite(pair) ||
245
+ typeof first !== 'number' ||
246
+ !Number.isFinite(first) ||
247
+ typeof second !== 'number' ||
248
+ !Number.isFinite(second) ||
249
+ first + second <= 0
250
+ ) {
251
+ return next;
252
+ }
253
+
254
+ const target = (first / (first + second)) * pair;
255
+ return resizeLayout(next, boundary, target - next[boundary]!, constraints);
256
+ }
257
+
230
258
  /** Where a seam sits, in percent, measured from the start of the splitter. */
231
259
  export function layoutOffset(layout: number[], boundary: number): number {
232
260
  'worklet';
@@ -778,7 +778,7 @@ function TabsList({ className, scrollable = false, children, ...props }: TabsLis
778
778
  }, [scrollable, activeLayout?.x, activeLayout]);
779
779
 
780
780
  const row = (
781
- <View accessibilityRole="tablist" className={cn(list(), className)} {...props}>
781
+ <View {...props} accessibilityRole="tablist" className={cn(list(), className)}>
782
782
  {/* Nothing to slide: in `expanding` every tab draws its own pill, and the
783
783
  open one is the shape that moves. */}
784
784
  {variant === 'expanding' ? null : <TabsIndicator />}