@nocobase/flow-engine 2.1.0-alpha.3 → 2.1.0-alpha.30

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 (160) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/lib/JSRunner.d.ts +10 -1
  4. package/lib/JSRunner.js +50 -5
  5. package/lib/ViewScopedFlowEngine.js +5 -1
  6. package/lib/components/FieldModelRenderer.js +2 -2
  7. package/lib/components/FlowModelRenderer.d.ts +3 -1
  8. package/lib/components/FlowModelRenderer.js +12 -6
  9. package/lib/components/MobilePopup.js +6 -5
  10. package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
  11. package/lib/components/dnd/gridDragPlanner.js +601 -21
  12. package/lib/components/dnd/index.d.ts +19 -1
  13. package/lib/components/dnd/index.js +243 -23
  14. package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
  15. package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
  16. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
  17. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +68 -10
  18. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
  19. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
  20. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
  21. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
  22. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
  23. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
  24. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
  25. package/lib/components/subModel/AddSubModelButton.js +27 -1
  26. package/lib/components/subModel/index.d.ts +1 -0
  27. package/lib/components/subModel/index.js +19 -0
  28. package/lib/components/subModel/utils.d.ts +1 -1
  29. package/lib/components/subModel/utils.js +2 -2
  30. package/lib/data-source/index.d.ts +73 -0
  31. package/lib/data-source/index.js +211 -1
  32. package/lib/executor/FlowExecutor.js +31 -8
  33. package/lib/flowContext.d.ts +2 -0
  34. package/lib/flowContext.js +31 -1
  35. package/lib/flowEngine.d.ts +151 -1
  36. package/lib/flowEngine.js +389 -15
  37. package/lib/flowI18n.js +2 -1
  38. package/lib/flowSettings.d.ts +14 -6
  39. package/lib/flowSettings.js +34 -6
  40. package/lib/lazy-helper.d.ts +14 -0
  41. package/lib/lazy-helper.js +71 -0
  42. package/lib/locale/en-US.json +1 -0
  43. package/lib/locale/index.d.ts +2 -0
  44. package/lib/locale/zh-CN.json +1 -0
  45. package/lib/models/DisplayItemModel.d.ts +1 -1
  46. package/lib/models/EditableItemModel.d.ts +1 -1
  47. package/lib/models/FilterableItemModel.d.ts +1 -1
  48. package/lib/models/flowModel.d.ts +13 -10
  49. package/lib/models/flowModel.js +78 -18
  50. package/lib/provider.js +38 -23
  51. package/lib/reactive/observer.js +46 -16
  52. package/lib/runjs-context/registry.d.ts +1 -1
  53. package/lib/runjs-context/setup.js +20 -12
  54. package/lib/runjs-context/snippets/index.js +13 -2
  55. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
  56. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
  57. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
  58. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
  59. package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
  60. package/lib/scheduler/ModelOperationScheduler.js +3 -2
  61. package/lib/types.d.ts +47 -1
  62. package/lib/utils/createCollectionContextMeta.js +6 -2
  63. package/lib/utils/index.d.ts +2 -2
  64. package/lib/utils/index.js +4 -0
  65. package/lib/utils/parsePathnameToViewParams.js +1 -1
  66. package/lib/utils/runjsTemplateCompat.js +1 -1
  67. package/lib/utils/runjsValue.js +41 -11
  68. package/lib/utils/schema-utils.d.ts +7 -1
  69. package/lib/utils/schema-utils.js +19 -0
  70. package/lib/views/FlowView.d.ts +7 -1
  71. package/lib/views/runViewBeforeClose.d.ts +10 -0
  72. package/lib/views/runViewBeforeClose.js +45 -0
  73. package/lib/views/useDialog.d.ts +2 -1
  74. package/lib/views/useDialog.js +20 -3
  75. package/lib/views/useDrawer.d.ts +2 -1
  76. package/lib/views/useDrawer.js +20 -3
  77. package/lib/views/usePage.d.ts +2 -1
  78. package/lib/views/usePage.js +10 -3
  79. package/package.json +6 -5
  80. package/src/JSRunner.ts +68 -4
  81. package/src/ViewScopedFlowEngine.ts +4 -0
  82. package/src/__tests__/JSRunner.test.ts +27 -1
  83. package/src/__tests__/flow-engine.test.ts +166 -0
  84. package/src/__tests__/flowContext.test.ts +65 -1
  85. package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
  86. package/src/__tests__/flowSettings.test.ts +94 -15
  87. package/src/__tests__/objectVariable.test.ts +24 -0
  88. package/src/__tests__/provider.test.tsx +24 -2
  89. package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
  90. package/src/__tests__/runjsContext.test.ts +16 -0
  91. package/src/__tests__/runjsContextRuntime.test.ts +2 -0
  92. package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
  93. package/src/__tests__/runjsSnippets.test.ts +21 -0
  94. package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
  95. package/src/components/FieldModelRenderer.tsx +2 -1
  96. package/src/components/FlowModelRenderer.tsx +18 -6
  97. package/src/components/MobilePopup.tsx +4 -2
  98. package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
  99. package/src/components/__tests__/dnd.test.ts +44 -0
  100. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
  101. package/src/components/__tests__/gridDragPlanner.test.ts +512 -3
  102. package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
  103. package/src/components/dnd/gridDragPlanner.ts +743 -19
  104. package/src/components/dnd/index.tsx +291 -27
  105. package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
  106. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +88 -10
  107. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
  108. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
  109. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +189 -3
  110. package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
  111. package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
  112. package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
  113. package/src/components/subModel/AddSubModelButton.tsx +32 -2
  114. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
  115. package/src/components/subModel/index.ts +1 -0
  116. package/src/components/subModel/utils.ts +1 -1
  117. package/src/data-source/__tests__/index.test.ts +34 -1
  118. package/src/data-source/index.ts +258 -2
  119. package/src/executor/FlowExecutor.ts +34 -9
  120. package/src/executor/__tests__/flowExecutor.test.ts +57 -0
  121. package/src/flowContext.ts +37 -3
  122. package/src/flowEngine.ts +445 -11
  123. package/src/flowI18n.ts +2 -1
  124. package/src/flowSettings.ts +40 -6
  125. package/src/lazy-helper.tsx +57 -0
  126. package/src/locale/en-US.json +1 -0
  127. package/src/locale/zh-CN.json +1 -0
  128. package/src/models/DisplayItemModel.tsx +1 -1
  129. package/src/models/EditableItemModel.tsx +1 -1
  130. package/src/models/FilterableItemModel.tsx +1 -1
  131. package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
  132. package/src/models/__tests__/flowModel.test.ts +19 -3
  133. package/src/models/flowModel.tsx +119 -33
  134. package/src/provider.tsx +41 -25
  135. package/src/reactive/__tests__/observer.test.tsx +82 -0
  136. package/src/reactive/observer.tsx +87 -25
  137. package/src/runjs-context/registry.ts +1 -1
  138. package/src/runjs-context/setup.ts +22 -12
  139. package/src/runjs-context/snippets/index.ts +12 -1
  140. package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
  141. package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
  142. package/src/scheduler/ModelOperationScheduler.ts +14 -3
  143. package/src/types.ts +60 -0
  144. package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
  145. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
  146. package/src/utils/__tests__/runjsValue.test.ts +11 -0
  147. package/src/utils/__tests__/utils.test.ts +62 -0
  148. package/src/utils/createCollectionContextMeta.ts +6 -2
  149. package/src/utils/index.ts +2 -1
  150. package/src/utils/parsePathnameToViewParams.ts +2 -2
  151. package/src/utils/runjsTemplateCompat.ts +1 -1
  152. package/src/utils/runjsValue.ts +50 -11
  153. package/src/utils/schema-utils.ts +30 -1
  154. package/src/views/FlowView.tsx +11 -1
  155. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  156. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
  157. package/src/views/runViewBeforeClose.ts +19 -0
  158. package/src/views/useDialog.tsx +25 -3
  159. package/src/views/useDrawer.tsx +25 -3
  160. package/src/views/usePage.tsx +12 -3
@@ -44,7 +44,12 @@ __export(gridDragPlanner_exports, {
44
44
  MAX_SLOT_THICKNESS: () => MAX_SLOT_THICKNESS,
45
45
  MIN_SLOT_THICKNESS: () => MIN_SLOT_THICKNESS,
46
46
  buildLayoutSnapshot: () => buildLayoutSnapshot,
47
+ findModelUidLayoutPosition: () => findModelUidLayoutPosition,
47
48
  getSlotKey: () => getSlotKey,
49
+ isSameGridLayout: () => isSameGridLayout,
50
+ normalizeGridLayout: () => normalizeGridLayout,
51
+ projectLayoutToLegacyRows: () => projectLayoutToLegacyRows,
52
+ replaceUidInGridLayout: () => replaceUidInGridLayout,
48
53
  resolveDropIntent: () => resolveDropIntent,
49
54
  simulateLayoutForSlot: () => simulateLayoutForSlot
50
55
  });
@@ -165,12 +170,46 @@ const createColumnInsertRect = /* @__PURE__ */ __name((itemRect, position) => {
165
170
  height: thickness
166
171
  });
167
172
  }, "createColumnInsertRect");
173
+ const parseLayoutPath = /* @__PURE__ */ __name((value) => {
174
+ if (!value) {
175
+ return void 0;
176
+ }
177
+ try {
178
+ const parsed = JSON.parse(value);
179
+ if (!Array.isArray(parsed)) {
180
+ return void 0;
181
+ }
182
+ const path = parsed.map((entry) => {
183
+ if (!entry || typeof entry !== "object" || typeof entry.rowId !== "string") {
184
+ return null;
185
+ }
186
+ return {
187
+ rowId: entry.rowId,
188
+ ...typeof entry.cellId === "string" ? { cellId: entry.cellId } : {}
189
+ };
190
+ }).filter(Boolean);
191
+ return path.length ? path : void 0;
192
+ } catch (error) {
193
+ return void 0;
194
+ }
195
+ }, "parseLayoutPath");
196
+ const createLegacyCellPath = /* @__PURE__ */ __name((rowId, columnIndex) => [
197
+ {
198
+ rowId,
199
+ cellId: `${rowId}:cell:${columnIndex}`
200
+ }
201
+ ], "createLegacyCellPath");
168
202
  const expandColumnRect = /* @__PURE__ */ __name((columnRect) => ({
169
203
  top: columnRect.top,
170
204
  left: columnRect.left,
171
205
  width: columnRect.width,
172
206
  height: Math.max(columnRect.height, MIN_SLOT_THICKNESS)
173
207
  }), "expandColumnRect");
208
+ const hasDirectNestedRows = /* @__PURE__ */ __name((columnElement) => {
209
+ return Array.from(columnElement.querySelectorAll("[data-grid-row-id]")).some((rowElement) => {
210
+ return rowElement.closest("[data-grid-column-row-id][data-grid-column-index]") === columnElement;
211
+ });
212
+ }, "hasDirectNestedRows");
174
213
  const buildLayoutSnapshot = /* @__PURE__ */ __name(({ container }) => {
175
214
  if (!container) {
176
215
  return {
@@ -178,13 +217,19 @@ const buildLayoutSnapshot = /* @__PURE__ */ __name(({ container }) => {
178
217
  containerRect: { top: 0, left: 0, width: 0, height: 0 }
179
218
  };
180
219
  }
181
- const containerRect = toRect(container.getBoundingClientRect());
220
+ const scope = container.hasAttribute("data-grid-root") ? container : container.querySelector("[data-grid-root]");
221
+ const layoutContainer = scope || container;
222
+ const containerRect = toRect(layoutContainer.getBoundingClientRect());
182
223
  const slots = [];
183
- const allRowElements = Array.from(container.querySelectorAll("[data-grid-row-id]"));
224
+ const allRowElements = Array.from(layoutContainer.querySelectorAll("[data-grid-row-id]"));
225
+ const hasGridRootScope = layoutContainer.hasAttribute("data-grid-root");
184
226
  const rowElements = allRowElements.filter((el) => {
185
227
  const htmlEl = el;
228
+ if (hasGridRootScope) {
229
+ return htmlEl.closest("[data-grid-root]") === layoutContainer;
230
+ }
186
231
  let parent = htmlEl.parentElement;
187
- while (parent && parent !== container) {
232
+ while (parent && parent !== layoutContainer) {
188
233
  if (parent.hasAttribute("data-grid-row-id")) {
189
234
  return false;
190
235
  }
@@ -204,23 +249,42 @@ const buildLayoutSnapshot = /* @__PURE__ */ __name(({ container }) => {
204
249
  });
205
250
  return { slots, containerRect };
206
251
  }
207
- rowElements.forEach((rowElement, rowIndex) => {
252
+ const getRowScopeElement = /* @__PURE__ */ __name((rowElement) => {
253
+ const parent = rowElement.parentElement;
254
+ if (!parent || !layoutContainer.contains(parent)) {
255
+ return layoutContainer;
256
+ }
257
+ return parent;
258
+ }, "getRowScopeElement");
259
+ const rowElementsByScope = /* @__PURE__ */ new Map();
260
+ rowElements.forEach((rowElement) => {
261
+ const rowScopeElement = getRowScopeElement(rowElement);
262
+ rowElementsByScope.set(rowScopeElement, [...rowElementsByScope.get(rowScopeElement) || [], rowElement]);
263
+ });
264
+ rowElements.forEach((rowElement) => {
208
265
  const rowId = rowElement.dataset.gridRowId;
209
266
  if (!rowId) {
210
267
  return;
211
268
  }
212
269
  const rowRect = toRect(rowElement.getBoundingClientRect());
213
- if (rowIndex === 0) {
270
+ const rowPath = parseLayoutPath(rowElement.dataset.gridPath) || [{ rowId }];
271
+ const rowScopeElement = getRowScopeElement(rowElement);
272
+ const rowScopeRect = toRect(rowScopeElement.getBoundingClientRect());
273
+ const rowElementsInScope = rowElementsByScope.get(rowScopeElement) || [];
274
+ if (rowElementsInScope[0] === rowElement) {
214
275
  slots.push({
215
276
  type: "row-gap",
216
277
  targetRowId: rowId,
217
278
  position: "above",
218
- rect: createRowGapRect(rowRect, "above", containerRect)
279
+ rect: createRowGapRect(rowRect, "above", rowScopeRect),
280
+ path: rowPath
219
281
  });
220
282
  }
221
283
  const columnElements = Array.from(
222
- container.querySelectorAll(`[data-grid-column-row-id="${rowId}"][data-grid-column-index]`)
223
- );
284
+ layoutContainer.querySelectorAll(`[data-grid-column-row-id="${rowId}"][data-grid-column-index]`)
285
+ ).filter((el) => {
286
+ return el.closest("[data-grid-row-id]") === rowElement;
287
+ });
224
288
  const sortedColumns = columnElements.sort((a, b) => {
225
289
  const indexA = Number(a.dataset.gridColumnIndex || 0);
226
290
  const indexB = Number(b.dataset.gridColumnIndex || 0);
@@ -229,34 +293,43 @@ const buildLayoutSnapshot = /* @__PURE__ */ __name(({ container }) => {
229
293
  sortedColumns.forEach((columnElement) => {
230
294
  const columnIndex = Number(columnElement.dataset.gridColumnIndex || 0);
231
295
  const columnRect = toRect(columnElement.getBoundingClientRect());
296
+ const columnPath = parseLayoutPath(columnElement.dataset.gridPath) || createLegacyCellPath(rowId, columnIndex);
232
297
  slots.push({
233
298
  type: "column-edge",
234
299
  rowId,
235
300
  columnIndex,
236
301
  direction: "left",
237
- rect: createColumnEdgeRect(columnRect, "left")
302
+ rect: createColumnEdgeRect(columnRect, "left"),
303
+ path: columnPath
238
304
  });
239
305
  slots.push({
240
306
  type: "column-edge",
241
307
  rowId,
242
308
  columnIndex,
243
309
  direction: "right",
244
- rect: createColumnEdgeRect(columnRect, "right")
310
+ rect: createColumnEdgeRect(columnRect, "right"),
311
+ path: columnPath
245
312
  });
246
313
  const itemElements = Array.from(
247
314
  columnElement.querySelectorAll(`[data-grid-item-row-id="${rowId}"][data-grid-column-index="${columnIndex}"]`)
248
- );
315
+ ).filter((el) => {
316
+ return el.closest("[data-grid-column-row-id][data-grid-column-index]") === columnElement;
317
+ });
249
318
  const sortedItems = itemElements.sort((a, b) => {
250
319
  const indexA = Number(a.dataset.gridItemIndex || 0);
251
320
  const indexB = Number(b.dataset.gridItemIndex || 0);
252
321
  return indexA - indexB;
253
322
  });
254
323
  if (sortedItems.length === 0) {
324
+ if (hasDirectNestedRows(columnElement)) {
325
+ return;
326
+ }
255
327
  slots.push({
256
328
  type: "empty-column",
257
329
  rowId,
258
330
  columnIndex,
259
- rect: expandColumnRect(columnRect)
331
+ rect: expandColumnRect(columnRect),
332
+ path: columnPath
260
333
  });
261
334
  return;
262
335
  }
@@ -267,17 +340,41 @@ const buildLayoutSnapshot = /* @__PURE__ */ __name(({ container }) => {
267
340
  columnIndex,
268
341
  insertIndex: 0,
269
342
  position: "before",
270
- rect: createColumnInsertRect(firstItemRect, "before")
343
+ rect: createColumnInsertRect(firstItemRect, "before"),
344
+ path: columnPath
271
345
  });
272
346
  sortedItems.forEach((itemElement, itemIndex) => {
273
347
  const itemRect = toRect(itemElement.getBoundingClientRect());
348
+ const itemPath = parseLayoutPath(itemElement.dataset.gridPath) || columnPath;
349
+ const itemUid = itemElement.dataset.gridItemUid || "";
350
+ slots.push({
351
+ type: "item-edge",
352
+ rowId,
353
+ columnIndex,
354
+ itemIndex,
355
+ itemUid,
356
+ direction: "left",
357
+ rect: createColumnEdgeRect(itemRect, "left"),
358
+ path: itemPath
359
+ });
360
+ slots.push({
361
+ type: "item-edge",
362
+ rowId,
363
+ columnIndex,
364
+ itemIndex,
365
+ itemUid,
366
+ direction: "right",
367
+ rect: createColumnEdgeRect(itemRect, "right"),
368
+ path: itemPath
369
+ });
274
370
  slots.push({
275
371
  type: "column",
276
372
  rowId,
277
373
  columnIndex,
278
374
  insertIndex: itemIndex + 1,
279
375
  position: "after",
280
- rect: createColumnInsertRect(itemRect, "after")
376
+ rect: createColumnInsertRect(itemRect, "after"),
377
+ path: itemPath
281
378
  });
282
379
  });
283
380
  });
@@ -285,7 +382,8 @@ const buildLayoutSnapshot = /* @__PURE__ */ __name(({ container }) => {
285
382
  type: "row-gap",
286
383
  targetRowId: rowId,
287
384
  position: "below",
288
- rect: createRowGapRect(rowRect, "below", containerRect)
385
+ rect: createRowGapRect(rowRect, "below", rowScopeRect),
386
+ path: rowPath
289
387
  });
290
388
  });
291
389
  return {
@@ -305,6 +403,8 @@ const getSlotKey = /* @__PURE__ */ __name((slot) => {
305
403
  return `${slot.type}`;
306
404
  case "empty-column":
307
405
  return `${slot.type}:${slot.rowId}:${slot.columnIndex}`;
406
+ case "item-edge":
407
+ return `${slot.type}:${slot.rowId}:${slot.columnIndex}:${slot.itemUid}:${slot.direction}`;
308
408
  }
309
409
  }, "getSlotKey");
310
410
  const isPointInsideRect = /* @__PURE__ */ __name((point, rect) => {
@@ -315,19 +415,38 @@ const distanceToRect = /* @__PURE__ */ __name((point, rect) => {
315
415
  const dy = Math.max(rect.top - point.y, 0, point.y - (rect.top + rect.height));
316
416
  return Math.sqrt(dx * dx + dy * dy);
317
417
  }, "distanceToRect");
418
+ const slotPriority = {
419
+ "item-edge": 5,
420
+ "column-edge": 4,
421
+ column: 3,
422
+ "row-gap": 2,
423
+ "empty-column": 1,
424
+ "empty-row": 0
425
+ };
318
426
  const resolveDropIntent = /* @__PURE__ */ __name((point, slots) => {
319
427
  if (!slots.length) {
320
428
  return null;
321
429
  }
322
- const insideSlot = slots.find((slot) => isPointInsideRect(point, slot.rect));
323
- if (insideSlot) {
324
- return insideSlot;
430
+ let bestInsideSlot = null;
431
+ let bestInsidePriority = Number.NEGATIVE_INFINITY;
432
+ slots.forEach((slot) => {
433
+ if (!isPointInsideRect(point, slot.rect)) {
434
+ return;
435
+ }
436
+ const priority = slotPriority[slot.type];
437
+ if (priority > bestInsidePriority) {
438
+ bestInsidePriority = priority;
439
+ bestInsideSlot = slot;
440
+ }
441
+ });
442
+ if (bestInsideSlot) {
443
+ return bestInsideSlot;
325
444
  }
326
445
  let closest = null;
327
446
  let minDistance = Number.POSITIVE_INFINITY;
328
447
  slots.forEach((slot) => {
329
448
  const distance = distanceToRect(point, slot.rect);
330
- if (distance < minDistance) {
449
+ if (distance < minDistance || distance === minDistance && closest && slotPriority[slot.type] > slotPriority[closest.type]) {
331
450
  minDistance = distance;
332
451
  closest = slot;
333
452
  }
@@ -377,7 +496,10 @@ const toIntSizes = /* @__PURE__ */ __name((weights, count) => {
377
496
  if (count === 0) {
378
497
  return [];
379
498
  }
380
- const normalizedWeights = weights.map((weight) => Number.isFinite(weight) && weight > 0 ? weight : 1);
499
+ const normalizedWeights = Array.from({ length: count }, (_2, index) => {
500
+ const weight = weights[index];
501
+ return Number.isFinite(weight) && weight > 0 ? weight : 1;
502
+ });
381
503
  const total = normalizedWeights.reduce((sum, weight) => sum + weight, 0) || count;
382
504
  const ratios = normalizedWeights.map((weight) => weight / total);
383
505
  const raw = ratios.map((ratio) => ratio * DEFAULT_GRID_COLUMNS);
@@ -410,6 +532,234 @@ const toIntSizes = /* @__PURE__ */ __name((weights, count) => {
410
532
  }
411
533
  return floors;
412
534
  }, "toIntSizes");
535
+ const EMPTY_COLUMN_VALUE = "EMPTY_COLUMN";
536
+ const createTopLevelRow = /* @__PURE__ */ __name((itemUid, id) => ({
537
+ id,
538
+ cells: [
539
+ {
540
+ id: `${id}:cell:0`,
541
+ items: [itemUid]
542
+ }
543
+ ],
544
+ sizes: [DEFAULT_GRID_COLUMNS]
545
+ }), "createTopLevelRow");
546
+ const convertLegacyRowsToLayout = /* @__PURE__ */ __name((rows = {}, sizes = {}, rowOrder) => {
547
+ const order = deriveRowOrder(rows, rowOrder);
548
+ return {
549
+ version: 2,
550
+ rows: order.map((rowId) => {
551
+ const cells = (rows[rowId] || []).map((items, columnIndex) => ({
552
+ id: `${rowId}:cell:${columnIndex}`,
553
+ items: [...items]
554
+ }));
555
+ return {
556
+ id: rowId,
557
+ cells,
558
+ sizes: toIntSizes(sizes[rowId] || new Array(cells.length).fill(1), cells.length)
559
+ };
560
+ }).filter((row) => row.cells.length > 0)
561
+ };
562
+ }, "convertLegacyRowsToLayout");
563
+ const collectCellItems = /* @__PURE__ */ __name((cell) => {
564
+ if (Array.isArray(cell.items)) {
565
+ return cell.items;
566
+ }
567
+ return (cell.rows || []).flatMap((row) => row.cells.flatMap((childCell) => collectCellItems(childCell)));
568
+ }, "collectCellItems");
569
+ const projectLayoutToLegacyRows = /* @__PURE__ */ __name((layout) => {
570
+ const rows = {};
571
+ const sizes = {};
572
+ const rowOrder = [];
573
+ const appendRows = /* @__PURE__ */ __name((sourceRows, prefix = "") => {
574
+ sourceRows.forEach((row) => {
575
+ const rowId = prefix ? `${prefix}/${row.id}` : row.id;
576
+ const cells = row.cells.map((cell) => collectCellItems(cell)).filter((items) => items.length > 0);
577
+ if (cells.length > 0) {
578
+ rows[rowId] = cells;
579
+ sizes[rowId] = toIntSizes(row.sizes || new Array(cells.length).fill(1), cells.length);
580
+ rowOrder.push(rowId);
581
+ }
582
+ });
583
+ }, "appendRows");
584
+ appendRows(layout.rows || []);
585
+ return { rows, sizes, rowOrder, layout };
586
+ }, "projectLayoutToLegacyRows");
587
+ const normalizeGridRows = /* @__PURE__ */ __name((rows, options) => {
588
+ return (Array.isArray(rows) ? rows : []).map((row, rowIndex) => {
589
+ const rawCells = Array.isArray(row == null ? void 0 : row.cells) ? row.cells : [];
590
+ const rawSizes = Array.isArray(row == null ? void 0 : row.sizes) ? row.sizes : [];
591
+ const cellsWithSizes = rawCells.map((cell, cellIndex) => {
592
+ const id = typeof (cell == null ? void 0 : cell.id) === "string" && cell.id ? cell.id : `${(row == null ? void 0 : row.id) || `row:${rowIndex}`}:cell:${cellIndex}`;
593
+ const size = rawSizes[cellIndex];
594
+ if (Array.isArray(cell == null ? void 0 : cell.rows) && cell.rows.length > 0) {
595
+ const childRows = normalizeGridRows(cell.rows, options);
596
+ if (childRows.length > 0) {
597
+ return { cell: { id, rows: childRows }, size };
598
+ }
599
+ }
600
+ const rawItems = Array.isArray(cell == null ? void 0 : cell.items) ? cell.items : void 0;
601
+ const items = (rawItems || []).filter((itemUid) => typeof itemUid === "string" && itemUid).filter((itemUid) => !options.validUids || options.validUids.has(itemUid) || itemUid === EMPTY_COLUMN_VALUE).filter((itemUid) => {
602
+ if (itemUid === EMPTY_COLUMN_VALUE) {
603
+ return true;
604
+ }
605
+ if (options.seenUids.has(itemUid)) {
606
+ return false;
607
+ }
608
+ options.seenUids.add(itemUid);
609
+ return true;
610
+ });
611
+ if (rawItems && (items.length > 0 || rawItems.length === 0)) {
612
+ return { cell: { id, items }, size };
613
+ }
614
+ return null;
615
+ }).filter(Boolean);
616
+ const cells = cellsWithSizes.map((entry) => entry.cell);
617
+ if (cells.length === 0) {
618
+ return null;
619
+ }
620
+ return {
621
+ id: typeof (row == null ? void 0 : row.id) === "string" && row.id ? row.id : `row:${rowIndex}`,
622
+ cells,
623
+ sizes: toIntSizes(
624
+ cellsWithSizes.map((entry) => entry.size),
625
+ cells.length
626
+ )
627
+ };
628
+ }).filter(Boolean);
629
+ }, "normalizeGridRows");
630
+ const collapseCell = /* @__PURE__ */ __name((cell) => {
631
+ var _a;
632
+ if ((_a = cell.rows) == null ? void 0 : _a.length) {
633
+ const rows = collapseRows(cell.rows);
634
+ if (rows.length === 0) {
635
+ return null;
636
+ }
637
+ if (rows.length === 1 && rows[0].cells.length === 1 && rows[0].sizes[0] === DEFAULT_GRID_COLUMNS) {
638
+ return {
639
+ id: cell.id,
640
+ ...import_lodash.default.omit(rows[0].cells[0], ["id"])
641
+ };
642
+ }
643
+ return { id: cell.id, rows };
644
+ }
645
+ if (Array.isArray(cell.items)) {
646
+ return { id: cell.id, items: cell.items.filter(Boolean) };
647
+ }
648
+ return null;
649
+ }, "collapseCell");
650
+ const collapseRows = /* @__PURE__ */ __name((rows) => {
651
+ return rows.map((row) => {
652
+ const cellsWithSizes = row.cells.map((cell, index) => {
653
+ var _a;
654
+ const collapsed = collapseCell(cell);
655
+ return collapsed ? { cell: collapsed, size: (_a = row.sizes) == null ? void 0 : _a[index] } : null;
656
+ }).filter(Boolean);
657
+ const cells = cellsWithSizes.map((entry) => entry.cell);
658
+ if (cells.length === 0) {
659
+ return null;
660
+ }
661
+ return {
662
+ id: row.id,
663
+ cells,
664
+ sizes: toIntSizes(
665
+ cellsWithSizes.map((entry) => entry.size),
666
+ cells.length
667
+ )
668
+ };
669
+ }).filter(Boolean);
670
+ }, "collapseRows");
671
+ const normalizeGridLayout = /* @__PURE__ */ __name(({
672
+ layout,
673
+ rows,
674
+ sizes,
675
+ rowOrder,
676
+ itemUids,
677
+ generateId = import_shared.uid,
678
+ logger,
679
+ gridUid
680
+ }) => {
681
+ var _a;
682
+ const validUids = itemUids ? new Set(itemUids) : void 0;
683
+ if (validUids) {
684
+ validUids.add(EMPTY_COLUMN_VALUE);
685
+ }
686
+ try {
687
+ const source = (layout == null ? void 0 : layout.version) === 2 ? import_lodash.default.cloneDeep(layout) : convertLegacyRowsToLayout(rows || {}, sizes || {}, rowOrder || Object.keys(rows || {}));
688
+ const seenUids = /* @__PURE__ */ new Set();
689
+ const next = {
690
+ version: 2,
691
+ rows: collapseRows(
692
+ normalizeGridRows(source.rows || [], {
693
+ validUids,
694
+ seenUids
695
+ })
696
+ )
697
+ };
698
+ if (itemUids == null ? void 0 : itemUids.length) {
699
+ itemUids.forEach((itemUid) => {
700
+ if (itemUid === EMPTY_COLUMN_VALUE || seenUids.has(itemUid)) {
701
+ return;
702
+ }
703
+ const rowId = generateId();
704
+ next.rows.push(createTopLevelRow(itemUid, rowId));
705
+ seenUids.add(itemUid);
706
+ });
707
+ }
708
+ return next;
709
+ } catch (error) {
710
+ (_a = logger == null ? void 0 : logger.warn) == null ? void 0 : _a.call(logger, `[GridModel] Failed to normalize grid layout${gridUid ? ` (${gridUid})` : ""}.`, error);
711
+ return {
712
+ version: 2,
713
+ rows: (itemUids || []).filter((itemUid) => itemUid !== EMPTY_COLUMN_VALUE).map((itemUid) => createTopLevelRow(itemUid, generateId()))
714
+ };
715
+ }
716
+ }, "normalizeGridLayout");
717
+ const replaceUidInGridLayout = /* @__PURE__ */ __name((layout, fromUid, toUid) => {
718
+ const replaceRows = /* @__PURE__ */ __name((rows) => rows.map((row) => ({
719
+ ...row,
720
+ cells: row.cells.map((cell) => {
721
+ if (cell.rows) {
722
+ return { ...cell, rows: replaceRows(cell.rows) };
723
+ }
724
+ return {
725
+ ...cell,
726
+ items: (cell.items || []).map((itemUid) => itemUid === fromUid ? toUid : itemUid)
727
+ };
728
+ })
729
+ })), "replaceRows");
730
+ return { version: 2, rows: replaceRows(import_lodash.default.cloneDeep(layout.rows || [])) };
731
+ }, "replaceUidInGridLayout");
732
+ const findModelUidLayoutPosition = /* @__PURE__ */ __name((layout, uidValue) => {
733
+ const visitRows = /* @__PURE__ */ __name((rows, parentPath) => {
734
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
735
+ const row = rows[rowIndex];
736
+ for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex += 1) {
737
+ const cell = row.cells[cellIndex];
738
+ const path = [...parentPath, { rowId: row.id, cellId: cell.id }];
739
+ if (cell.items) {
740
+ const itemIndex = cell.items.indexOf(uidValue);
741
+ if (itemIndex !== -1) {
742
+ return {
743
+ path,
744
+ rowIndex,
745
+ cellIndex,
746
+ itemIndex,
747
+ itemUid: uidValue
748
+ };
749
+ }
750
+ }
751
+ if (cell.rows) {
752
+ const result = visitRows(cell.rows, path);
753
+ if (result) {
754
+ return result;
755
+ }
756
+ }
757
+ }
758
+ }
759
+ return null;
760
+ }, "visitRows");
761
+ return visitRows(layout.rows || [], []);
762
+ }, "findModelUidLayoutPosition");
413
763
  const normalizeRowSizes = /* @__PURE__ */ __name((rowId, layout) => {
414
764
  const columns = layout.rows[rowId];
415
765
  if (!columns || columns.length === 0) {
@@ -451,12 +801,237 @@ const distributeSizesWithNewColumn = /* @__PURE__ */ __name((sizes, insertIndex,
451
801
  weights.splice(insertIndex, 0, reference);
452
802
  return toIntSizes(weights, columnCount);
453
803
  }, "distributeSizesWithNewColumn");
804
+ const resolveCellPath = /* @__PURE__ */ __name((layout, slot) => {
805
+ var _a;
806
+ if ("path" in slot && ((_a = slot.path) == null ? void 0 : _a.length)) {
807
+ return slot.path;
808
+ }
809
+ if ("rowId" in slot && "columnIndex" in slot) {
810
+ return createLegacyCellPath(slot.rowId, slot.columnIndex);
811
+ }
812
+ return void 0;
813
+ }, "resolveCellPath");
814
+ const findRowListByPath = /* @__PURE__ */ __name((layout, path) => {
815
+ if (!path || path.length <= 1) {
816
+ return layout.rows;
817
+ }
818
+ let rows = layout.rows;
819
+ for (let i = 0; i < path.length - 1; i += 1) {
820
+ const entry = path[i];
821
+ const row = rows.find((candidate) => candidate.id === entry.rowId);
822
+ const cell = row == null ? void 0 : row.cells.find((candidate) => candidate.id === entry.cellId);
823
+ if (!(cell == null ? void 0 : cell.rows)) {
824
+ return rows;
825
+ }
826
+ rows = cell.rows;
827
+ }
828
+ return rows;
829
+ }, "findRowListByPath");
830
+ const findCellByPath = /* @__PURE__ */ __name((layout, path) => {
831
+ if (!(path == null ? void 0 : path.length)) {
832
+ return null;
833
+ }
834
+ let rows = layout.rows;
835
+ for (let i = 0; i < path.length; i += 1) {
836
+ const entry = path[i];
837
+ const rowIndex = rows.findIndex((candidate) => candidate.id === entry.rowId);
838
+ const row = rows[rowIndex];
839
+ if (!row || !entry.cellId) {
840
+ return null;
841
+ }
842
+ const cellIndex = row.cells.findIndex((candidate) => candidate.id === entry.cellId);
843
+ const cell = row.cells[cellIndex];
844
+ if (!cell) {
845
+ return null;
846
+ }
847
+ if (i === path.length - 1) {
848
+ return { rows, row, cell, rowIndex, cellIndex };
849
+ }
850
+ rows = cell.rows || [];
851
+ }
852
+ return null;
853
+ }, "findCellByPath");
854
+ const removeItemFromGridLayout = /* @__PURE__ */ __name((layout, sourceUid) => {
855
+ const removeFromRows = /* @__PURE__ */ __name((rows) => rows.map((row) => {
856
+ const cellsWithSizes = row.cells.map((cell, index) => {
857
+ var _a, _b;
858
+ if (cell.rows) {
859
+ const childRows = removeFromRows(cell.rows);
860
+ return childRows.length ? { cell: { ...cell, rows: childRows }, size: (_a = row.sizes) == null ? void 0 : _a[index] } : null;
861
+ }
862
+ const currentItems = cell.items || [];
863
+ const hadSourceUid = currentItems.includes(sourceUid);
864
+ const items = currentItems.filter((itemUid) => itemUid !== sourceUid);
865
+ if (hadSourceUid && !items.length) {
866
+ return null;
867
+ }
868
+ return { cell: { ...cell, items }, size: (_b = row.sizes) == null ? void 0 : _b[index] };
869
+ }).filter(Boolean);
870
+ const cells = cellsWithSizes.map((entry) => entry.cell);
871
+ return cells.length ? {
872
+ ...row,
873
+ cells,
874
+ sizes: toIntSizes(
875
+ cellsWithSizes.map((entry) => entry.size),
876
+ cells.length
877
+ )
878
+ } : null;
879
+ }).filter(Boolean), "removeFromRows");
880
+ layout.rows = collapseRows(removeFromRows(layout.rows));
881
+ }, "removeItemFromGridLayout");
882
+ const createSingleCellRow = /* @__PURE__ */ __name((itemUid, rowId, cellId) => ({
883
+ id: rowId,
884
+ cells: [{ id: cellId, items: [itemUid] }],
885
+ sizes: [DEFAULT_GRID_COLUMNS]
886
+ }), "createSingleCellRow");
887
+ const getGeneratedId = /* @__PURE__ */ __name((key, options) => {
888
+ var _a, _b, _c;
889
+ const existing = (_a = options == null ? void 0 : options.generatedIds) == null ? void 0 : _a.get(key);
890
+ if (existing) {
891
+ return existing;
892
+ }
893
+ const value = ((_b = options == null ? void 0 : options.generateId) == null ? void 0 : _b.call(options, key)) || (0, import_shared.uid)();
894
+ (_c = options == null ? void 0 : options.generatedIds) == null ? void 0 : _c.set(key, value);
895
+ return value;
896
+ }, "getGeneratedId");
897
+ const simulateGridLayoutForSlot = /* @__PURE__ */ __name(({
898
+ slot,
899
+ sourceUid,
900
+ layout,
901
+ generatedIds,
902
+ generateId
903
+ }) => {
904
+ const original = normalizeGridLayout({
905
+ layout: layout.layout,
906
+ rows: layout.rows,
907
+ sizes: layout.sizes,
908
+ rowOrder: layout.rowOrder
909
+ });
910
+ const cloned = import_lodash.default.cloneDeep(original);
911
+ const slotKey = getSlotKey(slot);
912
+ const sourcePosition = findModelUidLayoutPosition(cloned, sourceUid);
913
+ if (slot.type === "item-edge" && slot.itemUid === sourceUid) {
914
+ return cloned;
915
+ }
916
+ const targetPath = resolveCellPath(cloned, slot);
917
+ const targetItemUid = slot.type === "item-edge" ? slot.itemUid : void 0;
918
+ removeItemFromGridLayout(cloned, sourceUid);
919
+ switch (slot.type) {
920
+ case "column": {
921
+ const target = findCellByPath(cloned, targetPath);
922
+ if (!target) {
923
+ break;
924
+ }
925
+ if (target.cell.rows) {
926
+ target.cell.rows.push(
927
+ createTopLevelRow(sourceUid, getGeneratedId(`${slotKey}:row`, { generatedIds, generateId }))
928
+ );
929
+ break;
930
+ }
931
+ target.cell.items ||= [];
932
+ const insertIndex = Math.max(0, Math.min(slot.insertIndex, target.cell.items.length));
933
+ target.cell.items.splice(insertIndex, 0, sourceUid);
934
+ break;
935
+ }
936
+ case "empty-column": {
937
+ const target = findCellByPath(cloned, targetPath);
938
+ if (target) {
939
+ delete target.cell.rows;
940
+ target.cell.items = [sourceUid];
941
+ }
942
+ break;
943
+ }
944
+ case "column-edge": {
945
+ const target = findCellByPath(cloned, targetPath);
946
+ if (!target) {
947
+ break;
948
+ }
949
+ const insertIndex = slot.direction === "left" ? target.cellIndex : target.cellIndex + 1;
950
+ const cellId = getGeneratedId(`${slotKey}:cell`, { generatedIds, generateId });
951
+ target.row.cells.splice(insertIndex, 0, { id: cellId, items: [sourceUid] });
952
+ target.row.sizes = distributeSizesWithNewColumn(target.row.sizes, insertIndex, target.row.cells.length);
953
+ break;
954
+ }
955
+ case "row-gap": {
956
+ const rows = findRowListByPath(cloned, slot.path);
957
+ const targetIndex = rows.findIndex((row) => row.id === slot.targetRowId);
958
+ const insertIndex = targetIndex === -1 ? rows.length : slot.position === "above" ? targetIndex : targetIndex + 1;
959
+ const rowId = getGeneratedId(`${slotKey}:row`, { generatedIds, generateId });
960
+ rows.splice(insertIndex, 0, createSingleCellRow(sourceUid, rowId, `${rowId}:cell:0`));
961
+ break;
962
+ }
963
+ case "empty-row": {
964
+ const rowId = getGeneratedId(`${slotKey}:row`, { generatedIds, generateId });
965
+ cloned.rows.push(createSingleCellRow(sourceUid, rowId, `${rowId}:cell:0`));
966
+ break;
967
+ }
968
+ case "item-edge": {
969
+ if (!targetItemUid) {
970
+ break;
971
+ }
972
+ const target = findCellByPath(cloned, targetPath);
973
+ if (!(target == null ? void 0 : target.cell.items)) {
974
+ break;
975
+ }
976
+ const targetIndex = target.cell.items.indexOf(targetItemUid);
977
+ if (targetIndex === -1) {
978
+ break;
979
+ }
980
+ const rows = [];
981
+ target.cell.items.forEach((itemUid, index) => {
982
+ if (index === targetIndex) {
983
+ const rowId2 = getGeneratedId(`${slotKey}:target-row`, { generatedIds, generateId });
984
+ const leftItem = slot.direction === "left" ? sourceUid : itemUid;
985
+ const rightItem = slot.direction === "left" ? itemUid : sourceUid;
986
+ rows.push({
987
+ id: rowId2,
988
+ cells: [
989
+ { id: getGeneratedId(`${slotKey}:target-cell:0`, { generatedIds, generateId }), items: [leftItem] },
990
+ { id: getGeneratedId(`${slotKey}:target-cell:1`, { generatedIds, generateId }), items: [rightItem] }
991
+ ],
992
+ sizes: [12, 12]
993
+ });
994
+ return;
995
+ }
996
+ const rowId = getGeneratedId(`${slotKey}:row:${index}`, { generatedIds, generateId });
997
+ rows.push(createSingleCellRow(itemUid, rowId, `${rowId}:cell:0`));
998
+ });
999
+ delete target.cell.items;
1000
+ target.cell.rows = rows;
1001
+ break;
1002
+ }
1003
+ default:
1004
+ break;
1005
+ }
1006
+ const normalized = normalizeGridLayout({ layout: cloned });
1007
+ if (sourcePosition && isSameGridLayout(normalized, original)) {
1008
+ return original;
1009
+ }
1010
+ return normalized;
1011
+ }, "simulateGridLayoutForSlot");
1012
+ const isSameGridLayout = /* @__PURE__ */ __name((a, b) => {
1013
+ return JSON.stringify(a) === JSON.stringify(b);
1014
+ }, "isSameGridLayout");
454
1015
  const simulateLayoutForSlot = /* @__PURE__ */ __name(({
455
1016
  slot,
456
1017
  sourceUid,
457
1018
  layout,
458
- generateRowId
1019
+ generateRowId,
1020
+ generatedIds,
1021
+ generateId
459
1022
  }) => {
1023
+ var _a;
1024
+ if (layout.layout || slot.type === "item-edge" || "path" in slot && ((_a = slot.path) == null ? void 0 : _a.length)) {
1025
+ const nextLayout = simulateGridLayoutForSlot({
1026
+ slot,
1027
+ sourceUid,
1028
+ layout,
1029
+ generateRowId,
1030
+ generatedIds,
1031
+ generateId
1032
+ });
1033
+ return projectLayoutToLegacyRows(nextLayout);
1034
+ }
460
1035
  const cloned = {
461
1036
  rows: import_lodash.default.cloneDeep(layout.rows),
462
1037
  sizes: import_lodash.default.cloneDeep(layout.sizes),
@@ -543,7 +1118,12 @@ const simulateLayoutForSlot = /* @__PURE__ */ __name(({
543
1118
  MAX_SLOT_THICKNESS,
544
1119
  MIN_SLOT_THICKNESS,
545
1120
  buildLayoutSnapshot,
1121
+ findModelUidLayoutPosition,
546
1122
  getSlotKey,
1123
+ isSameGridLayout,
1124
+ normalizeGridLayout,
1125
+ projectLayoutToLegacyRows,
1126
+ replaceUidInGridLayout,
547
1127
  resolveDropIntent,
548
1128
  simulateLayoutForSlot
549
1129
  });