@barefootjs/xyflow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/dist/classes.d.ts +31 -0
- package/dist/classes.d.ts.map +1 -0
- package/dist/compat.d.ts +67 -0
- package/dist/compat.d.ts.map +1 -0
- package/dist/connection.d.ts +20 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/edge-path.d.ts +14 -0
- package/dist/edge-path.d.ts.map +1 -0
- package/dist/flow-subsystems.d.ts +28 -0
- package/dist/flow-subsystems.d.ts.map +1 -0
- package/dist/hooks.d.ts +34 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6572 -0
- package/dist/node-resizer.d.ts +52 -0
- package/dist/node-resizer.d.ts.map +1 -0
- package/dist/node-type-dispatch.d.ts +34 -0
- package/dist/node-type-dispatch.d.ts.map +1 -0
- package/dist/selection.d.ts +32 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/store.d.ts +8 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/xyflow.browser.min.js +95 -0
- package/dist/xyflow.browser.min.js.map +129 -0
- package/package.json +58 -0
- package/src/__tests__/clamp-drag-position.test.ts +111 -0
- package/src/__tests__/compat.test.ts +157 -0
- package/src/__tests__/host-element-store.test.ts +33 -0
- package/src/__tests__/jsx-smoke.test.ts +33 -0
- package/src/__tests__/jsx-smoke.tsx +23 -0
- package/src/__tests__/node-type-dispatch.test.ts +104 -0
- package/src/__tests__/store.test.ts +399 -0
- package/src/__tests__/tsconfig.json +23 -0
- package/src/classes.ts +41 -0
- package/src/compat.ts +237 -0
- package/src/connection.ts +459 -0
- package/src/constants.ts +8 -0
- package/src/context.ts +8 -0
- package/src/edge-path.ts +89 -0
- package/src/flow-subsystems.ts +506 -0
- package/src/hooks.ts +72 -0
- package/src/index.ts +134 -0
- package/src/node-resizer.ts +276 -0
- package/src/node-type-dispatch.ts +46 -0
- package/src/selection.ts +407 -0
- package/src/store.ts +526 -0
- package/src/types.ts +329 -0
- package/src/utils.ts +13 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Public API for `@barefootjs/xyflow`.
|
|
2
|
+
//
|
|
3
|
+
// JSX-native renderer components (`<Flow>` / `<Background>` /
|
|
4
|
+
// `<Controls>` / `<MiniMap>` / `<Handle>` / `<NodeWrapper>` /
|
|
5
|
+
// `<SimpleEdge>`) are distributed via the shadcn registry at
|
|
6
|
+
// `ui/components/ui/xyflow/` — install with `bf add xyflow`.
|
|
7
|
+
// This package ships the utility helpers, types, signal hooks, store,
|
|
8
|
+
// and the imperative pointer-paced subsystems those components attach
|
|
9
|
+
// via `ref` callbacks.
|
|
10
|
+
|
|
11
|
+
// Store / context / signal hooks
|
|
12
|
+
export { createFlowStore } from './store'
|
|
13
|
+
export { FlowContext } from './context'
|
|
14
|
+
export {
|
|
15
|
+
useFlow,
|
|
16
|
+
useViewport,
|
|
17
|
+
useNodes,
|
|
18
|
+
useEdges,
|
|
19
|
+
useNodesInitialized,
|
|
20
|
+
useStore,
|
|
21
|
+
screenToFlowPosition,
|
|
22
|
+
} from './hooks'
|
|
23
|
+
|
|
24
|
+
// Geometry helpers consumed by the JSX `<SimpleEdge>` component
|
|
25
|
+
export { computeEdgePosition, getEdgePath } from './edge-path'
|
|
26
|
+
export type { EdgePathTuple } from './edge-path'
|
|
27
|
+
|
|
28
|
+
// Pointer-paced subsystems attached via `<Flow>` / `<Handle>` `ref`
|
|
29
|
+
// callbacks. JSX gives these no leverage — pan/zoom is owned by
|
|
30
|
+
// `XYPanZoom` (D3-zoom-derived), the selection rectangle owns global
|
|
31
|
+
// pointer capture, connection drag uses `elementFromPoint`, and the
|
|
32
|
+
// node resizer needs raw dimension math.
|
|
33
|
+
export { attachFlowSubsystems, clampDragPositionToParent } from './flow-subsystems'
|
|
34
|
+
export { attachConnectionHandler, attachReconnectionHandler } from './connection'
|
|
35
|
+
export { dispatchNodeType } from './node-type-dispatch'
|
|
36
|
+
export type { NodeInitFn } from './node-type-dispatch'
|
|
37
|
+
export { initNodeResizer, ResizeControlVariant } from './node-resizer'
|
|
38
|
+
export type {
|
|
39
|
+
NodeResizerOptions,
|
|
40
|
+
ControlPosition,
|
|
41
|
+
ControlLinePosition,
|
|
42
|
+
OnResize,
|
|
43
|
+
OnResizeStart,
|
|
44
|
+
OnResizeEnd,
|
|
45
|
+
ShouldResize,
|
|
46
|
+
ResizeControlDirection,
|
|
47
|
+
} from './node-resizer'
|
|
48
|
+
export { setupKeyboardHandlers, setupNodeSelection, setupSelectionRectangle } from './selection'
|
|
49
|
+
export type { SelectionRectOptions } from './selection'
|
|
50
|
+
|
|
51
|
+
// Stable CSS class names for the registry-side JSX components.
|
|
52
|
+
// Imported (rather than declared as inline literals) so site/ui's
|
|
53
|
+
// cssLayerPrefixer leaves the `bf-flow*` names un-prefixed, matching
|
|
54
|
+
// the chart pattern (`CHART_CLASS_*` from `@barefootjs/chart`).
|
|
55
|
+
export {
|
|
56
|
+
BF_FLOW,
|
|
57
|
+
BF_FLOW_VIEWPORT,
|
|
58
|
+
BF_FLOW_EDGES,
|
|
59
|
+
BF_FLOW_NODES,
|
|
60
|
+
BF_FLOW_NODE,
|
|
61
|
+
BF_FLOW_NODE_GROUP,
|
|
62
|
+
BF_FLOW_NODE_CHILD,
|
|
63
|
+
BF_FLOW_NODE_SELECTED,
|
|
64
|
+
BF_FLOW_NODE_CUSTOM,
|
|
65
|
+
BF_FLOW_EDGE,
|
|
66
|
+
BF_FLOW_EDGE_SELECTED,
|
|
67
|
+
BF_FLOW_EDGE_ANIMATED,
|
|
68
|
+
BF_FLOW_HANDLE,
|
|
69
|
+
BF_FLOW_HANDLE_TARGET,
|
|
70
|
+
BF_FLOW_HANDLE_SOURCE,
|
|
71
|
+
BF_FLOW_CONTROLS,
|
|
72
|
+
BF_FLOW_CONTROLS_BUTTON,
|
|
73
|
+
BF_FLOW_MINIMAP,
|
|
74
|
+
BF_FLOW_MINIMAP_MASK,
|
|
75
|
+
XYFLOW_VIEWPORT,
|
|
76
|
+
} from './classes'
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
// Types
|
|
80
|
+
export type {
|
|
81
|
+
FlowProps,
|
|
82
|
+
FlowStore,
|
|
83
|
+
InternalFlowStore,
|
|
84
|
+
FlowStoreOptions,
|
|
85
|
+
FitViewOptions,
|
|
86
|
+
NodeBase,
|
|
87
|
+
EdgeBase,
|
|
88
|
+
InternalNodeBase,
|
|
89
|
+
Viewport,
|
|
90
|
+
NodeLookup,
|
|
91
|
+
ParentLookup,
|
|
92
|
+
EdgeLookup,
|
|
93
|
+
ConnectionLookup,
|
|
94
|
+
CoordinateExtent,
|
|
95
|
+
SnapGrid,
|
|
96
|
+
NodeOrigin,
|
|
97
|
+
Transform,
|
|
98
|
+
PanZoomInstance,
|
|
99
|
+
XYPosition,
|
|
100
|
+
OnConnect,
|
|
101
|
+
OnConnectStart,
|
|
102
|
+
OnConnectEnd,
|
|
103
|
+
IsValidConnection,
|
|
104
|
+
NodeDragItem,
|
|
105
|
+
ConnectionMode,
|
|
106
|
+
NodeComponentProps,
|
|
107
|
+
EdgeComponentProps,
|
|
108
|
+
SelectionMode,
|
|
109
|
+
OnReconnect,
|
|
110
|
+
Connection,
|
|
111
|
+
} from './types'
|
|
112
|
+
// HandleType is consumed by the JSX `<Handle>` component (registry-side)
|
|
113
|
+
// for its `type` prop typing. Re-exported here from `@xyflow/system` so
|
|
114
|
+
// consumers don't need a separate import.
|
|
115
|
+
export type { HandleType } from '@xyflow/system'
|
|
116
|
+
|
|
117
|
+
// Compat layer (React Flow API shims for desk migration)
|
|
118
|
+
export { useNodesState, useEdgesState, useReactFlow, addEdge, reconnectEdge } from './compat'
|
|
119
|
+
|
|
120
|
+
// Re-export useful utilities from @xyflow/system
|
|
121
|
+
export {
|
|
122
|
+
getBezierPath,
|
|
123
|
+
getSmoothStepPath,
|
|
124
|
+
getStraightPath,
|
|
125
|
+
getConnectedEdges,
|
|
126
|
+
getOutgoers,
|
|
127
|
+
getIncomers,
|
|
128
|
+
getNodesBounds,
|
|
129
|
+
getNodesInside,
|
|
130
|
+
getEdgeToolbarTransform,
|
|
131
|
+
Position,
|
|
132
|
+
ConnectionMode as ConnectionModeEnum,
|
|
133
|
+
MarkerType,
|
|
134
|
+
} from '@xyflow/system'
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { onCleanup, untrack } from '@barefootjs/client'
|
|
2
|
+
import {
|
|
3
|
+
XYResizer,
|
|
4
|
+
XY_RESIZER_HANDLE_POSITIONS,
|
|
5
|
+
XY_RESIZER_LINE_POSITIONS,
|
|
6
|
+
ResizeControlVariant,
|
|
7
|
+
updateNodeInternals,
|
|
8
|
+
} from '@xyflow/system'
|
|
9
|
+
import type {
|
|
10
|
+
NodeBase,
|
|
11
|
+
InternalNodeUpdate,
|
|
12
|
+
ControlPosition,
|
|
13
|
+
ControlLinePosition,
|
|
14
|
+
OnResize,
|
|
15
|
+
OnResizeStart,
|
|
16
|
+
OnResizeEnd,
|
|
17
|
+
ShouldResize,
|
|
18
|
+
ResizeControlDirection,
|
|
19
|
+
} from '@xyflow/system'
|
|
20
|
+
import type { XYResizerChange, XYResizerChildChange, XYResizerInstance } from '@xyflow/system'
|
|
21
|
+
import type { FlowStore } from './types'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Options for initNodeResizer.
|
|
25
|
+
*/
|
|
26
|
+
export type NodeResizerOptions = {
|
|
27
|
+
/** Minimum width the node can be resized to (default: 10) */
|
|
28
|
+
minWidth?: number
|
|
29
|
+
/** Minimum height the node can be resized to (default: 10) */
|
|
30
|
+
minHeight?: number
|
|
31
|
+
/** Maximum width the node can be resized to (default: Infinity) */
|
|
32
|
+
maxWidth?: number
|
|
33
|
+
/** Maximum height the node can be resized to (default: Infinity) */
|
|
34
|
+
maxHeight?: number
|
|
35
|
+
/** Whether to keep the aspect ratio during resize (default: false) */
|
|
36
|
+
keepAspectRatio?: boolean
|
|
37
|
+
/** Which variant of resize controls to use: 'handle' (corners) or 'line' (edges) */
|
|
38
|
+
variant?: ResizeControlVariant | 'handle' | 'line'
|
|
39
|
+
/** Callback fired when resize starts */
|
|
40
|
+
onResizeStart?: OnResizeStart
|
|
41
|
+
/** Callback fired during resize with new dimensions */
|
|
42
|
+
onResize?: OnResize
|
|
43
|
+
/** Callback fired when resize ends */
|
|
44
|
+
onResizeEnd?: OnResizeEnd
|
|
45
|
+
/** Callback to determine if resize should proceed */
|
|
46
|
+
shouldResize?: ShouldResize
|
|
47
|
+
/** Whether the node is resizable (default: true) */
|
|
48
|
+
isVisible?: boolean
|
|
49
|
+
/** CSS color for the resize handle lines/corners */
|
|
50
|
+
color?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Initialize resize handles on a node element.
|
|
55
|
+
*
|
|
56
|
+
* Creates resize handle elements (corners and/or lines) and attaches
|
|
57
|
+
* XYResizer from @xyflow/system for the resize logic.
|
|
58
|
+
*
|
|
59
|
+
* Usage:
|
|
60
|
+
* // Inside a custom node type function:
|
|
61
|
+
* initNodeResizer(this.parentElement, {
|
|
62
|
+
* nodeId: props.id,
|
|
63
|
+
* store,
|
|
64
|
+
* minWidth: 100,
|
|
65
|
+
* minHeight: 50,
|
|
66
|
+
* onResize: (event, params) => console.log('Resized:', params),
|
|
67
|
+
* })
|
|
68
|
+
*/
|
|
69
|
+
export function initNodeResizer<NodeType extends NodeBase>(
|
|
70
|
+
nodeEl: HTMLElement,
|
|
71
|
+
nodeId: string,
|
|
72
|
+
store: FlowStore<NodeType>,
|
|
73
|
+
options: NodeResizerOptions = {},
|
|
74
|
+
): () => void {
|
|
75
|
+
const {
|
|
76
|
+
minWidth = 10,
|
|
77
|
+
minHeight = 10,
|
|
78
|
+
maxWidth = Number.MAX_SAFE_INTEGER,
|
|
79
|
+
maxHeight = Number.MAX_SAFE_INTEGER,
|
|
80
|
+
keepAspectRatio = false,
|
|
81
|
+
variant = ResizeControlVariant.Handle,
|
|
82
|
+
onResizeStart,
|
|
83
|
+
onResize,
|
|
84
|
+
onResizeEnd,
|
|
85
|
+
shouldResize,
|
|
86
|
+
isVisible = true,
|
|
87
|
+
color,
|
|
88
|
+
} = options
|
|
89
|
+
|
|
90
|
+
if (!isVisible) return () => {}
|
|
91
|
+
|
|
92
|
+
const resolvedVariant =
|
|
93
|
+
typeof variant === 'string'
|
|
94
|
+
? variant === 'line'
|
|
95
|
+
? ResizeControlVariant.Line
|
|
96
|
+
: ResizeControlVariant.Handle
|
|
97
|
+
: variant
|
|
98
|
+
|
|
99
|
+
// Determine which positions to use based on variant
|
|
100
|
+
const positions: ControlPosition[] =
|
|
101
|
+
resolvedVariant === ResizeControlVariant.Line
|
|
102
|
+
? (XY_RESIZER_LINE_POSITIONS as ControlPosition[])
|
|
103
|
+
: XY_RESIZER_HANDLE_POSITIONS
|
|
104
|
+
|
|
105
|
+
// Mark node as resizable for CSS
|
|
106
|
+
nodeEl.classList.add('bf-flow__node--resizable')
|
|
107
|
+
|
|
108
|
+
// Container for resize handles
|
|
109
|
+
const container = document.createElement('div')
|
|
110
|
+
container.className = 'bf-flow__node-resizer'
|
|
111
|
+
nodeEl.appendChild(container)
|
|
112
|
+
|
|
113
|
+
const resizerInstances: XYResizerInstance[] = []
|
|
114
|
+
|
|
115
|
+
for (const position of positions) {
|
|
116
|
+
const handleEl = document.createElement('div')
|
|
117
|
+
handleEl.className = `bf-flow__resize-handle bf-flow__resize-handle--${position}`
|
|
118
|
+
|
|
119
|
+
if (resolvedVariant === ResizeControlVariant.Line) {
|
|
120
|
+
handleEl.classList.add('bf-flow__resize-handle--line')
|
|
121
|
+
} else {
|
|
122
|
+
handleEl.classList.add('bf-flow__resize-handle--corner')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
handleEl.dataset.position = position
|
|
126
|
+
|
|
127
|
+
if (color) {
|
|
128
|
+
handleEl.style.setProperty('--bf-resize-color', color)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
container.appendChild(handleEl)
|
|
132
|
+
|
|
133
|
+
// Create XYResizer instance for this handle
|
|
134
|
+
const resizerInstance = XYResizer({
|
|
135
|
+
domNode: handleEl as HTMLDivElement,
|
|
136
|
+
nodeId,
|
|
137
|
+
getStoreItems: () => {
|
|
138
|
+
const nodeLookup = untrack(store.nodeLookup)
|
|
139
|
+
const transform = store.getTransform()
|
|
140
|
+
return {
|
|
141
|
+
nodeLookup,
|
|
142
|
+
transform,
|
|
143
|
+
snapGrid: store.snapToGrid ? store.snapGrid : undefined,
|
|
144
|
+
snapToGrid: store.snapToGrid,
|
|
145
|
+
nodeOrigin: store.nodeOrigin,
|
|
146
|
+
paneDomNode: untrack(store.domNode) as HTMLDivElement | null,
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
onChange: (changes: XYResizerChange, childChanges: XYResizerChildChange[]) => {
|
|
150
|
+
// Apply dimension and position changes to the node
|
|
151
|
+
const lookup = untrack(store.nodeLookup)
|
|
152
|
+
const node = lookup.get(nodeId)
|
|
153
|
+
if (!node) return
|
|
154
|
+
|
|
155
|
+
// Update measured dimensions
|
|
156
|
+
if (changes.width != null) {
|
|
157
|
+
node.measured.width = changes.width
|
|
158
|
+
nodeEl.style.width = `${changes.width}px`
|
|
159
|
+
}
|
|
160
|
+
if (changes.height != null) {
|
|
161
|
+
node.measured.height = changes.height
|
|
162
|
+
nodeEl.style.height = `${changes.height}px`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Update position if changed (e.g., resizing from top-left)
|
|
166
|
+
if (changes.x != null || changes.y != null) {
|
|
167
|
+
const newX = changes.x ?? node.internals.positionAbsolute.x
|
|
168
|
+
const newY = changes.y ?? node.internals.positionAbsolute.y
|
|
169
|
+
|
|
170
|
+
node.internals.positionAbsolute = { x: newX, y: newY }
|
|
171
|
+
node.internals.userNode.position = { x: newX, y: newY }
|
|
172
|
+
nodeEl.style.transform = `translate(${newX}px, ${newY}px)`
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Apply child position changes
|
|
176
|
+
for (const childChange of childChanges) {
|
|
177
|
+
const childNode = lookup.get(childChange.id)
|
|
178
|
+
if (childNode) {
|
|
179
|
+
childNode.internals.positionAbsolute = childChange.position
|
|
180
|
+
childNode.internals.userNode.position = childChange.position
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Update node internals for edge recalculation
|
|
185
|
+
const updates = new Map<string, InternalNodeUpdate>()
|
|
186
|
+
updates.set(nodeId, {
|
|
187
|
+
id: nodeId,
|
|
188
|
+
nodeElement: nodeEl as HTMLDivElement,
|
|
189
|
+
force: true,
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const parentLookup = untrack(store.parentLookup)
|
|
193
|
+
updateNodeInternals(
|
|
194
|
+
updates,
|
|
195
|
+
lookup,
|
|
196
|
+
parentLookup,
|
|
197
|
+
untrack(store.domNode),
|
|
198
|
+
store.nodeOrigin,
|
|
199
|
+
store.nodeExtent,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// Notify position-dependent subscribers (edges etc.)
|
|
203
|
+
store.triggerPositionUpdate()
|
|
204
|
+
},
|
|
205
|
+
onEnd: (change) => {
|
|
206
|
+
// Commit final dimensions to the nodes array
|
|
207
|
+
store.setNodes((prev) =>
|
|
208
|
+
prev.map((n) =>
|
|
209
|
+
n.id === nodeId
|
|
210
|
+
? {
|
|
211
|
+
...n,
|
|
212
|
+
position: { x: change.x, y: change.y },
|
|
213
|
+
measured: { width: change.width, height: change.height },
|
|
214
|
+
style: {
|
|
215
|
+
...(n as any).style,
|
|
216
|
+
width: change.width,
|
|
217
|
+
height: change.height,
|
|
218
|
+
},
|
|
219
|
+
}
|
|
220
|
+
: n,
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// Determine resize direction for line handles
|
|
227
|
+
const isLineHandle = resolvedVariant === ResizeControlVariant.Line
|
|
228
|
+
let resizeDirection: ResizeControlDirection | undefined
|
|
229
|
+
if (isLineHandle) {
|
|
230
|
+
if (position === 'left' || position === 'right') {
|
|
231
|
+
resizeDirection = 'horizontal'
|
|
232
|
+
} else if (position === 'top' || position === 'bottom') {
|
|
233
|
+
resizeDirection = 'vertical'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Configure the resizer instance
|
|
238
|
+
resizerInstance.update({
|
|
239
|
+
controlPosition: position,
|
|
240
|
+
boundaries: { minWidth, minHeight, maxWidth, maxHeight },
|
|
241
|
+
keepAspectRatio,
|
|
242
|
+
resizeDirection,
|
|
243
|
+
onResizeStart,
|
|
244
|
+
onResize,
|
|
245
|
+
onResizeEnd,
|
|
246
|
+
shouldResize,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
resizerInstances.push(resizerInstance)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Cleanup function
|
|
253
|
+
const cleanup = () => {
|
|
254
|
+
for (const instance of resizerInstances) {
|
|
255
|
+
instance.destroy()
|
|
256
|
+
}
|
|
257
|
+
container.remove()
|
|
258
|
+
nodeEl.classList.remove('bf-flow__node--resizable')
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
onCleanup(cleanup)
|
|
262
|
+
|
|
263
|
+
return cleanup
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Re-export types that consumers might need
|
|
267
|
+
export { ResizeControlVariant }
|
|
268
|
+
export type {
|
|
269
|
+
ControlPosition,
|
|
270
|
+
ControlLinePosition,
|
|
271
|
+
OnResize,
|
|
272
|
+
OnResizeStart,
|
|
273
|
+
OnResizeEnd,
|
|
274
|
+
ShouldResize,
|
|
275
|
+
ResizeControlDirection,
|
|
276
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-type dispatch — the helper that funnels `<Flow nodeTypes={...}>`
|
|
3
|
+
* entries into the host `<div>` produced by `FlowNodeTypeBridge`.
|
|
4
|
+
*
|
|
5
|
+
* `nodeTypes` accepts two entry shapes through the same map so projects
|
|
6
|
+
* can migrate custom nodes from imperative to JSX one file at a time
|
|
7
|
+
* without touching every `<Flow>` call site:
|
|
8
|
+
*
|
|
9
|
+
* 1. Imperative: `function MyNode(this: HTMLElement, props): void`
|
|
10
|
+
* — mutates `this` (the host element) and returns nothing.
|
|
11
|
+
* 2. JSX-component shim: post-`'use client'` `.tsx` compile output
|
|
12
|
+
* — ignores `this` and returns a real DOM `Node`. The bridge
|
|
13
|
+
* appends that returned node into the host so subsequent
|
|
14
|
+
* wipe+rebuild cycles see it.
|
|
15
|
+
*
|
|
16
|
+
* See piconic-ai/barefootjs#1236 for the migration motivation.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { NodeBase } from './types'
|
|
20
|
+
import type { NodeComponentProps } from './types'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Cross-shape `nodeTypes` entry. The return type covers both supported
|
|
24
|
+
* shapes — imperative entries return `void`, JSX-component shim
|
|
25
|
+
* entries return a `Node`.
|
|
26
|
+
*/
|
|
27
|
+
export type NodeInitFn<NodeType extends NodeBase = NodeBase> = (
|
|
28
|
+
this: HTMLElement,
|
|
29
|
+
props: NodeComponentProps<NodeType>,
|
|
30
|
+
) => void | Node | undefined
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Dispatch a `nodeTypes` entry into the host element. Invokes `initFn`
|
|
34
|
+
* with `el` bound as `this` (imperative path) and appends its return
|
|
35
|
+
* value when it is a `Node` (JSX-component shim path). The
|
|
36
|
+
* `instanceof Node` guard is harmless on the imperative path: `void`
|
|
37
|
+
* is not an instance of `Node`, so the branch is skipped.
|
|
38
|
+
*/
|
|
39
|
+
export function dispatchNodeType<NT extends NodeBase>(
|
|
40
|
+
el: HTMLElement,
|
|
41
|
+
initFn: NodeInitFn<NT>,
|
|
42
|
+
props: NodeComponentProps<NT>,
|
|
43
|
+
): void {
|
|
44
|
+
const result = initFn.call(el, props)
|
|
45
|
+
if (result instanceof Node) el.appendChild(result)
|
|
46
|
+
}
|