@squeed/flow-sdk 2.0.21 → 2.0.24

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 CHANGED
@@ -474,6 +474,88 @@ drop placement that settles into the size-aware automatic layout. The example
474
474
  creation picker uses this mode so object expansion, property edits, and the
475
475
  spacing slider can reposition new nodes without retaining their drop positions.
476
476
 
477
+ ### Node Collisions
478
+
479
+ Collision handling is opt-in through `FlowEngine`; the SDK does not automatically
480
+ resolve overlaps or enable node dragging. Call after measurement/layout (for
481
+ example from `onLayoutComplete`) or after explicitly placing nodes:
482
+
483
+ ```ts
484
+ const collisions = engine.detectNodeCollisions({ margin: 15 });
485
+ const result = engine.resolveNodeCollisions({ margin: 15, animate: true });
486
+ // result: { positions, movedNodeIds, collisions, iterations, resolved }
487
+ ```
488
+
489
+ `detectNodeCollisions` does not change state. It returns pairs with `source`,
490
+ `target`, and `overlap: { x, y }`, using axis-aligned measured bounds and a gap
491
+ of `margin` model units (default 15). Only visible layout nodes participate.
492
+ Siblings are compared at every hierarchy level; parent-child containment is
493
+ intentional, and conflicts between branches are reported as container pairs.
494
+ Container padding and label minimum width are included, but edge paths and
495
+ edge-label collisions are outside this API.
496
+
497
+ Resolution moves sibling groups with their descendants, preserving nodes pinned
498
+ by `setNodePosition` with persistent placement. A group containing a pinned node
499
+ cannot be translated. It uses at most `maxIterations` (default 50, allowed 0-1000)
500
+ and reports remaining collisions rather than moving pinned nodes or looping
501
+ indefinitely. Invalid margins/iteration limits throw `RangeError`.
502
+
503
+ `positions` is an independent map of target leaf positions, not animation progress;
504
+ `movedNodeIds` lists changed leaves and `collisions` lists unresolved target pairs.
505
+ `animate` defaults to true and reuses the layout animation driver; reduced motion,
506
+ `config.disableAnimations`, and performance mode apply immediately. Pan/zoom stays
507
+ unchanged, pending automatic viewport actions are cancelled when nodes move, and
508
+ resolution does not emit `onLayoutComplete` again. Another layout or explicit
509
+ position change can interrupt it. Results are renderer-local, are not pinned or
510
+ written into JSON, and may be replaced by the next automatic layout.
511
+
512
+ ### Swappable Grid Mode
513
+
514
+ One graph-level call hides edges, edge labels, collapse icons and connection handles, arranges
515
+ visible nodes in a measured grid, then enables drag-to-swap after placement:
516
+
517
+ ```ts
518
+ engine.setNodeGridMode(true, { columns: 3, margin: 40, animate: true });
519
+ engine.swapGridNodes(sourceId, targetId); // Optional programmatic swap
520
+ engine.setNodeGridMode(false); // Restore pre-grid positions and edge visibility
521
+ ```
522
+
523
+ Drop a node onto a sibling to exchange their slots; dropping outside or pressing
524
+ Escape cancels the drag. Read-only mode prevents swaps. The original JSON, edges
525
+ and parent relationships are not changed. Sibling grids preserve parent groups;
526
+ swaps across parents are rejected. Sibling sets containing persistent pinned
527
+ nodes are not rearranged or swapped. Grid order survives layout/size changes
528
+ while the mode is active. It is renderer-local and is not saved in the document.
529
+
530
+ The bottom toolbar's **Collision mode** button uses this same API. While active,
531
+ it shows the existing compact gap slider and a **Fill** toggle. These controls
532
+ stay synchronized with programmatic mode changes.
533
+
534
+ `columns` defaults to `ceil(sqrt(siblingCount))` without fill; `gaps` accepts a
535
+ number or `{ x, y }`, falling back to `margin` (15 model units). `animate` defaults
536
+ to true; reduced motion and the animation kill switch are respected. Ordinary
537
+ grid entry and exit center and fit with 80px viewport padding using the same queued
538
+ fit as direction changes, coordinated with placement and subsequent size updates.
539
+ Manual pan/zoom cancels the queued fit. Gap changes and swaps preserve zoom/pan.
540
+ Fill retains its separate tile padding and 1:1 viewport behavior.
541
+ For arrangement without hiding edges or enabling swaps,
542
+ use `engine.arrangeNodesInGrid(options)`, which returns `positions`, `movedNodeIds`,
543
+ `skippedNodeIds` and remaining `collisions` without changing graph data.
544
+
545
+ ```ts
546
+ engine.setNodeGridMode(true, { fill: true, gaps: 0 });
547
+ ```
548
+
549
+ Fill resizes the existing node tiles to cover the canvas, not just their positions.
550
+ It picks columns from the canvas aspect ratio and expands the final row to avoid
551
+ empty cells. `padding` defaults to 0; gaps are constrained when needed to keep cells
552
+ positive. Fill uses a 1:1 viewport (subject to configured zoom limits), with size,
553
+ position and viewport animation coordinated. It reflows on canvas resize and keeps
554
+ swapping enabled. Turning Fill off restores natural node sizes and queues an 80px
555
+ center-and-fit; exiting the mode
556
+ centers and fits the restored graph. `arrangeNodesInGrid` returns target `sizes`
557
+ and `bounds` when filling. These overrides never change the saved document.
558
+
477
559
  ### Animated Viewport Actions
478
560
 
479
561
  Capture `FlowEngine` through `callbacks.onInit`. Set `config.animateViewport`
@@ -884,6 +966,41 @@ Special `$`-prefixed keys control node rendering:
884
966
 
885
967
  `$content` renders Markdown headings, lists, emphasis, tables, links, and fenced code. Supply Markdown source text, not a filename or URL to load. Raw HTML is disabled and unsafe link schemes are filtered.
886
968
 
969
+ For `$contentFormat: "jsx"`, supply a JSON array of declarative UI components,
970
+ not executable JSX source:
971
+
972
+ ```json
973
+ {
974
+ "panel": {
975
+ "$view": "object",
976
+ "$contentFormat": "jsx",
977
+ "$content": [{ "type": "text", "text": "Hello" }]
978
+ }
979
+ }
980
+ ```
981
+
982
+ The host supplies `config.customViews.jsxContent`, a component receiving
983
+ `JsxContentRendererProps`: `{ components, colorMode, fillHeight }`. Squeed provides
984
+ `WorkflowContentRenderer`, which wraps the array in one `render_scene` step for
985
+ `UnifiedWorkflowRenderer`. The SDK does not import or depend on that renderer.
986
+ Without a supplied view, it displays an unavailable-renderer message and keeps
987
+ the content editable. JSON-encoded arrays are also accepted, but JSX source code
988
+ is no longer compiled or executed. Invalid arrays show a validation error.
989
+
990
+ The existing source editor and fullscreen controls are reused. JSX edits are
991
+ parsed back into arrays, and `onNodeContentChange` receives `NodeContent`
992
+ (`string | Record<string, unknown>[]`). Markdown stays text, and HTML keeps its
993
+ sanitized iframe preview. Host renderers are responsible for their components'
994
+ behavior and external actions; the SDK's HTML iframe sandbox does not apply to
995
+ a host-supplied JSX-content view.
996
+
997
+ Document playback reveals completed entries of JSX `$content` arrays as chunks
998
+ arrive, while unfinished entries remain hidden. Squeed's workflow channel also
999
+ previews repaired streamed documents containing component arrays. Previews do
1000
+ not edit saved data: completion strictly parses the final JSON before committing,
1001
+ and failure or disconnect restores the saved document. The renderer stays in
1002
+ the host; this does not enable executable JSX or create a per-chunk MCP command.
1003
+
887
1004
  With `config.editable: true`, double-click the content body to edit the original Markdown source. Enter inserts a new line; Ctrl/Cmd+Enter or clicking away saves, and Escape cancels. Source whitespace is preserved. The node grows vertically as text is added; label editors grow horizontally within their width limit. Both keep their starting size as a minimum and omit input focus outlines. Clearing the body keeps an empty, editable content view; use `setNodeProp(json, address, "$content", undefined)` to remove the content property. Content nodes use the same minimum width as their object-property rows, and the right-aligned table/article button switches views without changing the document.
888
1005
 
889
1006
  The node animation toggle supports single and multiple selections. The SVG stroke follows the measured node border without changing layout or intercepting input. Text, array, object/content, and compound nodes support it, including outlined or transparent nodes. It uses the explicit border color or theme accent. Reduced-motion preferences pause the movement; graphs over 200 nodes suppress the trace in performance mode. `readOnly` prevents editing/toggling but still renders configured animation; `selectionToolbar.hide: ["animation"]` hides the toggle.