panelui-native 0.63.0 → 0.66.1

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 (226) hide show
  1. package/README.md +5 -0
  2. package/lib/module/components/badge/index.js +1 -1
  3. package/lib/module/components/badge/index.js.map +1 -1
  4. package/lib/module/components/bar-chart/index.js +39 -2
  5. package/lib/module/components/bar-chart/index.js.map +1 -1
  6. package/lib/module/components/bottom-sheet/index.js +2 -2
  7. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  8. package/lib/module/components/button/index.js +19 -9
  9. package/lib/module/components/button/index.js.map +1 -1
  10. package/lib/module/components/carousel/carousel-lifecycle.js +51 -0
  11. package/lib/module/components/carousel/carousel-lifecycle.js.map +1 -0
  12. package/lib/module/components/carousel/index.js +86 -35
  13. package/lib/module/components/carousel/index.js.map +1 -1
  14. package/lib/module/components/checkbox/index.js +2 -3
  15. package/lib/module/components/checkbox/index.js.map +1 -1
  16. package/lib/module/components/context-menu/context-menu-invocation.js +23 -0
  17. package/lib/module/components/context-menu/context-menu-invocation.js.map +1 -0
  18. package/lib/module/components/context-menu/index.js +78 -9
  19. package/lib/module/components/context-menu/index.js.map +1 -1
  20. package/lib/module/components/dialog/index.js +2 -2
  21. package/lib/module/components/dialog/index.js.map +1 -1
  22. package/lib/module/components/drawer/index.js +2 -2
  23. package/lib/module/components/drawer/index.js.map +1 -1
  24. package/lib/module/components/fab/index.js +4 -3
  25. package/lib/module/components/fab/index.js.map +1 -1
  26. package/lib/module/components/field/index.js +41 -6
  27. package/lib/module/components/field/index.js.map +1 -1
  28. package/lib/module/components/flow/flow-accessibility.js +100 -0
  29. package/lib/module/components/flow/flow-accessibility.js.map +1 -0
  30. package/lib/module/components/flow/flow-identifiers.js +81 -0
  31. package/lib/module/components/flow/flow-identifiers.js.map +1 -0
  32. package/lib/module/components/flow/index.js +112 -57
  33. package/lib/module/components/flow/index.js.map +1 -1
  34. package/lib/module/components/form/use-form.js +93 -66
  35. package/lib/module/components/form/use-form.js.map +1 -1
  36. package/lib/module/components/heatmap-chart/index.js +2 -2
  37. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  38. package/lib/module/components/hex-chart/index.js +2 -2
  39. package/lib/module/components/hex-chart/index.js.map +1 -1
  40. package/lib/module/components/kpi/index.js +3 -1
  41. package/lib/module/components/kpi/index.js.map +1 -1
  42. package/lib/module/components/line-chart/index.js +3 -3
  43. package/lib/module/components/line-chart/index.js.map +1 -1
  44. package/lib/module/components/map/index.js +71 -8
  45. package/lib/module/components/map/index.js.map +1 -1
  46. package/lib/module/components/map/map-accessibility.js +20 -0
  47. package/lib/module/components/map/map-accessibility.js.map +1 -0
  48. package/lib/module/components/map/maplibre.js.map +1 -1
  49. package/lib/module/components/menu/index.js +4 -1
  50. package/lib/module/components/menu/index.js.map +1 -1
  51. package/lib/module/components/message-scroller/index.js +17 -9
  52. package/lib/module/components/message-scroller/index.js.map +1 -1
  53. package/lib/module/components/message-scroller/message-scroller-math.js +20 -0
  54. package/lib/module/components/message-scroller/message-scroller-math.js.map +1 -0
  55. package/lib/module/components/number-input/index.js +1 -23
  56. package/lib/module/components/number-input/index.js.map +1 -1
  57. package/lib/module/components/number-input/number-input-math.js +44 -0
  58. package/lib/module/components/number-input/number-input-math.js.map +1 -0
  59. package/lib/module/components/plot/index.js +2 -2
  60. package/lib/module/components/plot/index.js.map +1 -1
  61. package/lib/module/components/popover/index.js.map +1 -1
  62. package/lib/module/components/radio-group/index.js +1 -2
  63. package/lib/module/components/radio-group/index.js.map +1 -1
  64. package/lib/module/components/selection-mode/index.js +26 -31
  65. package/lib/module/components/selection-mode/index.js.map +1 -1
  66. package/lib/module/components/selection-mode/selection-mode-contracts.js +48 -0
  67. package/lib/module/components/selection-mode/selection-mode-contracts.js.map +1 -0
  68. package/lib/module/components/signature/index.js +62 -20
  69. package/lib/module/components/signature/index.js.map +1 -1
  70. package/lib/module/components/signature/signature-accessibility.js +58 -0
  71. package/lib/module/components/signature/signature-accessibility.js.map +1 -0
  72. package/lib/module/components/slider/index.js +22 -18
  73. package/lib/module/components/slider/index.js.map +1 -1
  74. package/lib/module/components/sortable/index.js +5 -55
  75. package/lib/module/components/sortable/index.js.map +1 -1
  76. package/lib/module/components/sortable/reorder.js +63 -0
  77. package/lib/module/components/sortable/reorder.js.map +1 -0
  78. package/lib/module/components/swipe/index.js +4 -4
  79. package/lib/module/components/swipe/index.js.map +1 -1
  80. package/lib/module/components/switch/index.js +36 -3
  81. package/lib/module/components/switch/index.js.map +1 -1
  82. package/lib/module/components/time-picker/accessibility.js +21 -0
  83. package/lib/module/components/time-picker/accessibility.js.map +1 -0
  84. package/lib/module/components/time-picker/index.js +34 -8
  85. package/lib/module/components/time-picker/index.js.map +1 -1
  86. package/lib/module/components/waterfall-chart/index.js +1325 -0
  87. package/lib/module/components/waterfall-chart/index.js.map +1 -0
  88. package/lib/module/index.js +1 -0
  89. package/lib/module/index.js.map +1 -1
  90. package/lib/module/primitives/modal-isolation-store.js +29 -0
  91. package/lib/module/primitives/modal-isolation-store.js.map +1 -0
  92. package/lib/module/primitives/portal.js +36 -4
  93. package/lib/module/primitives/portal.js.map +1 -1
  94. package/lib/module/primitives/scrim.js +94 -3
  95. package/lib/module/primitives/scrim.js.map +1 -1
  96. package/lib/module/providers/panel-ui-provider.js +27 -6
  97. package/lib/module/providers/panel-ui-provider.js.map +1 -1
  98. package/lib/typescript/src/components/badge/index.d.ts.map +1 -1
  99. package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
  100. package/lib/typescript/src/components/bottom-sheet/index.d.ts +3 -0
  101. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  102. package/lib/typescript/src/components/button/index.d.ts.map +1 -1
  103. package/lib/typescript/src/components/carousel/carousel-lifecycle.d.ts +23 -0
  104. package/lib/typescript/src/components/carousel/carousel-lifecycle.d.ts.map +1 -0
  105. package/lib/typescript/src/components/carousel/index.d.ts +5 -2
  106. package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
  107. package/lib/typescript/src/components/checkbox/index.d.ts.map +1 -1
  108. package/lib/typescript/src/components/context-menu/context-menu-invocation.d.ts +20 -0
  109. package/lib/typescript/src/components/context-menu/context-menu-invocation.d.ts.map +1 -0
  110. package/lib/typescript/src/components/context-menu/index.d.ts +18 -8
  111. package/lib/typescript/src/components/context-menu/index.d.ts.map +1 -1
  112. package/lib/typescript/src/components/dialog/index.d.ts +3 -0
  113. package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
  114. package/lib/typescript/src/components/drawer/index.d.ts +3 -0
  115. package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
  116. package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
  117. package/lib/typescript/src/components/field/index.d.ts +8 -1
  118. package/lib/typescript/src/components/field/index.d.ts.map +1 -1
  119. package/lib/typescript/src/components/flow/flow-accessibility.d.ts +57 -0
  120. package/lib/typescript/src/components/flow/flow-accessibility.d.ts.map +1 -0
  121. package/lib/typescript/src/components/flow/flow-identifiers.d.ts +24 -0
  122. package/lib/typescript/src/components/flow/flow-identifiers.d.ts.map +1 -0
  123. package/lib/typescript/src/components/flow/index.d.ts +20 -7
  124. package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
  125. package/lib/typescript/src/components/form/use-form.d.ts.map +1 -1
  126. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  127. package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
  128. package/lib/typescript/src/components/kpi/index.d.ts.map +1 -1
  129. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  130. package/lib/typescript/src/components/map/index.d.ts +13 -5
  131. package/lib/typescript/src/components/map/index.d.ts.map +1 -1
  132. package/lib/typescript/src/components/map/map-accessibility.d.ts +19 -0
  133. package/lib/typescript/src/components/map/map-accessibility.d.ts.map +1 -0
  134. package/lib/typescript/src/components/map/maplibre.d.ts +2 -0
  135. package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -1
  136. package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
  137. package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
  138. package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts +8 -0
  139. package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts.map +1 -0
  140. package/lib/typescript/src/components/number-input/index.d.ts.map +1 -1
  141. package/lib/typescript/src/components/number-input/number-input-math.d.ts +9 -0
  142. package/lib/typescript/src/components/number-input/number-input-math.d.ts.map +1 -0
  143. package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
  144. package/lib/typescript/src/components/popover/index.d.ts +3 -0
  145. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  146. package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
  147. package/lib/typescript/src/components/selection-mode/index.d.ts +6 -4
  148. package/lib/typescript/src/components/selection-mode/index.d.ts.map +1 -1
  149. package/lib/typescript/src/components/selection-mode/selection-mode-contracts.d.ts +18 -0
  150. package/lib/typescript/src/components/selection-mode/selection-mode-contracts.d.ts.map +1 -0
  151. package/lib/typescript/src/components/signature/index.d.ts +6 -0
  152. package/lib/typescript/src/components/signature/index.d.ts.map +1 -1
  153. package/lib/typescript/src/components/signature/signature-accessibility.d.ts +27 -0
  154. package/lib/typescript/src/components/signature/signature-accessibility.d.ts.map +1 -0
  155. package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
  156. package/lib/typescript/src/components/sortable/index.d.ts.map +1 -1
  157. package/lib/typescript/src/components/sortable/reorder.d.ts +14 -0
  158. package/lib/typescript/src/components/sortable/reorder.d.ts.map +1 -0
  159. package/lib/typescript/src/components/swipe/index.d.ts.map +1 -1
  160. package/lib/typescript/src/components/switch/index.d.ts +3 -3
  161. package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
  162. package/lib/typescript/src/components/time-picker/accessibility.d.ts +10 -0
  163. package/lib/typescript/src/components/time-picker/accessibility.d.ts.map +1 -0
  164. package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
  165. package/lib/typescript/src/components/waterfall-chart/index.d.ts +379 -0
  166. package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -0
  167. package/lib/typescript/src/index.d.ts +3 -2
  168. package/lib/typescript/src/index.d.ts.map +1 -1
  169. package/lib/typescript/src/primitives/modal-isolation-store.d.ts +10 -0
  170. package/lib/typescript/src/primitives/modal-isolation-store.d.ts.map +1 -0
  171. package/lib/typescript/src/primitives/portal.d.ts +6 -0
  172. package/lib/typescript/src/primitives/portal.d.ts.map +1 -1
  173. package/lib/typescript/src/primitives/scrim.d.ts +5 -3
  174. package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
  175. package/lib/typescript/src/providers/panel-ui-provider.d.ts.map +1 -1
  176. package/package.json +11 -5
  177. package/src/components/badge/index.tsx +4 -1
  178. package/src/components/bar-chart/index.tsx +40 -3
  179. package/src/components/bottom-sheet/index.tsx +6 -3
  180. package/src/components/button/index.tsx +15 -21
  181. package/src/components/carousel/carousel-lifecycle.ts +74 -0
  182. package/src/components/carousel/index.tsx +118 -41
  183. package/src/components/checkbox/index.tsx +1 -2
  184. package/src/components/context-menu/context-menu-invocation.ts +44 -0
  185. package/src/components/context-menu/index.tsx +113 -13
  186. package/src/components/dialog/index.tsx +6 -3
  187. package/src/components/drawer/index.tsx +6 -3
  188. package/src/components/fab/index.tsx +8 -12
  189. package/src/components/field/index.tsx +52 -10
  190. package/src/components/flow/flow-accessibility.ts +123 -0
  191. package/src/components/flow/flow-identifiers.ts +98 -0
  192. package/src/components/flow/index.tsx +191 -69
  193. package/src/components/form/use-form.ts +101 -65
  194. package/src/components/heatmap-chart/index.tsx +2 -3
  195. package/src/components/hex-chart/index.tsx +2 -3
  196. package/src/components/kpi/index.tsx +3 -1
  197. package/src/components/line-chart/index.tsx +3 -2
  198. package/src/components/map/index.tsx +110 -7
  199. package/src/components/map/map-accessibility.ts +41 -0
  200. package/src/components/map/maplibre.ts +2 -0
  201. package/src/components/menu/index.tsx +4 -1
  202. package/src/components/message-scroller/index.tsx +39 -7
  203. package/src/components/message-scroller/message-scroller-math.ts +24 -0
  204. package/src/components/number-input/index.tsx +1 -26
  205. package/src/components/number-input/number-input-math.ts +50 -0
  206. package/src/components/plot/index.tsx +2 -1
  207. package/src/components/popover/index.tsx +3 -0
  208. package/src/components/radio-group/index.tsx +1 -2
  209. package/src/components/selection-mode/index.tsx +37 -30
  210. package/src/components/selection-mode/selection-mode-contracts.ts +53 -0
  211. package/src/components/signature/index.tsx +107 -20
  212. package/src/components/signature/signature-accessibility.ts +67 -0
  213. package/src/components/slider/index.tsx +28 -18
  214. package/src/components/sortable/index.tsx +12 -64
  215. package/src/components/sortable/reorder.ts +88 -0
  216. package/src/components/swipe/index.tsx +7 -4
  217. package/src/components/switch/index.tsx +42 -6
  218. package/src/components/time-picker/accessibility.ts +24 -0
  219. package/src/components/time-picker/index.tsx +48 -2
  220. package/src/components/waterfall-chart/index.tsx +1589 -0
  221. package/src/index.ts +25 -0
  222. package/src/primitives/modal-isolation-store.ts +31 -0
  223. package/src/primitives/portal.tsx +34 -4
  224. package/src/primitives/scrim.tsx +127 -7
  225. package/src/providers/panel-ui-provider.tsx +41 -9
  226. package/theme.css +30 -3
@@ -48,6 +48,7 @@ import {
48
48
  useCallback,
49
49
  useContext,
50
50
  useEffect,
51
+ useId,
51
52
  useMemo,
52
53
  useRef,
53
54
  useState,
@@ -97,6 +98,22 @@ import {
97
98
  type FlowRect,
98
99
  type FlowSide,
99
100
  } from './flow-paths';
101
+ import {
102
+ clampNodePosition,
103
+ FLOW_DELETE_ACTION,
104
+ FLOW_MOVE_ACTIONS,
105
+ getFlowConnectionActions,
106
+ moveNodePosition,
107
+ type FlowAccessibilityHandle,
108
+ type FlowAccessibilityNode,
109
+ } from './flow-accessibility';
110
+ import {
111
+ encodeFlowEdgeKey,
112
+ encodeFlowHandleKey,
113
+ resolveFlowEndpoint,
114
+ type FlowEndpoint,
115
+ type FlowEndpointReference,
116
+ } from './flow-identifiers';
100
117
 
101
118
  const AnimatedPath = Animated.createAnimatedComponent(Path);
102
119
  const AnimatedRect = Animated.createAnimatedComponent(Rect);
@@ -167,13 +184,9 @@ export interface FlowConnection {
167
184
  targetHandle?: string;
168
185
  }
169
186
 
170
- interface HandleEntry {
171
- key: string;
172
- node: string;
173
- id: string;
187
+ interface HandleEntry extends FlowAccessibilityHandle {
174
188
  side: FlowSide;
175
189
  offset: number;
176
- type: 'source' | 'target' | 'both';
177
190
  }
178
191
 
179
192
  interface FlowContextValue {
@@ -215,7 +228,7 @@ interface FlowContextValue {
215
228
  maxZoom: number;
216
229
  locked: boolean;
217
230
  setLocked: (locked: boolean) => void;
218
- nodeIds: string[];
231
+ nodes: FlowAccessibilityNode[];
219
232
  /**
220
233
  * Which of `nodeIds` are containers rather than nodes. A group writes its box
221
234
  * into the same registry — that is what lets an edge name one and the minimap
@@ -224,7 +237,7 @@ interface FlowContextValue {
224
237
  groupIds: string[];
225
238
  handles: HandleEntry[];
226
239
  edges: { key: string; props: FlowEdgeProps }[];
227
- registerNode: (id: string) => void;
240
+ registerNode: (id: string, label?: string) => void;
228
241
  unregisterNode: (id: string) => void;
229
242
  registerGroup: (id: string) => void;
230
243
  unregisterGroup: (id: string) => void;
@@ -232,8 +245,8 @@ interface FlowContextValue {
232
245
  unregisterHandle: (key: string) => void;
233
246
  registerEdge: (key: string, props: FlowEdgeProps) => void;
234
247
  unregisterEdge: (key: string) => void;
235
- onConnect?: (connection: FlowConnection) => void;
236
- isValidConnection?: (connection: FlowConnection) => boolean;
248
+ connectNodes: (connection: FlowConnection) => void;
249
+ canConnect: boolean;
237
250
  onNodeDragEnd?: (id: string, position: FlowNodePosition) => void;
238
251
  fitView: () => void;
239
252
  zoomBy: (factor: number) => void;
@@ -332,7 +345,7 @@ function FlowRoot({
332
345
  const size = useSharedValue({ width: 0, height: 0 });
333
346
 
334
347
  const [locked, setLocked] = useState(false);
335
- const [nodeIds, setNodeIds] = useState<string[]>([]);
348
+ const [nodes, setNodes] = useState<FlowAccessibilityNode[]>([]);
336
349
  const [groupIds, setGroupIds] = useState<string[]>([]);
337
350
  const [handles, setHandles] = useState<HandleEntry[]>([]);
338
351
  const [edges, setEdges] = useState<{ key: string; props: FlowEdgeProps }[]>([]);
@@ -441,12 +454,12 @@ function FlowRoot({
441
454
  viewportChangeRef.current?.({ x, y, zoom: z });
442
455
  }, []);
443
456
 
444
- const registerNode = useCallback((id: string) => {
445
- setNodeIds((current) => (current.includes(id) ? current : [...current, id]));
457
+ const registerNode = useCallback((id: string, label = id) => {
458
+ setNodes((current) => [...current.filter((entry) => entry.id !== id), { id, label }]);
446
459
  }, []);
447
460
 
448
461
  const unregisterNode = useCallback((id: string) => {
449
- setNodeIds((current) => current.filter((entry) => entry !== id));
462
+ setNodes((current) => current.filter((entry) => entry.id !== id));
450
463
  }, []);
451
464
 
452
465
  const registerGroup = useCallback((id: string) => {
@@ -476,6 +489,15 @@ function FlowRoot({
476
489
  setEdges((current) => current.filter((edge) => edge.key !== key));
477
490
  }, []);
478
491
 
492
+ const connectRef = useRef(onConnect);
493
+ connectRef.current = onConnect;
494
+ const validConnectionRef = useRef(isValidConnection);
495
+ validConnectionRef.current = isValidConnection;
496
+ const connectNodes = useCallback((next: FlowConnection) => {
497
+ if (validConnectionRef.current && !validConnectionRef.current(next)) return;
498
+ connectRef.current?.(next);
499
+ }, []);
500
+
479
501
  const fitView = useCallback(() => {
480
502
  const { width, height } = size.value;
481
503
  const all = Object.values(rects.value);
@@ -549,10 +571,10 @@ function FlowRoot({
549
571
  // mount has nothing to fit to yet. One frame later they do.
550
572
  const fitOnMount = useRef(fitViewOnMount);
551
573
  useEffect(() => {
552
- if (!fitOnMount.current || nodeIds.length === 0) return;
574
+ if (!fitOnMount.current || nodes.length === 0) return;
553
575
  const timer = setTimeout(fitView, 32);
554
576
  return () => clearTimeout(timer);
555
- }, [fitView, nodeIds.length]);
577
+ }, [fitView, nodes.length]);
556
578
 
557
579
  const panStart = useSharedValue({ x: 0, y: 0 });
558
580
  const pinchStart = useSharedValue({ zoom: 1, x: 0, y: 0 });
@@ -647,7 +669,7 @@ function FlowRoot({
647
669
  maxZoom,
648
670
  locked,
649
671
  setLocked,
650
- nodeIds,
672
+ nodes,
651
673
  groupIds,
652
674
  handles,
653
675
  edges,
@@ -659,8 +681,8 @@ function FlowRoot({
659
681
  unregisterHandle,
660
682
  registerEdge,
661
683
  unregisterEdge,
662
- onConnect,
663
- isValidConnection,
684
+ connectNodes,
685
+ canConnect: Boolean(onConnect),
664
686
  onNodeDragEnd,
665
687
  fitView,
666
688
  zoomBy,
@@ -678,11 +700,11 @@ function FlowRoot({
678
700
  handles,
679
701
  registerEdge,
680
702
  unregisterEdge,
681
- isValidConnection,
703
+ connectNodes,
682
704
  locked,
683
705
  maxZoom,
684
706
  minZoom,
685
- nodeIds,
707
+ nodes,
686
708
  groupIds,
687
709
  onConnect,
688
710
  onNodeDragEnd,
@@ -856,7 +878,7 @@ function FlowBackground({
856
878
  const token = useCSSVariable('--color-muted-foreground');
857
879
  const tint = color ?? (typeof token === 'string' ? token : '#737373');
858
880
  // A pattern is referenced by id, and two canvases on one screen would collide.
859
- const id = useRef(`panelui-flow-${Math.random().toString(36).slice(2, 7)}`).current;
881
+ const id = `panelui-flow-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
860
882
 
861
883
  /*
862
884
  * The grid is a tile that follows the viewport in whole-cell steps.
@@ -972,6 +994,13 @@ export interface FlowNodeProps extends Omit<ViewProps, 'children'> {
972
994
  selected?: boolean;
973
995
  /** Tapping the node — separate from dragging it. */
974
996
  onPress?: () => void;
997
+ /**
998
+ * Delete the node when assistive technology requests the advertised Delete
999
+ * node action. No delete action is exposed when this is omitted.
1000
+ */
1001
+ onDelete?: () => void;
1002
+ /** Graph points covered by each Move up, right, down or left accessibility action. */
1003
+ accessibilityMoveStep?: number;
975
1004
  /** Spoken name. Defaults to the node's id. */
976
1005
  accessibilityLabel?: string;
977
1006
  children?: ReactNode;
@@ -991,7 +1020,11 @@ function FlowNode({
991
1020
  pinned = false,
992
1021
  selected = false,
993
1022
  onPress,
1023
+ onDelete,
1024
+ accessibilityMoveStep = 24,
994
1025
  accessibilityLabel,
1026
+ accessibilityActions,
1027
+ onAccessibilityAction,
995
1028
  children,
996
1029
  ...props
997
1030
  }: FlowNodeProps) {
@@ -1009,6 +1042,10 @@ function FlowNode({
1009
1042
  registerGroup,
1010
1043
  unregisterGroup,
1011
1044
  onNodeDragEnd,
1045
+ nodes,
1046
+ handles,
1047
+ connectNodes,
1048
+ canConnect,
1012
1049
  } = flow;
1013
1050
 
1014
1051
  /*
@@ -1018,13 +1055,13 @@ function FlowNode({
1018
1055
  * layer both need to know it is a container and not a card.
1019
1056
  */
1020
1057
  useEffect(() => {
1021
- registerNode(id);
1058
+ registerNode(id, accessibilityLabel ?? id);
1022
1059
  registerGroup(id);
1023
1060
  return () => {
1024
1061
  unregisterNode(id);
1025
1062
  unregisterGroup(id);
1026
1063
  };
1027
- }, [id, registerGroup, registerNode, unregisterGroup, unregisterNode]);
1064
+ }, [accessibilityLabel, id, registerGroup, registerNode, unregisterGroup, unregisterNode]);
1028
1065
 
1029
1066
  // Writing on every render would fight the drag, snapping a node back to the
1030
1067
  // position it was first given. Only an actual change to the prop moves it.
@@ -1065,11 +1102,70 @@ function FlowNode({
1065
1102
  );
1066
1103
 
1067
1104
  const start = useSharedValue({ x: 0, y: 0 });
1068
-
1069
1105
  const pressRef = useRef(onPress);
1070
1106
  pressRef.current = onPress;
1071
1107
  const press = useCallback(() => pressRef.current?.(), []);
1072
1108
 
1109
+ const moveNode = useCallback(
1110
+ (actionName: string) => {
1111
+ const rect = rects.value[id];
1112
+ if (!rect) return;
1113
+ const bounds = confine && group ? rects.value[group] : undefined;
1114
+ const next = moveNodePosition(rect, bounds, actionName, accessibilityMoveStep);
1115
+ if (!next) return;
1116
+ setNodeRect(id, next);
1117
+ dragEnd(next.x, next.y);
1118
+ },
1119
+ [accessibilityMoveStep, confine, dragEnd, group, id, rects, setNodeRect]
1120
+ );
1121
+
1122
+ const connectionActions = useMemo(
1123
+ () => (canConnect && !locked ? getFlowConnectionActions(id, nodes, handles) : []),
1124
+ [canConnect, handles, id, locked, nodes]
1125
+ );
1126
+ const connectionByAction = useMemo(
1127
+ () => new Map(connectionActions.map((entry) => [entry.name, entry.connection])),
1128
+ [connectionActions]
1129
+ );
1130
+ const movable = draggable && !pinned && !locked;
1131
+ const nodeActions = useMemo(
1132
+ () => [
1133
+ ...(onPress ? [{ name: 'activate' }] : []),
1134
+ ...(movable ? FLOW_MOVE_ACTIONS : []),
1135
+ ...connectionActions.map(({ name, label }) => ({ name, label })),
1136
+ ...(onDelete ? [FLOW_DELETE_ACTION] : []),
1137
+ ...(accessibilityActions ?? []),
1138
+ ],
1139
+ [accessibilityActions, connectionActions, movable, onDelete, onPress]
1140
+ );
1141
+
1142
+ const handleAccessibilityAction = useCallback<
1143
+ NonNullable<ViewProps['onAccessibilityAction']>
1144
+ >(
1145
+ (event) => {
1146
+ const action = event.nativeEvent.actionName;
1147
+ if (action === 'activate' && onPress) {
1148
+ press();
1149
+ return;
1150
+ }
1151
+ if (movable && FLOW_MOVE_ACTIONS.some((entry) => entry.name === action)) {
1152
+ moveNode(action);
1153
+ return;
1154
+ }
1155
+ const nextConnection = connectionByAction.get(action);
1156
+ if (nextConnection) {
1157
+ connectNodes(nextConnection);
1158
+ return;
1159
+ }
1160
+ if (action === FLOW_DELETE_ACTION.name && onDelete) {
1161
+ onDelete();
1162
+ return;
1163
+ }
1164
+ onAccessibilityAction?.(event);
1165
+ },
1166
+ [connectNodes, connectionByAction, movable, moveNode, onAccessibilityAction, onDelete, onPress, press]
1167
+ );
1168
+
1073
1169
  const drag = useMemo(
1074
1170
  () =>
1075
1171
  Gesture.Pan()
@@ -1096,13 +1192,9 @@ function FlowNode({
1096
1192
  * would leave the edges drawn to a position the node never reached.
1097
1193
  */
1098
1194
  if (confine && group) {
1099
- const box = rects.value[group];
1100
- if (box) {
1101
- const maxX = box.x + box.width - rect.width;
1102
- const maxY = box.y + box.height - rect.height;
1103
- x = x < box.x ? box.x : x > maxX ? maxX : x;
1104
- y = y < box.y ? box.y : y > maxY ? maxY : y;
1105
- }
1195
+ const next = clampNodePosition(rect, rects.value[group], { x, y });
1196
+ x = next.x;
1197
+ y = next.y;
1106
1198
  }
1107
1199
 
1108
1200
  rects.value = { ...rects.value, [id]: { ...rect, x, y } };
@@ -1158,6 +1250,8 @@ function FlowNode({
1158
1250
  accessibilityRole={onPress ? 'button' : 'none'}
1159
1251
  accessibilityLabel={accessibilityLabel ?? id}
1160
1252
  accessibilityState={{ selected }}
1253
+ accessibilityActions={nodeActions}
1254
+ onAccessibilityAction={handleAccessibilityAction}
1161
1255
  {...props}
1162
1256
  >
1163
1257
  {textChildren(children)}
@@ -1185,6 +1279,11 @@ export interface FlowHandleProps {
1185
1279
  /** Where along the face, 0–1. For more than one handle on a side. */
1186
1280
  offset?: number;
1187
1281
  className?: string;
1282
+ /**
1283
+ * Spoken handle name used in the parent node's connection actions. Defaults
1284
+ * to the handle's id.
1285
+ */
1286
+ accessibilityLabel?: string;
1188
1287
  /** Draw nothing. The handle still anchors edges and still accepts a drop. */
1189
1288
  hidden?: boolean;
1190
1289
  }
@@ -1203,6 +1302,7 @@ function FlowHandle({
1203
1302
  type = 'both',
1204
1303
  offset = 0.5,
1205
1304
  className,
1305
+ accessibilityLabel,
1206
1306
  hidden = false,
1207
1307
  }: FlowHandleProps) {
1208
1308
  const flow = useFlow('Flow.Handle');
@@ -1211,31 +1311,51 @@ function FlowHandle({
1211
1311
  throw new Error('Flow.Handle must be used within a <Flow.Node>');
1212
1312
  }
1213
1313
 
1214
- const { rects, zoom, connection, handles, locked, registerHandle, unregisterHandle } = flow;
1215
- const key = `${node}.${id}`;
1314
+ const {
1315
+ rects,
1316
+ zoom,
1317
+ connection,
1318
+ handles,
1319
+ locked,
1320
+ registerHandle,
1321
+ unregisterHandle,
1322
+ connectNodes,
1323
+ } = flow;
1324
+ const key = encodeFlowHandleKey(node, id);
1216
1325
 
1217
1326
  useEffect(() => {
1218
- registerHandle({ key, node, id, side: position, offset, type });
1327
+ registerHandle({
1328
+ key,
1329
+ node,
1330
+ id,
1331
+ label: accessibilityLabel ?? id,
1332
+ side: position,
1333
+ offset,
1334
+ type,
1335
+ });
1219
1336
  return () => unregisterHandle(key);
1220
- }, [id, key, node, offset, position, registerHandle, type, unregisterHandle]);
1221
-
1222
- const connectRef = useRef(flow.onConnect);
1223
- connectRef.current = flow.onConnect;
1224
- const validRef = useRef(flow.isValidConnection);
1225
- validRef.current = flow.isValidConnection;
1337
+ }, [
1338
+ accessibilityLabel,
1339
+ id,
1340
+ key,
1341
+ node,
1342
+ offset,
1343
+ position,
1344
+ registerHandle,
1345
+ type,
1346
+ unregisterHandle,
1347
+ ]);
1226
1348
 
1227
1349
  const land = useCallback(
1228
1350
  (target: string, targetHandle: string | undefined) => {
1229
- const payload: FlowConnection = {
1351
+ connectNodes({
1230
1352
  source: node,
1231
1353
  sourceHandle: id,
1232
1354
  target,
1233
1355
  targetHandle,
1234
- };
1235
- if (validRef.current && !validRef.current(payload)) return;
1236
- connectRef.current?.(payload);
1356
+ });
1237
1357
  },
1238
- [id, node]
1358
+ [connectNodes, id, node]
1239
1359
  );
1240
1360
 
1241
1361
  // A snapshot of what a drag can land on, rebuilt only when handles come and
@@ -1354,8 +1474,7 @@ function FlowHandle({
1354
1474
  <View
1355
1475
  collapsable={false}
1356
1476
  hitSlop={12}
1357
- accessibilityRole="button"
1358
- accessibilityLabel={`${position} connection point`}
1477
+ accessible={false}
1359
1478
  style={placement}
1360
1479
  className={flowVariants().handle({ className })}
1361
1480
  />
@@ -1369,10 +1488,10 @@ FlowHandle.displayName = 'Flow.Handle';
1369
1488
  /* -------------------------------------------------------------------------- */
1370
1489
 
1371
1490
  export interface FlowEdgeProps {
1372
- /** Source, as `"nodeId"` or `"nodeId.handleId"`. */
1373
- from: string;
1374
- /** Target, same shape. */
1375
- to: string;
1491
+ /** Source node or handle. Strings retain the `"nodeId.handleId"` shorthand. */
1492
+ from: FlowEndpointReference;
1493
+ /** Target node or handle, in the same shape. */
1494
+ to: FlowEndpointReference;
1376
1495
  /** How the edge is routed. */
1377
1496
  variant?: FlowEdgeVariant;
1378
1497
  /**
@@ -1418,7 +1537,7 @@ export interface FlowEdgeProps {
1418
1537
  */
1419
1538
  function FlowEdge(props: FlowEdgeProps) {
1420
1539
  const { registerEdge, unregisterEdge } = useFlow('Flow.Edge');
1421
- const key = `${props.from}->${props.to}`;
1540
+ const key = encodeFlowEdgeKey(props.from, props.to);
1422
1541
 
1423
1542
  // Re-registered whenever any prop changes, so `animated` toggling or a
1424
1543
  // variant switch reaches the layer that draws it.
@@ -1459,8 +1578,9 @@ function FlowEdgePath({
1459
1578
  tint: string;
1460
1579
  origin: { x: number; y: number };
1461
1580
  }) {
1462
- const source = resolveEnd(from, fromSide, handles);
1463
- const target = resolveEnd(to, toSide, handles);
1581
+ const nodes = Object.keys(boxes);
1582
+ const source = resolveEnd(from, fromSide, nodes, handles);
1583
+ const target = resolveEnd(to, toSide, nodes, handles);
1464
1584
 
1465
1585
  const fromRect = boxes[source.node];
1466
1586
  const toRect = boxes[target.node];
@@ -1519,22 +1639,24 @@ function FlowEdgePath({
1519
1639
  }
1520
1640
 
1521
1641
  /**
1522
- * Splits `"node"` or `"node.handle"` into the node and, when a handle was
1523
- * named, the face and offset that handle registered.
1642
+ * Resolves a node or handle without parsing either identifier. Legacy
1643
+ * `"node.handle"` references are matched against the structured registry.
1524
1644
  */
1525
1645
  function resolveEnd(
1526
- reference: string,
1646
+ reference: FlowEndpointReference,
1527
1647
  explicit: FlowSide | undefined,
1648
+ nodes: string[],
1528
1649
  handles: HandleEntry[]
1529
1650
  ): { node: string; side: FlowSide | undefined; offset: number } {
1530
- const dot = reference.indexOf('.');
1531
- if (dot === -1) {
1532
- return { node: reference, side: explicit, offset: 0.5 };
1533
- }
1534
- const node = reference.slice(0, dot);
1535
- const entry = handles.find((handle) => handle.key === reference);
1651
+ const endpoint = resolveFlowEndpoint(reference, nodes, handles);
1652
+ const entry =
1653
+ endpoint.handle === undefined
1654
+ ? undefined
1655
+ : handles.find(
1656
+ (handle) => handle.node === endpoint.node && handle.id === endpoint.handle
1657
+ );
1536
1658
  return {
1537
- node,
1659
+ node: endpoint.node,
1538
1660
  side: explicit ?? entry?.side,
1539
1661
  offset: entry?.offset ?? 0.5,
1540
1662
  };
@@ -1645,9 +1767,9 @@ function FlowGroup({
1645
1767
  } = flow;
1646
1768
 
1647
1769
  useEffect(() => {
1648
- registerNode(id);
1770
+ registerNode(id, label ?? id);
1649
1771
  return () => unregisterNode(id);
1650
- }, [id, registerNode, unregisterNode]);
1772
+ }, [id, label, registerNode, unregisterNode]);
1651
1773
 
1652
1774
  useEffect(() => {
1653
1775
  setNodeRect(id, {
@@ -1919,7 +2041,7 @@ function FlowMiniMap({
1919
2041
  height = 84,
1920
2042
  nodeColor,
1921
2043
  }: FlowMiniMapProps) {
1922
- const { rects, nodeIds, groupIds, translateX, translateY, zoom, size } =
2044
+ const { rects, nodes, groupIds, translateX, translateY, zoom, size } =
1923
2045
  useFlow('Flow.MiniMap');
1924
2046
  const token = useCSSVariable('--color-muted-foreground');
1925
2047
  const tint = nodeColor ?? (typeof token === 'string' ? token : '#737373');
@@ -1999,7 +2121,7 @@ function FlowMiniMap({
1999
2121
  {/* Containers first and drawn as outlines, so the map reads the way
2000
2122
  the canvas does — boxes with things in them, rather than a wash of
2001
2123
  identical rectangles where the largest happens to be a group. */}
2002
- {nodeIds.map((id) => {
2124
+ {nodes.map(({ id }) => {
2003
2125
  const group = groups.has(id);
2004
2126
  return (
2005
2127
  <MiniMapNode
@@ -2087,4 +2209,4 @@ export const Flow = Object.assign(FlowRoot, {
2087
2209
  MiniMap: FlowMiniMap,
2088
2210
  });
2089
2211
 
2090
- export type { FlowSide, FlowRect, FlowPoint };
2212
+ export type { FlowEndpoint, FlowEndpointReference, FlowSide, FlowRect, FlowPoint };