@pixerate/editor 0.142.1 → 0.143.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.
@@ -88,12 +88,13 @@ import {
88
88
  matchSize,
89
89
  overlaps,
90
90
  placeBeside,
91
+ placeInView,
91
92
  readStyleSize,
92
93
  reorderNodes,
93
94
  toggleLockNodes,
94
95
  unlockNodes,
95
96
  withStyleSize
96
- } from "../chunk-DW4CV5H4.js";
97
+ } from "../chunk-U4VE4UI2.js";
97
98
  import {
98
99
  DEFAULT_ARRANGE_SHORTCUTS,
99
100
  matchArrangeShortcut
@@ -12057,6 +12058,7 @@ export {
12057
12058
  pathfinder,
12058
12059
  pieScene,
12059
12060
  placeBeside,
12061
+ placeInView,
12060
12062
  planarSplit,
12061
12063
  playCanvasSceneTransition,
12062
12064
  playWebAnimations,
@@ -1,4 +1,4 @@
1
- import type { CanvasEdge, CanvasLayoutOptions, CanvasNode, IncrementalLayoutOptions, PlaceBesideOptions, Rect, Side, XYPosition } from './types.cjs';
1
+ import type { CanvasEdge, CanvasLayoutOptions, CanvasNode, IncrementalLayoutOptions, PlaceBesideOptions, Rect, Side, XYPosition } from "./types.cjs";
2
2
  export declare const DEFAULT_NODE_WIDTH = 200;
3
3
  export declare const DEFAULT_NODE_HEIGHT = 60;
4
4
  export declare const DEFAULT_PLACEMENT_GAP = 48;
@@ -42,7 +42,7 @@ export declare function overlaps(a: Rect, b: Rect, pad?: number): boolean;
42
42
  /**
43
43
  * Maps directed layout flow to its natural forward growth side.
44
44
  */
45
- export declare function flowSide(direction?: 'LR' | 'TB' | 'RL' | 'BT'): Side;
45
+ export declare function flowSide(direction?: "LR" | "TB" | "RL" | "BT"): Side;
46
46
  /**
47
47
  * Incremental layout blending:
48
48
  * Keeps established/manual node positions intact. Runs Dagre on the entire graph,
@@ -58,3 +58,16 @@ export declare function placeBeside(rects: Record<string, Rect>, size: {
58
58
  width: number;
59
59
  height: number;
60
60
  }, side: Side, nearNodeId?: string, options?: PlaceBesideOptions): XYPosition;
61
+ /**
62
+ * Where a node of `size` goes when nothing anchors it: centred in `view` (the visible part of
63
+ * the canvas, in flow coordinates) when that is free, else the nearest free spot in rings
64
+ * around the centre, so new work appears where the user is looking and never on top of
65
+ * `rects`.
66
+ */
67
+ export declare function placeInView(rects: Record<string, Rect>, size: {
68
+ width: number;
69
+ height: number;
70
+ }, view: Rect, options?: {
71
+ gap?: number;
72
+ maxRings?: number;
73
+ }): XYPosition;
@@ -1,4 +1,4 @@
1
- import type { CanvasEdge, CanvasLayoutOptions, CanvasNode, IncrementalLayoutOptions, PlaceBesideOptions, Rect, Side, XYPosition } from './types.js';
1
+ import type { CanvasEdge, CanvasLayoutOptions, CanvasNode, IncrementalLayoutOptions, PlaceBesideOptions, Rect, Side, XYPosition } from "./types.js";
2
2
  export declare const DEFAULT_NODE_WIDTH = 200;
3
3
  export declare const DEFAULT_NODE_HEIGHT = 60;
4
4
  export declare const DEFAULT_PLACEMENT_GAP = 48;
@@ -42,7 +42,7 @@ export declare function overlaps(a: Rect, b: Rect, pad?: number): boolean;
42
42
  /**
43
43
  * Maps directed layout flow to its natural forward growth side.
44
44
  */
45
- export declare function flowSide(direction?: 'LR' | 'TB' | 'RL' | 'BT'): Side;
45
+ export declare function flowSide(direction?: "LR" | "TB" | "RL" | "BT"): Side;
46
46
  /**
47
47
  * Incremental layout blending:
48
48
  * Keeps established/manual node positions intact. Runs Dagre on the entire graph,
@@ -58,3 +58,16 @@ export declare function placeBeside(rects: Record<string, Rect>, size: {
58
58
  width: number;
59
59
  height: number;
60
60
  }, side: Side, nearNodeId?: string, options?: PlaceBesideOptions): XYPosition;
61
+ /**
62
+ * Where a node of `size` goes when nothing anchors it: centred in `view` (the visible part of
63
+ * the canvas, in flow coordinates) when that is free, else the nearest free spot in rings
64
+ * around the centre, so new work appears where the user is looking and never on top of
65
+ * `rects`.
66
+ */
67
+ export declare function placeInView(rects: Record<string, Rect>, size: {
68
+ width: number;
69
+ height: number;
70
+ }, view: Rect, options?: {
71
+ gap?: number;
72
+ maxRings?: number;
73
+ }): XYPosition;
@@ -4,8 +4,14 @@ var DEFAULT_NODE_WIDTH = 200;
4
4
  var DEFAULT_NODE_HEIGHT = 60;
5
5
  var DEFAULT_PLACEMENT_GAP = 48;
6
6
  function getCenteredNodePosition(width = 0, height = 0, screenToFlowPosition, screenDimensions) {
7
- const rect = _nullishCoalesce(screenDimensions, () => ( (typeof document !== "undefined" ? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } : { width: 1920, height: 1080 })));
8
- const flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });
7
+ const rect = _nullishCoalesce(screenDimensions, () => ( (typeof document !== "undefined" ? {
8
+ width: document.documentElement.clientWidth,
9
+ height: document.documentElement.clientHeight
10
+ } : { width: 1920, height: 1080 })));
11
+ const flowPos = screenToFlowPosition({
12
+ x: rect.width / 2,
13
+ y: rect.height / 2
14
+ });
9
15
  return {
10
16
  x: flowPos.x - width / 2,
11
17
  y: flowPos.y - height / 2
@@ -73,7 +79,10 @@ function getLayoutedNodes(nodes, edges, getNodesBounds, options = {}) {
73
79
  function estimateNodeSize(node, defaultWidth = DEFAULT_NODE_WIDTH, defaultHeight = DEFAULT_NODE_HEIGHT) {
74
80
  const label = String(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([node, 'access', _10 => _10.data, 'optionalAccess', _11 => _11.label]), () => ( _optionalChain([node, 'access', _12 => _12.data, 'optionalAccess', _13 => _13.title]))), () => ( node.id)), () => ( "")));
75
81
  const lines = label.split(/\n|<br\s*\/?>/);
76
- const longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, "").length), 1);
82
+ const longest = Math.max(
83
+ ...lines.map((l) => l.replace(/<[^>]+>/g, "").length),
84
+ 1
85
+ );
77
86
  const width = Math.min(480, Math.max(defaultWidth, longest * 8 + 48));
78
87
  const height = Math.max(defaultHeight, lines.length * 20 + 24);
79
88
  return { width, height };
@@ -203,6 +212,38 @@ function placeBeside(rects, size, side, nearNodeId, options = {}) {
203
212
  y: Math.round(baseY)
204
213
  };
205
214
  }
215
+ function placeInView(rects, size, view, options = {}) {
216
+ const gap = _nullishCoalesce(options.gap, () => ( DEFAULT_PLACEMENT_GAP));
217
+ const maxRings = _nullishCoalesce(options.maxRings, () => ( 24));
218
+ const obstacles = Object.values(rects);
219
+ const start = {
220
+ x: view.x + view.width / 2 - size.width / 2,
221
+ y: view.y + view.height / 2 - size.height / 2
222
+ };
223
+ const free = (p) => !obstacles.some(
224
+ (o) => overlaps({ ...p, width: size.width, height: size.height }, o, gap / 2)
225
+ );
226
+ if (free(start)) return { x: Math.round(start.x), y: Math.round(start.y) };
227
+ const stepX = size.width + gap;
228
+ const stepY = size.height + gap;
229
+ for (let ring = 1; ring <= maxRings; ring++) {
230
+ const candidates = [];
231
+ for (let i = -ring; i <= ring; i++) {
232
+ candidates.push(
233
+ { x: start.x + ring * stepX, y: start.y + i * stepY },
234
+ { x: start.x - ring * stepX, y: start.y + i * stepY },
235
+ { x: start.x + i * stepX, y: start.y + ring * stepY },
236
+ { x: start.x + i * stepX, y: start.y - ring * stepY }
237
+ );
238
+ }
239
+ candidates.sort(
240
+ (a, b) => Math.hypot(a.x - start.x, a.y - start.y) - Math.hypot(b.x - start.x, b.y - start.y)
241
+ );
242
+ const spot = candidates.find(free);
243
+ if (spot) return { x: Math.round(spot.x), y: Math.round(spot.y) };
244
+ }
245
+ return { x: Math.round(start.x), y: Math.round(start.y) };
246
+ }
206
247
 
207
248
  // src/canvas/arrange.ts
208
249
  var LOCKED_DATA_KEY = "locked";
@@ -588,4 +629,5 @@ function invertNodeUpdates(nodes, updates) {
588
629
 
589
630
 
590
631
 
591
- exports.DEFAULT_NODE_WIDTH = DEFAULT_NODE_WIDTH; exports.DEFAULT_NODE_HEIGHT = DEFAULT_NODE_HEIGHT; exports.DEFAULT_PLACEMENT_GAP = DEFAULT_PLACEMENT_GAP; exports.getCenteredNodePosition = getCenteredNodePosition; exports.centerNodes = centerNodes; exports.getLayoutedNodes = getLayoutedNodes; exports.estimateNodeSize = estimateNodeSize; exports.boundsOf = boundsOf; exports.centre = centre; exports.overlaps = overlaps; exports.flowSide = flowSide; exports.ensureLayout = ensureLayout; exports.placeBeside = placeBeside; exports.LOCKED_DATA_KEY = LOCKED_DATA_KEY; exports.LOCKED_PREVIOUS_FLAGS_KEY = LOCKED_PREVIOUS_FLAGS_KEY; exports.readStyleSize = readStyleSize; exports.withStyleSize = withStyleSize; exports.getNodeSize = getNodeSize; exports.getAbsolutePosition = getAbsolutePosition; exports.createNodeLookup = createNodeLookup; exports.getNodeRect = getNodeRect; exports.isLocked = isLocked; exports.lockNodes = lockNodes; exports.unlockNodes = unlockNodes; exports.toggleLockNodes = toggleLockNodes; exports.alignNodes = alignNodes; exports.distributeNodes = distributeNodes; exports.matchSize = matchSize; exports.reorderNodes = reorderNodes; exports.applyNodeUpdates = applyNodeUpdates; exports.invertNodeUpdates = invertNodeUpdates;
632
+
633
+ exports.DEFAULT_NODE_WIDTH = DEFAULT_NODE_WIDTH; exports.DEFAULT_NODE_HEIGHT = DEFAULT_NODE_HEIGHT; exports.DEFAULT_PLACEMENT_GAP = DEFAULT_PLACEMENT_GAP; exports.getCenteredNodePosition = getCenteredNodePosition; exports.centerNodes = centerNodes; exports.getLayoutedNodes = getLayoutedNodes; exports.estimateNodeSize = estimateNodeSize; exports.boundsOf = boundsOf; exports.centre = centre; exports.overlaps = overlaps; exports.flowSide = flowSide; exports.ensureLayout = ensureLayout; exports.placeBeside = placeBeside; exports.placeInView = placeInView; exports.LOCKED_DATA_KEY = LOCKED_DATA_KEY; exports.LOCKED_PREVIOUS_FLAGS_KEY = LOCKED_PREVIOUS_FLAGS_KEY; exports.readStyleSize = readStyleSize; exports.withStyleSize = withStyleSize; exports.getNodeSize = getNodeSize; exports.getAbsolutePosition = getAbsolutePosition; exports.createNodeLookup = createNodeLookup; exports.getNodeRect = getNodeRect; exports.isLocked = isLocked; exports.lockNodes = lockNodes; exports.unlockNodes = unlockNodes; exports.toggleLockNodes = toggleLockNodes; exports.alignNodes = alignNodes; exports.distributeNodes = distributeNodes; exports.matchSize = matchSize; exports.reorderNodes = reorderNodes; exports.applyNodeUpdates = applyNodeUpdates; exports.invertNodeUpdates = invertNodeUpdates;
@@ -4,8 +4,14 @@ var DEFAULT_NODE_WIDTH = 200;
4
4
  var DEFAULT_NODE_HEIGHT = 60;
5
5
  var DEFAULT_PLACEMENT_GAP = 48;
6
6
  function getCenteredNodePosition(width = 0, height = 0, screenToFlowPosition, screenDimensions) {
7
- const rect = screenDimensions ?? (typeof document !== "undefined" ? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } : { width: 1920, height: 1080 });
8
- const flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });
7
+ const rect = screenDimensions ?? (typeof document !== "undefined" ? {
8
+ width: document.documentElement.clientWidth,
9
+ height: document.documentElement.clientHeight
10
+ } : { width: 1920, height: 1080 });
11
+ const flowPos = screenToFlowPosition({
12
+ x: rect.width / 2,
13
+ y: rect.height / 2
14
+ });
9
15
  return {
10
16
  x: flowPos.x - width / 2,
11
17
  y: flowPos.y - height / 2
@@ -73,7 +79,10 @@ function getLayoutedNodes(nodes, edges, getNodesBounds, options = {}) {
73
79
  function estimateNodeSize(node, defaultWidth = DEFAULT_NODE_WIDTH, defaultHeight = DEFAULT_NODE_HEIGHT) {
74
80
  const label = String(node.data?.label ?? node.data?.title ?? node.id ?? "");
75
81
  const lines = label.split(/\n|<br\s*\/?>/);
76
- const longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, "").length), 1);
82
+ const longest = Math.max(
83
+ ...lines.map((l) => l.replace(/<[^>]+>/g, "").length),
84
+ 1
85
+ );
77
86
  const width = Math.min(480, Math.max(defaultWidth, longest * 8 + 48));
78
87
  const height = Math.max(defaultHeight, lines.length * 20 + 24);
79
88
  return { width, height };
@@ -203,6 +212,38 @@ function placeBeside(rects, size, side, nearNodeId, options = {}) {
203
212
  y: Math.round(baseY)
204
213
  };
205
214
  }
215
+ function placeInView(rects, size, view, options = {}) {
216
+ const gap = options.gap ?? DEFAULT_PLACEMENT_GAP;
217
+ const maxRings = options.maxRings ?? 24;
218
+ const obstacles = Object.values(rects);
219
+ const start = {
220
+ x: view.x + view.width / 2 - size.width / 2,
221
+ y: view.y + view.height / 2 - size.height / 2
222
+ };
223
+ const free = (p) => !obstacles.some(
224
+ (o) => overlaps({ ...p, width: size.width, height: size.height }, o, gap / 2)
225
+ );
226
+ if (free(start)) return { x: Math.round(start.x), y: Math.round(start.y) };
227
+ const stepX = size.width + gap;
228
+ const stepY = size.height + gap;
229
+ for (let ring = 1; ring <= maxRings; ring++) {
230
+ const candidates = [];
231
+ for (let i = -ring; i <= ring; i++) {
232
+ candidates.push(
233
+ { x: start.x + ring * stepX, y: start.y + i * stepY },
234
+ { x: start.x - ring * stepX, y: start.y + i * stepY },
235
+ { x: start.x + i * stepX, y: start.y + ring * stepY },
236
+ { x: start.x + i * stepX, y: start.y - ring * stepY }
237
+ );
238
+ }
239
+ candidates.sort(
240
+ (a, b) => Math.hypot(a.x - start.x, a.y - start.y) - Math.hypot(b.x - start.x, b.y - start.y)
241
+ );
242
+ const spot = candidates.find(free);
243
+ if (spot) return { x: Math.round(spot.x), y: Math.round(spot.y) };
244
+ }
245
+ return { x: Math.round(start.x), y: Math.round(start.y) };
246
+ }
206
247
 
207
248
  // src/canvas/arrange.ts
208
249
  var LOCKED_DATA_KEY = "locked";
@@ -570,6 +611,7 @@ export {
570
611
  flowSide,
571
612
  ensureLayout,
572
613
  placeBeside,
614
+ placeInView,
573
615
  LOCKED_DATA_KEY,
574
616
  LOCKED_PREVIOUS_FLAGS_KEY,
575
617
  readStyleSize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.142.1",
3
+ "version": "0.143.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",