panelui-native 0.51.1 → 0.53.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 (45) hide show
  1. package/README.md +2 -1
  2. package/lib/module/components/candlestick-chart/index.js +1161 -0
  3. package/lib/module/components/candlestick-chart/index.js.map +1 -0
  4. package/lib/module/components/combobox/index.js +73 -7
  5. package/lib/module/components/combobox/index.js.map +1 -1
  6. package/lib/module/components/context-menu/index.js +529 -0
  7. package/lib/module/components/context-menu/index.js.map +1 -0
  8. package/lib/module/components/menu/index.js +18 -10
  9. package/lib/module/components/menu/index.js.map +1 -1
  10. package/lib/module/components/popover/index.js +71 -7
  11. package/lib/module/components/popover/index.js.map +1 -1
  12. package/lib/module/components/sortable/index.js +159 -23
  13. package/lib/module/components/sortable/index.js.map +1 -1
  14. package/lib/module/components/tabs/index.js +56 -15
  15. package/lib/module/components/tabs/index.js.map +1 -1
  16. package/lib/module/components/time-picker/index.js +295 -31
  17. package/lib/module/components/time-picker/index.js.map +1 -1
  18. package/lib/module/index.js +3 -1
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/typescript/src/components/candlestick-chart/index.d.ts +278 -0
  21. package/lib/typescript/src/components/candlestick-chart/index.d.ts.map +1 -0
  22. package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
  23. package/lib/typescript/src/components/context-menu/index.d.ts +270 -0
  24. package/lib/typescript/src/components/context-menu/index.d.ts.map +1 -0
  25. package/lib/typescript/src/components/menu/index.d.ts +20 -20
  26. package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
  27. package/lib/typescript/src/components/popover/index.d.ts +51 -1
  28. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  29. package/lib/typescript/src/components/sortable/index.d.ts +13 -3
  30. package/lib/typescript/src/components/sortable/index.d.ts.map +1 -1
  31. package/lib/typescript/src/components/tabs/index.d.ts +34 -2
  32. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  33. package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
  34. package/lib/typescript/src/index.d.ts +3 -1
  35. package/lib/typescript/src/index.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/components/candlestick-chart/index.tsx +1360 -0
  38. package/src/components/combobox/index.tsx +84 -6
  39. package/src/components/context-menu/index.tsx +658 -0
  40. package/src/components/menu/index.tsx +17 -10
  41. package/src/components/popover/index.tsx +94 -6
  42. package/src/components/sortable/index.tsx +208 -22
  43. package/src/components/tabs/index.tsx +82 -16
  44. package/src/components/time-picker/index.tsx +330 -35
  45. package/src/index.ts +27 -0
@@ -296,6 +296,13 @@ function eachOption(children: ReactNode, visit: (option: ComboboxItemProps) => v
296
296
  * A group is rebuilt around whatever survives inside it and dropped when that
297
297
  * is nothing — a heading over no options reads as a section that failed to load
298
298
  * rather than one the query emptied.
299
+ *
300
+ * Every kept node is given a key on the way out. This walk builds a plain array
301
+ * rather than going through `Children.map`, which is the one that hands out keys
302
+ * of its own, so options written as literal JSX — the ordinary way to write a
303
+ * short, fixed list — arrive here with none. An option's `value` is already
304
+ * unique within a list, and a group is named by its label, so both key
305
+ * themselves; an explicit key on the element still wins.
299
306
  */
300
307
  function filterOptions(
301
308
  children: ReactNode,
@@ -303,20 +310,31 @@ function filterOptions(
303
310
  ): ReactNode[] {
304
311
  const kept: ReactNode[] = [];
305
312
 
306
- Children.forEach(children, (child) => {
313
+ Children.forEach(children, (child, index) => {
307
314
  if (!isValidElement(child)) return;
308
315
 
309
316
  if (child.type === ComboboxGroup) {
310
317
  const props = child.props as ComboboxGroupProps;
311
318
  const inner = filterOptions(props.children, matches);
312
319
  if (inner.length) {
313
- kept.push(cloneElement(child as ReactElement<ComboboxGroupProps>, {}, inner));
320
+ kept.push(
321
+ cloneElement(
322
+ child as ReactElement<ComboboxGroupProps>,
323
+ { key: child.key ?? `group:${props.label ?? index}` },
324
+ inner
325
+ )
326
+ );
314
327
  }
315
328
  return;
316
329
  }
317
330
 
318
331
  if (child.type === ComboboxItem && matches(child.props as ComboboxItemProps)) {
319
- kept.push(child);
332
+ const props = child.props as ComboboxItemProps;
333
+ kept.push(
334
+ cloneElement(child as ReactElement<ComboboxItemProps>, {
335
+ key: child.key ?? `option:${props.value}`,
336
+ })
337
+ );
320
338
  }
321
339
  });
322
340
 
@@ -549,13 +567,66 @@ function ComboboxRoot<Mode extends ComboboxMode = 'single'>({
549
567
  * that lifts its content clear of the keyboard moves the field after it was
550
568
  * measured, and the list would stay at the old position. Re-measure whenever
551
569
  * the keyboard's height changes while the list is open.
570
+ *
571
+ * Except while the list is being dragged. The list dismisses the keyboard on
572
+ * drag, so the first finger movement changes the keyboard's height, which
573
+ * lands a new anchor, which recomputes the panel's `top` *and* its
574
+ * `maxHeight` — the panel resizes and moves under the finger that is
575
+ * scrolling it, which is the stutter. The measurement is only stale once the
576
+ * keyboard has finished leaving, so it is deferred to the end of the drag,
577
+ * which is also the first moment it can be taken correctly.
552
578
  */
553
- useEffect(() => {
554
- if (!open || presentation !== 'overlay') return;
579
+ const draggingList = useRef(false);
580
+ const anchorStale = useRef(false);
581
+
582
+ const remeasure = useCallback(() => {
555
583
  fieldRef.current?.measureInWindow((x, y, width, height) =>
556
584
  setAnchor({ x, y, width, height })
557
585
  );
558
- }, [open, presentation, keyboardHeight]);
586
+ }, []);
587
+
588
+ useEffect(() => {
589
+ if (!open || presentation !== 'overlay') return;
590
+ if (draggingList.current) {
591
+ anchorStale.current = true;
592
+ return;
593
+ }
594
+ remeasure();
595
+ }, [open, presentation, keyboardHeight, remeasure]);
596
+
597
+ const settleTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
598
+
599
+ const cancelSettle = useCallback(() => {
600
+ if (settleTimer.current === null) return;
601
+ clearTimeout(settleTimer.current);
602
+ settleTimer.current = null;
603
+ }, []);
604
+
605
+ const onListDragStart = useCallback(() => {
606
+ cancelSettle();
607
+ draggingList.current = true;
608
+ }, [cancelSettle]);
609
+
610
+ const onListSettled = useCallback(() => {
611
+ cancelSettle();
612
+ draggingList.current = false;
613
+ if (!anchorStale.current) return;
614
+ anchorStale.current = false;
615
+ remeasure();
616
+ }, [cancelSettle, remeasure]);
617
+
618
+ /*
619
+ * A fling ends the drag and then keeps moving, and momentum begins a frame
620
+ * *after* the drag ends — so the end of the drag cannot tell the two apart by
621
+ * itself. It arms a short timer instead, which momentum starting cancels; a
622
+ * drag that stops dead has no momentum to cancel it and settles on the timer.
623
+ */
624
+ const onListDragEnd = useCallback(() => {
625
+ cancelSettle();
626
+ settleTimer.current = setTimeout(onListSettled, 80);
627
+ }, [cancelSettle, onListSettled]);
628
+
629
+ useEffect(() => cancelSettle, [cancelSettle]);
559
630
 
560
631
  /*
561
632
  * A single-select field shows the chosen option's label when it is not being
@@ -751,6 +822,13 @@ function ComboboxRoot<Mode extends ComboboxMode = 'single'>({
751
822
  // option would be swallowed by the keyboard dismissing first.
752
823
  keyboardShouldPersistTaps="always"
753
824
  keyboardDismissMode="on-drag"
825
+ // Both ends are wired: a slow drag that stops where it started never
826
+ // gets a momentum event, and a fling reports the drag ending long before
827
+ // the list has stopped.
828
+ onScrollBeginDrag={onListDragStart}
829
+ onScrollEndDrag={onListDragEnd}
830
+ onMomentumScrollBegin={cancelSettle}
831
+ onMomentumScrollEnd={onListSettled}
754
832
  >
755
833
  <View className="gap-1">{body}</View>
756
834
  </ScrollView>