@juo/blocks 1.13.0 → 1.15.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.
Files changed (157) hide show
  1. package/dist/ExtensionRoot-C3lVJEBl.js +385 -0
  2. package/dist/block.d.ts.map +1 -1
  3. package/dist/block.devtools.test.d.ts +2 -0
  4. package/dist/block.devtools.test.d.ts.map +1 -0
  5. package/dist/{bridge-w5KQ-czl.js → bridge-xiKEzCGR.js} +146 -32
  6. package/dist/context.d.ts.map +1 -1
  7. package/dist/context.devtools.test.d.ts +2 -0
  8. package/dist/context.devtools.test.d.ts.map +1 -0
  9. package/dist/core.js +786 -37
  10. package/dist/{juo-workflow-BlG9CcAG.js → css-runtime-ClH0MEYr.js} +511 -618
  11. package/dist/devtools/env.d.ts +3 -0
  12. package/dist/devtools/env.d.ts.map +1 -0
  13. package/dist/devtools/hook.d.ts +40 -0
  14. package/dist/devtools/hook.d.ts.map +1 -0
  15. package/dist/devtools/index.d.ts +5 -0
  16. package/dist/devtools/index.d.ts.map +1 -0
  17. package/dist/devtools/instrumentation.d.ts +47 -0
  18. package/dist/devtools/instrumentation.d.ts.map +1 -0
  19. package/dist/devtools/protocol.d.ts +77 -0
  20. package/dist/devtools/protocol.d.ts.map +1 -0
  21. package/dist/devtools.js +11 -0
  22. package/dist/editor/bridge.d.ts +8 -0
  23. package/dist/editor/bridge.d.ts.map +1 -1
  24. package/dist/editor/canvas-mode-signal.d.ts +22 -0
  25. package/dist/editor/canvas-mode-signal.d.ts.map +1 -0
  26. package/dist/editor/canvas-mode.d.ts +15 -0
  27. package/dist/editor/canvas-mode.d.ts.map +1 -0
  28. package/dist/editor/canvas-mode.test.d.ts +2 -0
  29. package/dist/editor/canvas-mode.test.d.ts.map +1 -0
  30. package/dist/editor/components.d.ts +113 -0
  31. package/dist/editor/components.d.ts.map +1 -1
  32. package/dist/editor/components.test.d.ts.map +1 -1
  33. package/dist/editor/handler.d.ts.map +1 -1
  34. package/dist/editor/handler.test.d.ts +2 -0
  35. package/dist/editor/handler.test.d.ts.map +1 -0
  36. package/dist/editor/layout-command-emitter.d.ts +37 -0
  37. package/dist/editor/layout-command-emitter.d.ts.map +1 -0
  38. package/dist/editor/layout-command-emitter.test.d.ts +2 -0
  39. package/dist/editor/layout-command-emitter.test.d.ts.map +1 -0
  40. package/dist/editor/layout-system-signal.d.ts +21 -0
  41. package/dist/editor/layout-system-signal.d.ts.map +1 -0
  42. package/dist/editor/main.d.ts +1 -0
  43. package/dist/editor/main.d.ts.map +1 -1
  44. package/dist/editor/messages.d.ts +96 -3
  45. package/dist/editor/messages.d.ts.map +1 -1
  46. package/dist/editor/messages.test.d.ts +2 -0
  47. package/dist/editor/messages.test.d.ts.map +1 -0
  48. package/dist/editor/surface-context.d.ts +7 -0
  49. package/dist/editor/surface-context.d.ts.map +1 -0
  50. package/dist/editor/surface-context.test.d.ts +2 -0
  51. package/dist/editor/surface-context.test.d.ts.map +1 -0
  52. package/dist/editor/wrapper-shared.d.ts +31 -0
  53. package/dist/editor/wrapper-shared.d.ts.map +1 -0
  54. package/dist/editor.js +882 -57
  55. package/dist/gridstack-adapter-CDWbSpbt.js +5489 -0
  56. package/dist/index-JSv6UH_o.js +169 -0
  57. package/dist/layout/css-runtime.d.ts +41 -0
  58. package/dist/layout/css-runtime.d.ts.map +1 -0
  59. package/dist/layout/css-runtime.test.d.ts +2 -0
  60. package/dist/layout/css-runtime.test.d.ts.map +1 -0
  61. package/dist/layout/derive.d.ts +4 -0
  62. package/dist/layout/derive.d.ts.map +1 -0
  63. package/dist/layout/derive.test.d.ts +2 -0
  64. package/dist/layout/derive.test.d.ts.map +1 -0
  65. package/dist/layout/dividers.d.ts +34 -0
  66. package/dist/layout/dividers.d.ts.map +1 -0
  67. package/dist/layout/dividers.test.d.ts +2 -0
  68. package/dist/layout/dividers.test.d.ts.map +1 -0
  69. package/dist/layout/engine.d.ts +3 -0
  70. package/dist/layout/engine.d.ts.map +1 -0
  71. package/dist/layout/engine.test.d.ts +2 -0
  72. package/dist/layout/engine.test.d.ts.map +1 -0
  73. package/dist/layout/grid-utils.d.ts +57 -0
  74. package/dist/layout/grid-utils.d.ts.map +1 -0
  75. package/dist/layout/grid-utils.test.d.ts +2 -0
  76. package/dist/layout/grid-utils.test.d.ts.map +1 -0
  77. package/dist/layout/index.d.ts +13 -0
  78. package/dist/layout/index.d.ts.map +1 -0
  79. package/dist/layout/index.test.d.ts +2 -0
  80. package/dist/layout/index.test.d.ts.map +1 -0
  81. package/dist/layout/insert.d.ts +34 -0
  82. package/dist/layout/insert.d.ts.map +1 -0
  83. package/dist/layout/insert.test.d.ts +2 -0
  84. package/dist/layout/insert.test.d.ts.map +1 -0
  85. package/dist/layout/override.d.ts +10 -0
  86. package/dist/layout/override.d.ts.map +1 -0
  87. package/dist/layout/override.test.d.ts +2 -0
  88. package/dist/layout/override.test.d.ts.map +1 -0
  89. package/dist/layout/project-css.d.ts +6 -0
  90. package/dist/layout/project-css.d.ts.map +1 -0
  91. package/dist/layout/project-css.test.d.ts +2 -0
  92. package/dist/layout/project-css.test.d.ts.map +1 -0
  93. package/dist/layout/project-gridstack.d.ts +50 -0
  94. package/dist/layout/project-gridstack.d.ts.map +1 -0
  95. package/dist/layout/project-gridstack.test.d.ts +2 -0
  96. package/dist/layout/project-gridstack.test.d.ts.map +1 -0
  97. package/dist/layout/reconcile.d.ts +23 -0
  98. package/dist/layout/reconcile.d.ts.map +1 -0
  99. package/dist/layout/reconcile.test.d.ts +2 -0
  100. package/dist/layout/reconcile.test.d.ts.map +1 -0
  101. package/dist/layout/types.d.ts +118 -0
  102. package/dist/layout/types.d.ts.map +1 -0
  103. package/dist/layout-mode-adapter-BBd4JRQd.js +21 -0
  104. package/dist/main.d.ts +7 -1
  105. package/dist/main.d.ts.map +1 -1
  106. package/dist/morph-n4LMxOYo.js +182 -0
  107. package/dist/preact/block.d.ts.map +1 -1
  108. package/dist/preact.js +14 -10
  109. package/dist/project-gridstack-CM783oKI.js +52 -0
  110. package/dist/react/block.d.ts.map +1 -1
  111. package/dist/react.js +13 -9
  112. package/dist/registry.devtools.test.d.ts +2 -0
  113. package/dist/registry.devtools.test.d.ts.map +1 -0
  114. package/dist/theme-state.d.ts +16 -0
  115. package/dist/theme-state.d.ts.map +1 -1
  116. package/dist/theme-state.layout.test.d.ts +2 -0
  117. package/dist/theme-state.layout.test.d.ts.map +1 -0
  118. package/dist/theme-state.test.d.ts +2 -0
  119. package/dist/theme-state.test.d.ts.map +1 -0
  120. package/dist/vue/block.d.ts.map +1 -1
  121. package/dist/vue.js +22 -4
  122. package/dist/web-components/ExtensionRoot.d.ts +50 -2
  123. package/dist/web-components/ExtensionRoot.d.ts.map +1 -1
  124. package/dist/web-components/editor/ExtensionRoot.d.ts +11 -0
  125. package/dist/web-components/editor/ExtensionRoot.d.ts.map +1 -0
  126. package/dist/web-components/editor/ExtensionRoot.test.d.ts +2 -0
  127. package/dist/web-components/editor/ExtensionRoot.test.d.ts.map +1 -0
  128. package/dist/web-components/editor/InlineText.d.ts +1 -0
  129. package/dist/web-components/editor/InlineText.d.ts.map +1 -1
  130. package/dist/web-components/editor/editor-layout-controller.d.ts +28 -0
  131. package/dist/web-components/editor/editor-layout-controller.d.ts.map +1 -0
  132. package/dist/web-components/editor/gridstack-adapter.d.ts +3 -0
  133. package/dist/web-components/editor/gridstack-adapter.d.ts.map +1 -0
  134. package/dist/web-components/editor/gridstack-adapter.test.d.ts +2 -0
  135. package/dist/web-components/editor/gridstack-adapter.test.d.ts.map +1 -0
  136. package/dist/web-components/editor/morph.d.ts +32 -0
  137. package/dist/web-components/editor/morph.d.ts.map +1 -0
  138. package/dist/web-components/editor/morph.test.d.ts +2 -0
  139. package/dist/web-components/editor/morph.test.d.ts.map +1 -0
  140. package/dist/web-components/layout/layout-controller.d.ts +55 -0
  141. package/dist/web-components/layout/layout-controller.d.ts.map +1 -0
  142. package/dist/web-components/layout/view-layout-controller.d.ts +17 -0
  143. package/dist/web-components/layout/view-layout-controller.d.ts.map +1 -0
  144. package/dist/web-components/layout/view-layout-controller.test.d.ts +2 -0
  145. package/dist/web-components/layout/view-layout-controller.test.d.ts.map +1 -0
  146. package/dist/web-components/layout-grid.d.ts +47 -0
  147. package/dist/web-components/layout-grid.d.ts.map +1 -0
  148. package/dist/web-components/layout-grid.test.d.ts +2 -0
  149. package/dist/web-components/layout-grid.test.d.ts.map +1 -0
  150. package/dist/web-components/layout-mode-adapter.d.ts +57 -0
  151. package/dist/web-components/layout-mode-adapter.d.ts.map +1 -0
  152. package/dist/web-components/layout-mode-adapter.test.d.ts +2 -0
  153. package/dist/web-components/layout-mode-adapter.test.d.ts.map +1 -0
  154. package/dist/web-components-editor.js +130 -17
  155. package/dist/web-components-runtime.js +4 -3
  156. package/package.json +7 -2
  157. package/dist/ExtensionRoot-2xTGTu3K.js +0 -219
package/dist/editor.js CHANGED
@@ -1,9 +1,12 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { createBlockInstanceFromObject, indexOverrides, DEFAULT_GLOBAL_STYLES, effect, onRegisterBlock, getDefinedBlocks } from "@juo/blocks";
5
- import { a as createEditorBridge, f as createOpenAddBlockMenuMessage, g as createRemoveBlockMessage, h as createRegisterBlocksMessage, i as createRegisterRoutesMessage } from "./bridge-w5KQ-czl.js";
6
- import { j, k, b, l, m, n, o, p, c, q, r, s, t, u, e, v, w, x, y, d, z, A, B, C } from "./bridge-w5KQ-czl.js";
4
+ import { serializeBlocks, createBlockInstanceFromObject, setActiveSurfaceContext, indexOverrides, canvasMode, getActiveSurfaceContext, emitLayoutCommand, DEFAULT_GLOBAL_STYLES, effect, setLayoutCommandEmitter, setLayoutCommandDispatcher, onRegisterBlock, getDefinedBlocks, clearAllSurfaceContexts } from "@juo/blocks";
5
+ import { canvasMode as canvasMode2 } from "@juo/blocks";
6
+ import { L as LAYOUT_BLOCK_WRAPPER_TAG, a as createEditorBridge, b as createOpenAddBlockMenuMessage, d as createRemoveBlockMessage, i as isBlockWrapperTag, r as resolveOutlineTarget, e as getBlockLabel, f as createLayoutCommandMessage, h as createRegisterBlocksMessage, j as createRegisterRoutesMessage } from "./bridge-xiKEzCGR.js";
7
+ import { k, l, m, c, n, o, p, q, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, g, M, N } from "./bridge-xiKEzCGR.js";
8
+ import { s as setCanvasMode, e as enterLayout } from "./morph-n4LMxOYo.js";
9
+ import { r } from "./morph-n4LMxOYo.js";
7
10
  function createEditorHandler(options) {
8
11
  const { themeState, onBlockSelection } = options;
9
12
  const blockSurfaceIndex = /* @__PURE__ */ new Map();
@@ -53,19 +56,23 @@ function createEditorHandler(options) {
53
56
  return "page";
54
57
  }
55
58
  function handleMessage(message) {
56
- var _a, _b;
59
+ var _a;
57
60
  if (message.type === "SET_GLOBAL_STYLES") {
58
61
  themeState.setGlobalStyles(message.payload.styles);
59
62
  return;
60
63
  }
61
64
  if (message.type === "PROVIDE_THEME_STATE") {
62
65
  const surface = message.payload.surface ?? "page";
63
- const blocks = ((_a = message.payload.blocks) == null ? void 0 : _a.map(
64
- (block) => createBlockInstanceFromObject(block)
65
- )) || [];
66
66
  const target = getBlocks(surface);
67
- target.value = blocks;
68
- indexSurface(surface, target.value);
67
+ const incoming = message.payload.blocks ?? [];
68
+ const unchanged = JSON.stringify(serializeBlocks(target.value)) === JSON.stringify(incoming);
69
+ if (!unchanged) {
70
+ target.value = incoming.map(
71
+ (block) => createBlockInstanceFromObject(block)
72
+ );
73
+ indexSurface(surface, target.value);
74
+ }
75
+ setActiveSurfaceContext(surface, message.payload.id);
69
76
  } else if (message.type === "SELECT_BLOCK") {
70
77
  const blockId = message.payload.blockId;
71
78
  if (onBlockSelection) {
@@ -138,12 +145,12 @@ function createEditorHandler(options) {
138
145
  for (const slotKey of Object.keys(blocks[i].slots || {})) {
139
146
  const slotBlocks = blocks[i].slots[slotKey];
140
147
  const slotArray = Array.isArray(slotBlocks) ? slotBlocks : [slotBlocks];
141
- for (let j2 = 0; j2 < slotArray.length; j2++) {
142
- if (typeof slotArray[j2] !== "string" && ((_a2 = slotArray[j2]) == null ? void 0 : _a2.id) === targetId) {
148
+ for (let j = 0; j < slotArray.length; j++) {
149
+ if (typeof slotArray[j] !== "string" && ((_a2 = slotArray[j]) == null ? void 0 : _a2.id) === targetId) {
143
150
  if (position === "before") {
144
- slotArray.splice(j2, 0, newBlock2);
151
+ slotArray.splice(j, 0, newBlock2);
145
152
  } else {
146
- slotArray.splice(j2 + 1, 0, newBlock2);
153
+ slotArray.splice(j + 1, 0, newBlock2);
147
154
  }
148
155
  blocks[i].slots[slotKey] = slotArray;
149
156
  return true;
@@ -204,7 +211,7 @@ function createEditorHandler(options) {
204
211
  const slotBlocks = blocks[i].slots[slot];
205
212
  const slotArray = Array.isArray(slotBlocks) ? slotBlocks : [slotBlocks];
206
213
  const index = slotArray.findIndex(
207
- (b2) => typeof b2 !== "string" && (b2 == null ? void 0 : b2.id) === blockId
214
+ (b) => typeof b !== "string" && (b == null ? void 0 : b.id) === blockId
208
215
  );
209
216
  if (index !== -1) {
210
217
  slotArray.splice(index, 1);
@@ -230,7 +237,7 @@ function createEditorHandler(options) {
230
237
  const target = getBlocks(surface);
231
238
  let resolvedParentId = message.payload.location.parentId;
232
239
  if (resolvedParentId === "root") {
233
- resolvedParentId = ((_b = target.value[0]) == null ? void 0 : _b.id) ?? "root";
240
+ resolvedParentId = ((_a = target.value[0]) == null ? void 0 : _a.id) ?? "root";
234
241
  }
235
242
  findAndRemoveBlock(
236
243
  target.value,
@@ -248,7 +255,7 @@ function createEditorHandler(options) {
248
255
  const slotBlocks = block.slots[slot];
249
256
  const slotArray = Array.isArray(slotBlocks) ? slotBlocks : [slotBlocks];
250
257
  const oldIndex = slotArray.findIndex(
251
- (b2) => typeof b2 !== "string" && (b2 == null ? void 0 : b2.id) === blockId
258
+ (b) => typeof b !== "string" && (b == null ? void 0 : b.id) === blockId
252
259
  );
253
260
  if (oldIndex !== -1) {
254
261
  const [moved] = slotArray.splice(oldIndex, 1);
@@ -282,6 +289,17 @@ function createEditorHandler(options) {
282
289
  );
283
290
  target.value = [...target.value];
284
291
  indexSurface(surface, target.value);
292
+ } else if (message.type === "SET_LAYOUTING_MODE") {
293
+ setCanvasMode(message.payload.enabled);
294
+ } else if (message.type === "PROVIDE_LAYOUT_DOCUMENT") {
295
+ const surface = message.payload.surface;
296
+ const target = surface === "overlay" ? themeState.overlay : themeState.page;
297
+ setActiveSurfaceContext(surface, message.payload.id);
298
+ const incoming = message.payload.layout;
299
+ const changed = JSON.stringify(target.layout.value) !== JSON.stringify(incoming);
300
+ if (changed) {
301
+ target.layout.value = incoming;
302
+ }
285
303
  }
286
304
  }
287
305
  indexSurface("page", themeState.page.blocks.value);
@@ -789,14 +807,39 @@ const addBlockIconSvg = `
789
807
  <path d="M7 4.77V9.23M4.77 7H9.23" stroke="white" stroke-width="1.14" stroke-linecap="round"/>
790
808
  </svg>
791
809
  `;
792
- const trashIconSvg = `
810
+ const layoutToggleSvg = `
793
811
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
794
- <path d="M2 4H12" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
795
- <path d="M5 4V2.5C5 2.22386 5.22386 2 5.5 2H8.5C8.77614 2 9 2.22386 9 2.5V4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
796
- <path d="M10.5 4L10 11.5C10 11.7761 9.77614 12 9.5 12H4.5C4.22386 12 4 11.7761 4 11.5L3.5 4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
797
- <line x1="7" y1="6.5" x2="7" y2="9.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
798
- <line x1="5.5" y1="6.5" x2="5.5" y2="9.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
799
- <line x1="8.5" y1="6.5" x2="8.5" y2="9.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
812
+ <rect x="8.5" y="0.5" width="5" height="7" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
813
+ <rect x="8.5" y="9.5" width="5" height="4" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
814
+ <rect x="0.5" y="0.5" width="5" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
815
+ </svg>
816
+ `;
817
+ const dragGripSvg = `
818
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
819
+ <circle cx="5" cy="3.5" r="1" fill="currentColor"/>
820
+ <circle cx="9" cy="3.5" r="1" fill="currentColor"/>
821
+ <circle cx="5" cy="7" r="1" fill="currentColor"/>
822
+ <circle cx="9" cy="7" r="1" fill="currentColor"/>
823
+ <circle cx="5" cy="10.5" r="1" fill="currentColor"/>
824
+ <circle cx="9" cy="10.5" r="1" fill="currentColor"/>
825
+ </svg>
826
+ `;
827
+ const addCircleOutlineSvg = `
828
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
829
+ <rect x="0.5" y="0.5" width="13" height="13" rx="6.5" stroke="currentColor"/>
830
+ <path d="M4.17133 7.0003L9.82818 7.0003" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
831
+ <path d="M7.00006 9.82843L7.00006 4.17157" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
832
+ </svg>
833
+ `;
834
+ const fillHorizontalSvg = `
835
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
836
+ <path d="M4.50013 4.11285L1.50013 7.00174L4.50013 9.89062M1.50013 7.00174L12.3647 7.00174" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
837
+ <path d="M9.50024 4.11328L12.5002 7.00217L9.50024 9.89106" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
838
+ </svg>
839
+ `;
840
+ const trashIconSvg = `
841
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
842
+ <path d="M11.375 2.39649V11.5C11.375 12.6046 10.4796 13.5 9.375 13.5H4.375C3.27043 13.5 2.375 12.6046 2.375 11.5V2.39649H4.375M11.375 2.39649H9.375M11.375 2.39649H13.125M9.375 2.39649V0.5H4.375V2.39649M9.375 2.39649H4.375M4.375 2.39649H0.875M5 4.06653V11.0665M7 4.06653V11.0665M9 4.06653V11.0665" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
800
843
  </svg>
801
844
  `;
802
845
  class BlockWrapper extends HTMLElement {
@@ -857,8 +900,8 @@ class BlockWrapper extends HTMLElement {
857
900
  const moreBtn = document.createElement("button");
858
901
  moreBtn.type = "button";
859
902
  moreBtn.innerHTML = moreIconSvg;
860
- moreBtn.addEventListener("click", (e2) => {
861
- e2.stopPropagation();
903
+ moreBtn.addEventListener("click", (e) => {
904
+ e.stopPropagation();
862
905
  this.toggleMenu();
863
906
  });
864
907
  const menuEl = document.createElement("div");
@@ -874,8 +917,8 @@ class BlockWrapper extends HTMLElement {
874
917
  deleteBtn.className = "block-wrapper__menu-item block-wrapper__menu-item--danger";
875
918
  deleteBtn.setAttribute("role", "menuitem");
876
919
  deleteBtn.innerHTML = `${trashIconSvg}<span>Delete</span>`;
877
- deleteBtn.addEventListener("click", (e2) => {
878
- e2.stopPropagation();
920
+ deleteBtn.addEventListener("click", (e) => {
921
+ e.stopPropagation();
879
922
  this.closeMenu();
880
923
  this.handleRemoveBlock();
881
924
  });
@@ -943,9 +986,9 @@ class BlockWrapper extends HTMLElement {
943
986
  surface.appendChild(text);
944
987
  inner.appendChild(surface);
945
988
  btn.appendChild(inner);
946
- btn.addEventListener("click", (e2) => {
947
- e2.stopPropagation();
948
- e2.preventDefault();
989
+ btn.addEventListener("click", (e) => {
990
+ e.stopPropagation();
991
+ e.preventDefault();
949
992
  this.handleAddBlock(position, btn);
950
993
  });
951
994
  return btn;
@@ -1051,8 +1094,8 @@ class BlockWrapper extends HTMLElement {
1051
1094
  if (!this.menuElement) return;
1052
1095
  this.menuElement.classList.add("open");
1053
1096
  this.isMenuOpen = true;
1054
- this.closeMenuOnOutsideClick = (e2) => {
1055
- if (!this.shadowRoot.contains(e2.composedPath()[0])) {
1097
+ this.closeMenuOnOutsideClick = (e) => {
1098
+ if (!this.shadowRoot.contains(e.composedPath()[0])) {
1056
1099
  this.closeMenu();
1057
1100
  }
1058
1101
  };
@@ -1130,6 +1173,761 @@ class BlockWrapper extends HTMLElement {
1130
1173
  if (customElements.get("juo-editor-block-wrapper") == null) {
1131
1174
  customElements.define("juo-editor-block-wrapper", BlockWrapper);
1132
1175
  }
1176
+ const layoutStyles = `
1177
+ <style>
1178
+ :host {
1179
+ display: block;
1180
+ box-sizing: border-box;
1181
+ /* Re-enable hit-testing (mirrors the legacy wrapper). */
1182
+ pointer-events: auto;
1183
+ }
1184
+
1185
+ .layout-block-wrapper {
1186
+ position: relative;
1187
+ box-sizing: border-box;
1188
+ }
1189
+
1190
+ /*
1191
+ * Grid-cell fill chain. The GridStack adapter tags the host with
1192
+ * data-juo-grid-cell; that gates an unbroken height:100% chain from the cell
1193
+ * down to the slotted block root so short blocks fill the cell the merchant
1194
+ * sized instead of top-aligning. In grid mode the slotted content is hidden
1195
+ * and the hardcoded skeleton (below) fills the cell instead.
1196
+ */
1197
+ :host([data-juo-grid-cell]),
1198
+ :host([data-juo-grid-cell]) .layout-block-wrapper,
1199
+ :host([data-juo-grid-cell]) .layout-block-wrapper__slot-host {
1200
+ height: 100%;
1201
+ }
1202
+
1203
+ :host([data-juo-grid-cell]) ::slotted([data-block-id]) {
1204
+ /* display:block so inline CE block hosts honour height:100% and fill the cell. */
1205
+ display: block;
1206
+ height: 100%;
1207
+ }
1208
+
1209
+ /*
1210
+ * Bare row divider in the SETTLED grid view (mirrors the customer portal).
1211
+ * Paints a hairline centred in the gap above each row leader; the divider gap
1212
+ * top margin grows only boundaries that show a divider. Both are driven by the
1213
+ * breakpoint-aware flags projectToCss sets on the light-DOM grid item
1214
+ * (--juo-divider-row / --juo-divider-line), which inherit across the shadow
1215
+ * boundary the same way --juo-layout-c / --juo-layout-cols already do — so the
1216
+ * divider follows the active breakpoint's row structure with no static
1217
+ * attribute. The unitless flag multiplies into the length: 0 collapses to 0px
1218
+ * (spacing) / a 0-height unpainted box (line). Gated with
1219
+ * :not([data-juo-grid-cell]) so nothing paints while dragging in layout mode
1220
+ * (where the projectToCss stylesheet is absent and the flags are unset anyway).
1221
+ */
1222
+ :host(:not([data-juo-grid-cell])) {
1223
+ margin-top: calc(var(--juo-layout-divider-gap, 0px) * var(--juo-divider-row, 0));
1224
+ }
1225
+
1226
+ :host(:not([data-juo-grid-cell])) .layout-block-wrapper::before {
1227
+ content: "";
1228
+ position: absolute;
1229
+ top: calc(-0.5 * (var(--juo-layout-gap, var(--card-divider-margin, 20px)) + var(--juo-layout-divider-gap, 0px)) - var(--card-divider-height, 1px) / 2);
1230
+ left: calc(-1 * var(--juo-layout-c, 0) * (100cqw + var(--juo-layout-gap, 0px)) / var(--juo-layout-cols, 12));
1231
+ width: 100cqw;
1232
+ height: calc(var(--card-divider-height, 1px) * var(--juo-divider-line, 0));
1233
+ background: var(--card-divider-background, #e5e5e5);
1234
+ pointer-events: none;
1235
+ }
1236
+
1237
+ /*
1238
+ * Layout-mode skeleton. Hidden while the settled view shows real content; in
1239
+ * grid (drag) mode the slot host is hidden and this shimmer placeholder fills
1240
+ * the cell so the canvas stays uniform while the merchant arranges blocks. The
1241
+ * block name is retained so each cell stays identifiable.
1242
+ */
1243
+ .layout-block-wrapper__skeleton {
1244
+ display: none;
1245
+ }
1246
+
1247
+ :host([data-juo-grid-cell]) .layout-block-wrapper__slot-host {
1248
+ display: none !important;
1249
+ }
1250
+
1251
+ :host([data-juo-grid-cell]) .layout-block-wrapper__skeleton {
1252
+ display: flex;
1253
+ flex-direction: column;
1254
+ gap: 14px;
1255
+ box-sizing: border-box;
1256
+ height: 100%;
1257
+ padding: 16px 18px;
1258
+ background: #ffffff;
1259
+ border: 1px solid #ececf1;
1260
+ border-radius: 10px;
1261
+ overflow: hidden;
1262
+ /* Ease the placeholder in when entering layout mode instead of snapping.
1263
+ * Long, gentle fade so the card doesn't appear aggressively. */
1264
+ animation: juo-skeleton-in 320ms cubic-bezier(0.22, 1, 0.36, 1) both;
1265
+ }
1266
+
1267
+ @keyframes juo-skeleton-in {
1268
+ from { opacity: 0; }
1269
+ to { opacity: 1; }
1270
+ }
1271
+
1272
+ /* Opacity-only (no translate) so bars breathe in softly rather than jumping. */
1273
+ @keyframes juo-skeleton-bar-in {
1274
+ from { opacity: 0; }
1275
+ to { opacity: 1; }
1276
+ }
1277
+
1278
+ .layout-block-wrapper__skeleton-head {
1279
+ display: flex;
1280
+ align-items: center;
1281
+ gap: 8px;
1282
+ flex-shrink: 0;
1283
+ }
1284
+
1285
+ .layout-block-wrapper__skeleton-icon {
1286
+ display: inline-flex;
1287
+ align-items: center;
1288
+ justify-content: center;
1289
+ color: #9a9aa6;
1290
+ }
1291
+
1292
+ .layout-block-wrapper__skeleton-title {
1293
+ font-family: inherit;
1294
+ font-size: 12px;
1295
+ font-weight: 600;
1296
+ letter-spacing: -0.04px;
1297
+ color: #6b6b76;
1298
+ white-space: nowrap;
1299
+ overflow: hidden;
1300
+ text-overflow: ellipsis;
1301
+ }
1302
+
1303
+ /*
1304
+ * Placeholder bars sit directly under the header with a fixed height and a
1305
+ * uniform gap, so every block's skeleton reads identically. The region grows
1306
+ * to the skeleton's padding box (flex: 1) but the bars stay top-aligned — the
1307
+ * count is intentionally sparse (recomputeSkeletonLines caps it) so the
1308
+ * skeleton reads as a light "block placeholder" rather than a wall of noise.
1309
+ * Per-bar widths are assigned in JS from a block-type-seeded sequence so
1310
+ * adjacent blocks don't share the same width rhythm.
1311
+ */
1312
+ .layout-block-wrapper__skeleton-lines {
1313
+ display: flex;
1314
+ flex: 1 1 auto;
1315
+ min-height: 0;
1316
+ flex-direction: column;
1317
+ justify-content: flex-start;
1318
+ gap: 12px;
1319
+ overflow: hidden;
1320
+ }
1321
+
1322
+ /* Flat placeholder bars — no shimmer so the layout-mode skeleton reads as a
1323
+ static "block placeholder" rather than a loading state. Width is set in JS
1324
+ (block-type-seeded); the value here is only a pre-measurement default. */
1325
+ .layout-block-wrapper__skeleton-line {
1326
+ width: 82%;
1327
+ height: 12px;
1328
+ flex-shrink: 0;
1329
+ border-radius: 4px;
1330
+ background-color: #ededf2;
1331
+ /* Newly added bars (first measure / resize) fade in softly, lightly
1332
+ * staggered (see nth-child delays) so they don't all snap in together. */
1333
+ animation: juo-skeleton-bar-in 360ms cubic-bezier(0.22, 1, 0.36, 1) both;
1334
+ }
1335
+
1336
+ .layout-block-wrapper__skeleton-line:nth-child(2) { animation-delay: 60ms; }
1337
+ .layout-block-wrapper__skeleton-line:nth-child(3) { animation-delay: 120ms; }
1338
+ .layout-block-wrapper__skeleton-line:nth-child(4) { animation-delay: 180ms; }
1339
+
1340
+ /*
1341
+ * Center hover toolbar — the block's affordances: layout toggle (leading grip
1342
+ * = morph trigger), add-below, fill, and delete. Shown on hover in BOTH view
1343
+ * mode and grid/layout mode (data-juo-grid-cell). Its vertical position is set
1344
+ * by JS (positionToolbar) to the midpoint of the block's VISIBLE portion so it
1345
+ * stays reachable on tall / partially-scrolled blocks; top:50% is the
1346
+ * fully-on-screen default before JS runs.
1347
+ */
1348
+ .layout-block-wrapper__toolbar {
1349
+ position: absolute;
1350
+ top: 50%;
1351
+ left: 50%;
1352
+ transform: translate(-50%, -50%);
1353
+ z-index: 104;
1354
+ display: none;
1355
+ align-items: center;
1356
+ gap: 6px;
1357
+ box-sizing: border-box;
1358
+ padding: 6px;
1359
+ border-radius: 10px;
1360
+ background: #f5f5f5;
1361
+ border: 1px solid #ffffff;
1362
+ box-shadow: 0 2px 3.5px rgba(0, 0, 0, 0.15);
1363
+ backdrop-filter: blur(17px);
1364
+ -webkit-backdrop-filter: blur(17px);
1365
+ opacity: 0;
1366
+ pointer-events: auto;
1367
+ transition: opacity 140ms ease;
1368
+ }
1369
+
1370
+ :host(:hover) .layout-block-wrapper__toolbar {
1371
+ display: inline-flex;
1372
+ opacity: 1;
1373
+ }
1374
+
1375
+ /* While GridStack is actively dragging / resizing this block, hide the toolbar
1376
+ so it doesn't overlap or interfere with the block being moved. The grid
1377
+ adapter toggles data-juo-grid-interacting on the wrapper under interaction. */
1378
+ :host([data-juo-grid-interacting]) .layout-block-wrapper__toolbar {
1379
+ display: none !important;
1380
+ }
1381
+
1382
+ .layout-block-wrapper__tool-btn {
1383
+ display: inline-flex;
1384
+ align-items: center;
1385
+ justify-content: center;
1386
+ box-sizing: border-box;
1387
+ width: 24px;
1388
+ height: 24px;
1389
+ padding: 0;
1390
+ border: none;
1391
+ border-radius: 6px;
1392
+ background: transparent;
1393
+ color: #121212;
1394
+ cursor: pointer;
1395
+ transition: background 120ms ease;
1396
+ }
1397
+
1398
+ .layout-block-wrapper__tool-btn:hover {
1399
+ background: rgba(0, 0, 0, 0.06);
1400
+ }
1401
+
1402
+ .layout-block-wrapper__tool-btn:disabled {
1403
+ opacity: 0.4;
1404
+ cursor: default;
1405
+ }
1406
+
1407
+ .layout-block-wrapper__tool-btn:disabled:hover {
1408
+ background: transparent;
1409
+ }
1410
+
1411
+ .layout-block-wrapper__tool-btn svg {
1412
+ display: block;
1413
+ }
1414
+
1415
+ /* Leading position swaps between two buttons: the layout toggle in preview
1416
+ mode and the drag grip in layout mode (host tagged data-juo-grid-cell by the
1417
+ GridStack adapter). They are mutually exclusive so only one shows at a time. */
1418
+ .layout-block-wrapper__tool-btn--toggle {
1419
+ display: inline-flex;
1420
+ }
1421
+
1422
+ :host([data-juo-grid-cell]) .layout-block-wrapper__tool-btn--toggle {
1423
+ display: none;
1424
+ }
1425
+
1426
+ .layout-block-wrapper__tool-btn--grip {
1427
+ display: none;
1428
+ cursor: grab;
1429
+ touch-action: none;
1430
+ }
1431
+
1432
+ .layout-block-wrapper__tool-btn--grip:active {
1433
+ cursor: grabbing;
1434
+ }
1435
+
1436
+ :host([data-juo-grid-cell]) .layout-block-wrapper__tool-btn--grip {
1437
+ display: inline-flex;
1438
+ }
1439
+
1440
+ /* The fill toggle only makes sense while arranging, so it is hidden in the
1441
+ settled view and revealed only in layout mode (host tagged data-juo-grid-cell
1442
+ by the GridStack adapter). Add / delete stay visible in both modes. */
1443
+ .layout-block-wrapper__tool-btn--fill-h {
1444
+ display: none;
1445
+ }
1446
+
1447
+ :host([data-juo-grid-cell]) .layout-block-wrapper__tool-btn--fill-h {
1448
+ display: inline-flex;
1449
+ }
1450
+
1451
+ /* Single-column breakpoints (e.g. mobile, cols:1) support reordering but not
1452
+ width fill — there is only one lane — so hide the fill toggle while keeping
1453
+ the drag grip so blocks can still be reordered. Add/delete (tree edits)
1454
+ remain in both modes. */
1455
+ :host([data-juo-grid-single-col]) .layout-block-wrapper__tool-btn--fill-h {
1456
+ display: none;
1457
+ }
1458
+
1459
+ /* Active (pressed) state for the fill toggle — shown while the block spans the
1460
+ full row (data-juo-grid-full-width set on the host by the layout runtime). */
1461
+ :host([data-juo-grid-full-width]) .layout-block-wrapper__tool-btn--fill-h {
1462
+ background: #121212;
1463
+ color: #ffffff;
1464
+ }
1465
+
1466
+ :host([data-juo-grid-full-width]) .layout-block-wrapper__tool-btn--fill-h:hover {
1467
+ background: #121212;
1468
+ }
1469
+
1470
+ @media (prefers-reduced-motion: reduce) {
1471
+ .layout-block-wrapper__toolbar,
1472
+ .layout-block-wrapper__tool-btn {
1473
+ transition: none;
1474
+ }
1475
+
1476
+ .layout-block-wrapper__skeleton,
1477
+ .layout-block-wrapper__skeleton-line {
1478
+ animation: none;
1479
+ }
1480
+ }
1481
+ </style>
1482
+ `;
1483
+ const _LayoutBlockWrapper = class _LayoutBlockWrapper extends HTMLElement {
1484
+ constructor() {
1485
+ super();
1486
+ __publicField(this, "block", null);
1487
+ __publicField(this, "editorBridge", createEditorBridge());
1488
+ __publicField(this, "skeletonTitle", null);
1489
+ __publicField(this, "skeletonLines", null);
1490
+ __publicField(this, "skeletonResizeObserver", null);
1491
+ __publicField(this, "toolbar", null);
1492
+ __publicField(this, "toolbarRafId", 0);
1493
+ __publicField(this, "toolbarScrollListening", false);
1494
+ __publicField(this, "hoverOutlineTarget", null);
1495
+ __publicField(this, "selectionOutlineTarget", null);
1496
+ __publicField(this, "skeletonRafId", 0);
1497
+ __publicField(this, "scheduleSkeletonRecompute", () => {
1498
+ if (typeof requestAnimationFrame !== "function") {
1499
+ this.recomputeSkeletonLines();
1500
+ return;
1501
+ }
1502
+ if (this.skeletonRafId) return;
1503
+ this.skeletonRafId = requestAnimationFrame(() => {
1504
+ this.skeletonRafId = 0;
1505
+ this.recomputeSkeletonLines();
1506
+ });
1507
+ });
1508
+ /**
1509
+ * Pointerdown on the layout toggle grip.
1510
+ *
1511
+ * - View mode: "arm" layout mode — the canvas morphs into GridStack, after
1512
+ * which the merchant drags/resizes natively. Passing THIS element (not its
1513
+ * id) lets the morph controller scroll + pop the exact block grabbed.
1514
+ * stopPropagation keeps the bridge's global-click selection — and the morph
1515
+ * controller's click-away exit — from also firing.
1516
+ * - Layout mode: the grip IS GridStack's drag handle, so we do nothing here and
1517
+ * let the event fall through. GridStack starts the drag off `mousedown` (a
1518
+ * separate event from this `pointerdown`), so it is unaffected by the
1519
+ * toolbar's pointerdown stop; toggling back to view would abort the drag
1520
+ * before it begins.
1521
+ */
1522
+ __publicField(this, "handleMorphGrab", (e) => {
1523
+ if (e.button !== 0) return;
1524
+ if (canvasMode.value) return;
1525
+ e.stopPropagation();
1526
+ enterLayout(this);
1527
+ });
1528
+ __publicField(this, "handleHoverEnter", () => {
1529
+ this.startToolbarTracking();
1530
+ if (this.classList.contains("active")) return;
1531
+ if (this.hasAttribute("data-juo-grid-cell")) return;
1532
+ const target = this.paintTarget();
1533
+ if (!target) return;
1534
+ this.hoverOutlineTarget = target;
1535
+ target.style.outline = "1px solid #060606";
1536
+ target.style.outlineOffset = "0";
1537
+ });
1538
+ __publicField(this, "handleHoverLeave", () => {
1539
+ this.stopToolbarTracking();
1540
+ if (!this.hoverOutlineTarget) return;
1541
+ this.hoverOutlineTarget.style.outline = "";
1542
+ this.hoverOutlineTarget.style.outlineOffset = "";
1543
+ this.hoverOutlineTarget = null;
1544
+ });
1545
+ /**
1546
+ * Center the toolbar on the block's VISIBLE portion so it stays reachable when
1547
+ * the block is taller than / partially scrolled out of the viewport. When the
1548
+ * block is fully on screen this equals its geometric center.
1549
+ */
1550
+ __publicField(this, "positionToolbar", () => {
1551
+ if (!this.toolbar) return;
1552
+ const rect = this.getBoundingClientRect();
1553
+ if (rect.height <= 0) return;
1554
+ const viewportH = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : rect.bottom;
1555
+ const visibleTop = Math.max(rect.top, 0);
1556
+ const visibleBottom = Math.min(rect.bottom, viewportH);
1557
+ const midpoint = visibleBottom > visibleTop ? (visibleTop + visibleBottom) / 2 : rect.top + rect.height / 2;
1558
+ const relative = Math.min(Math.max(midpoint - rect.top, 0), rect.height);
1559
+ this.toolbar.style.top = `${relative}px`;
1560
+ });
1561
+ __publicField(this, "scheduleToolbarReposition", () => {
1562
+ if (typeof requestAnimationFrame !== "function") {
1563
+ this.positionToolbar();
1564
+ return;
1565
+ }
1566
+ if (this.toolbarRafId) return;
1567
+ this.toolbarRafId = requestAnimationFrame(() => {
1568
+ this.toolbarRafId = 0;
1569
+ this.positionToolbar();
1570
+ });
1571
+ });
1572
+ this.attachShadow({ mode: "open" });
1573
+ const styleElement = document.createElement("style");
1574
+ styleElement.textContent = layoutStyles.replace(/<style>|<\/style>/g, "").trim();
1575
+ this.shadowRoot.appendChild(styleElement);
1576
+ this.setupTemplate();
1577
+ }
1578
+ static get observedAttributes() {
1579
+ return ["data-block-id", "data-juo-grid-cell"];
1580
+ }
1581
+ attributeChangedCallback(name) {
1582
+ if (name === "data-juo-grid-cell") this.scheduleSkeletonRecompute();
1583
+ }
1584
+ setupTemplate() {
1585
+ const wrapper = document.createElement("div");
1586
+ wrapper.className = "layout-block-wrapper";
1587
+ const slotHost = document.createElement("div");
1588
+ slotHost.className = "layout-block-wrapper__slot-host";
1589
+ slotHost.appendChild(document.createElement("slot"));
1590
+ wrapper.appendChild(slotHost);
1591
+ const skeleton = document.createElement("div");
1592
+ skeleton.className = "layout-block-wrapper__skeleton";
1593
+ skeleton.setAttribute("aria-hidden", "true");
1594
+ const skeletonHead = document.createElement("div");
1595
+ skeletonHead.className = "layout-block-wrapper__skeleton-head";
1596
+ const skeletonIcon = document.createElement("span");
1597
+ skeletonIcon.className = "layout-block-wrapper__skeleton-icon";
1598
+ skeletonIcon.innerHTML = blockIconSvg;
1599
+ const skeletonTitle = document.createElement("span");
1600
+ skeletonTitle.className = "layout-block-wrapper__skeleton-title";
1601
+ skeletonHead.appendChild(skeletonIcon);
1602
+ skeletonHead.appendChild(skeletonTitle);
1603
+ const skeletonLines = document.createElement("div");
1604
+ skeletonLines.className = "layout-block-wrapper__skeleton-lines";
1605
+ for (let i = 0; i < 3; i++) {
1606
+ const line = document.createElement("span");
1607
+ line.className = "layout-block-wrapper__skeleton-line";
1608
+ skeletonLines.appendChild(line);
1609
+ }
1610
+ skeleton.appendChild(skeletonHead);
1611
+ skeleton.appendChild(skeletonLines);
1612
+ wrapper.appendChild(skeleton);
1613
+ this.skeletonTitle = skeletonTitle;
1614
+ this.skeletonLines = skeletonLines;
1615
+ const toolbar = this.buildToolbar();
1616
+ wrapper.appendChild(toolbar);
1617
+ this.toolbar = toolbar;
1618
+ this.shadowRoot.appendChild(wrapper);
1619
+ this.editorBridge.onMessage((message) => {
1620
+ var _a;
1621
+ if (message.type === "SELECT_BLOCK") {
1622
+ this.updateActiveState(message.payload.blockId === ((_a = this.block) == null ? void 0 : _a.id));
1623
+ }
1624
+ });
1625
+ }
1626
+ /**
1627
+ * Build the center hover toolbar. Buttons (left→right): layout toggle (grip,
1628
+ * leading), add-below, delete, fill-horizontal.
1629
+ * pointerdown/click are stopped at the container so selecting a tool never
1630
+ * bubbles to the bridge's global-click selection.
1631
+ */
1632
+ buildToolbar() {
1633
+ const toolbar = document.createElement("div");
1634
+ toolbar.className = "layout-block-wrapper__toolbar";
1635
+ toolbar.setAttribute("role", "toolbar");
1636
+ toolbar.setAttribute("aria-label", "Block actions");
1637
+ toolbar.addEventListener("pointerdown", (e) => e.stopPropagation());
1638
+ toolbar.addEventListener("click", (e) => e.stopPropagation());
1639
+ const toggle = this.createToolButton(
1640
+ layoutToggleSvg,
1641
+ "Edit layout",
1642
+ "layout-block-wrapper__tool-btn--toggle"
1643
+ );
1644
+ toggle.addEventListener(
1645
+ "pointerdown",
1646
+ (e) => this.handleMorphGrab(e)
1647
+ );
1648
+ const grip = this.createToolButton(
1649
+ dragGripSvg,
1650
+ "Drag to reposition",
1651
+ "layout-block-wrapper__tool-btn--grip"
1652
+ );
1653
+ grip.addEventListener(
1654
+ "pointerdown",
1655
+ (e) => this.handleMorphGrab(e)
1656
+ );
1657
+ const addBtn = this.createToolButton(
1658
+ addCircleOutlineSvg,
1659
+ "Add block below",
1660
+ "layout-block-wrapper__tool-btn--add"
1661
+ );
1662
+ addBtn.addEventListener("click", (e) => {
1663
+ e.stopPropagation();
1664
+ this.handleAddBelow(addBtn);
1665
+ });
1666
+ const deleteBtn = this.createToolButton(trashIconSvg, "Delete block");
1667
+ deleteBtn.addEventListener("click", (e) => {
1668
+ e.stopPropagation();
1669
+ this.handleRemoveBlock();
1670
+ });
1671
+ const fillH = this.createToolButton(
1672
+ fillHorizontalSvg,
1673
+ "Fill horizontally",
1674
+ "layout-block-wrapper__tool-btn--fill-h"
1675
+ );
1676
+ fillH.addEventListener("click", (e) => {
1677
+ e.stopPropagation();
1678
+ this.handleFillWidth();
1679
+ });
1680
+ toolbar.append(toggle, grip, addBtn, fillH, deleteBtn);
1681
+ return toolbar;
1682
+ }
1683
+ createToolButton(svg, label, extraClass) {
1684
+ const btn = document.createElement("button");
1685
+ btn.type = "button";
1686
+ btn.className = "layout-block-wrapper__tool-btn";
1687
+ if (extraClass) btn.classList.add(extraClass);
1688
+ btn.setAttribute("aria-label", label);
1689
+ btn.setAttribute("title", label);
1690
+ btn.innerHTML = svg;
1691
+ return btn;
1692
+ }
1693
+ /** Open the add-block menu anchored to the toolbar button, inserting AFTER. */
1694
+ handleAddBelow(button) {
1695
+ if (!this.block) return;
1696
+ const rect = button.getBoundingClientRect();
1697
+ this.editorBridge.sendMessage(
1698
+ createOpenAddBlockMenuMessage({
1699
+ targetBlockId: this.block.id,
1700
+ position: "after",
1701
+ anchor: { x: rect.right, y: rect.top }
1702
+ })
1703
+ );
1704
+ }
1705
+ /**
1706
+ * Traverse up the DOM (crossing shadow-root boundaries) to resolve the
1707
+ * containing block wrapper's id, then send REMOVE_BLOCK. Mirrors the legacy
1708
+ * wrapper's removal, but recognizes either wrapper tag (grid nesting uses the
1709
+ * layout wrapper).
1710
+ */
1711
+ handleRemoveBlock() {
1712
+ if (!this.block) return;
1713
+ const extensionRoot = this.closest("juo-extension-root");
1714
+ const slotName = (extensionRoot == null ? void 0 : extensionRoot.getAttribute("name")) || "default";
1715
+ let parentId = "root";
1716
+ let current = this;
1717
+ while (current) {
1718
+ const parent = this.getParentElement(current);
1719
+ if (!parent) break;
1720
+ if (parent !== this && isBlockWrapperTag(parent.tagName)) {
1721
+ const parentBlockId = parent.getAttribute("data-block-id");
1722
+ if (parentBlockId) {
1723
+ parentId = parentBlockId;
1724
+ break;
1725
+ }
1726
+ }
1727
+ current = parent;
1728
+ }
1729
+ this.editorBridge.sendMessage(
1730
+ createRemoveBlockMessage(
1731
+ { blockId: this.block.id, location: { slot: slotName, parentId } },
1732
+ this.block.id
1733
+ )
1734
+ );
1735
+ }
1736
+ /** Resolve the parent element, crossing shadow-root boundaries. */
1737
+ getParentElement(element) {
1738
+ const root = element.getRootNode();
1739
+ if (root instanceof ShadowRoot) return root.host;
1740
+ return element.parentElement;
1741
+ }
1742
+ /**
1743
+ * Toggle the block's sticky fill flag. Emitted as a layout command; the parent
1744
+ * engine flips the flag, expands the block into its free row-band space, pushes
1745
+ * the change to history + re-syncs the iframe.
1746
+ */
1747
+ handleFillWidth() {
1748
+ if (!this.block) return;
1749
+ const surface = this.closest(
1750
+ 'juo-extension-root[surface="overlay"]'
1751
+ ) ? "overlay" : "page";
1752
+ const ctx = getActiveSurfaceContext(surface);
1753
+ if (!ctx) return;
1754
+ emitLayoutCommand({
1755
+ surface,
1756
+ id: ctx.id,
1757
+ command: { kind: "fillWidth", blockId: this.block.id }
1758
+ });
1759
+ }
1760
+ connectedCallback() {
1761
+ const isInOverlay = this.closest('juo-extension-root[surface="overlay"]') !== null;
1762
+ if (isInOverlay && this.toolbar) {
1763
+ this.toolbar.style.display = "none";
1764
+ }
1765
+ const isWorkflowPreview = (() => {
1766
+ try {
1767
+ const params = new URLSearchParams(window.location.search);
1768
+ return params.get("workflow-mode") === "true";
1769
+ } catch {
1770
+ return false;
1771
+ }
1772
+ })();
1773
+ if (isWorkflowPreview && !isInOverlay) {
1774
+ this.style.pointerEvents = "none";
1775
+ }
1776
+ this.addEventListener("mouseenter", this.handleHoverEnter);
1777
+ this.addEventListener("mouseleave", this.handleHoverLeave);
1778
+ if (typeof ResizeObserver !== "undefined" && this.skeletonLines) {
1779
+ this.skeletonResizeObserver = new ResizeObserver(
1780
+ this.scheduleSkeletonRecompute
1781
+ );
1782
+ this.skeletonResizeObserver.observe(this.skeletonLines);
1783
+ }
1784
+ }
1785
+ disconnectedCallback() {
1786
+ var _a;
1787
+ this.stopToolbarTracking();
1788
+ (_a = this.skeletonResizeObserver) == null ? void 0 : _a.disconnect();
1789
+ this.skeletonResizeObserver = null;
1790
+ if (this.skeletonRafId) {
1791
+ cancelAnimationFrame(this.skeletonRafId);
1792
+ this.skeletonRafId = 0;
1793
+ }
1794
+ }
1795
+ startToolbarTracking() {
1796
+ this.positionToolbar();
1797
+ if (this.toolbarScrollListening || typeof window === "undefined") return;
1798
+ this.toolbarScrollListening = true;
1799
+ window.addEventListener("scroll", this.scheduleToolbarReposition, true);
1800
+ window.addEventListener("resize", this.scheduleToolbarReposition);
1801
+ }
1802
+ stopToolbarTracking() {
1803
+ if (this.toolbarScrollListening && typeof window !== "undefined") {
1804
+ this.toolbarScrollListening = false;
1805
+ window.removeEventListener("scroll", this.scheduleToolbarReposition, true);
1806
+ window.removeEventListener("resize", this.scheduleToolbarReposition);
1807
+ }
1808
+ if (this.toolbarRafId) {
1809
+ cancelAnimationFrame(this.toolbarRafId);
1810
+ this.toolbarRafId = 0;
1811
+ }
1812
+ }
1813
+ paintTarget() {
1814
+ const slotted = this.firstElementChild;
1815
+ if (!slotted || slotted.hasAttribute("data-empty")) return null;
1816
+ return resolveOutlineTarget(slotted);
1817
+ }
1818
+ updateActiveState(isActive) {
1819
+ if (isActive) {
1820
+ this.classList.add("active");
1821
+ this.handleHoverLeave();
1822
+ if (this.hasAttribute("data-juo-grid-cell")) return;
1823
+ const target = this.paintTarget();
1824
+ if (!target) return;
1825
+ this.selectionOutlineTarget = target;
1826
+ target.style.outline = "2px solid #060606";
1827
+ target.style.outlineOffset = "0";
1828
+ } else {
1829
+ this.classList.remove("active");
1830
+ if (this.selectionOutlineTarget) {
1831
+ this.selectionOutlineTarget.style.outline = "";
1832
+ this.selectionOutlineTarget.style.outlineOffset = "";
1833
+ this.selectionOutlineTarget = null;
1834
+ }
1835
+ }
1836
+ }
1837
+ /**
1838
+ * Match the placeholder bars to the (dynamic) cell height. Bars are a fixed
1839
+ * height with a uniform gap and stay top-aligned under the header, so every
1840
+ * skeleton reads identically. The count scales gently with height but is
1841
+ * halved and capped so tall cells don't fill with noise; very short cells
1842
+ * (< 2 bars) drop the bars entirely and show just the header. Count depends
1843
+ * only on the cell height (not on the bars), so there is no measure→mutate
1844
+ * feedback loop.
1845
+ */
1846
+ recomputeSkeletonLines() {
1847
+ var _a;
1848
+ if (!this.skeletonLines) return;
1849
+ if (!this.hasAttribute("data-juo-grid-cell")) return;
1850
+ const avail = this.skeletonLines.clientHeight;
1851
+ if (avail <= 0) return;
1852
+ const { SKELETON_LINE_H: LINE_H, SKELETON_GAP: GAP, SKELETON_MAX_BARS: MAX } = _LayoutBlockWrapper;
1853
+ const fit = Math.floor((avail + GAP) / (LINE_H + GAP));
1854
+ const count = fit < 2 ? 0 : Math.min(MAX, Math.max(2, Math.round(fit / 2)));
1855
+ const current = this.skeletonLines.childElementCount;
1856
+ if (count > current) {
1857
+ for (let i = current; i < count; i++) {
1858
+ const line = document.createElement("span");
1859
+ line.className = "layout-block-wrapper__skeleton-line";
1860
+ this.skeletonLines.appendChild(line);
1861
+ }
1862
+ } else if (count < current) {
1863
+ for (let i = current; i > count; i--) {
1864
+ (_a = this.skeletonLines.lastElementChild) == null ? void 0 : _a.remove();
1865
+ }
1866
+ }
1867
+ this.applyBarWidths();
1868
+ }
1869
+ /**
1870
+ * Stable offset into {@link LayoutBlockWrapper.SKELETON_BAR_WIDTHS} derived
1871
+ * from the block type, so each block type has its own width rhythm and
1872
+ * adjacent (differently-typed) blocks don't line up.
1873
+ */
1874
+ skeletonWidthOffset() {
1875
+ var _a;
1876
+ const key = ((_a = this.block) == null ? void 0 : _a.definition.name) ?? "";
1877
+ let hash = 0;
1878
+ for (let i = 0; i < key.length; i++) {
1879
+ hash = hash * 31 + key.charCodeAt(i) >>> 0;
1880
+ }
1881
+ return hash % _LayoutBlockWrapper.SKELETON_BAR_WIDTHS.length;
1882
+ }
1883
+ /** Assign each bar its width from the block-type-seeded sequence. */
1884
+ applyBarWidths() {
1885
+ if (!this.skeletonLines) return;
1886
+ const widths = _LayoutBlockWrapper.SKELETON_BAR_WIDTHS;
1887
+ const offset = this.skeletonWidthOffset();
1888
+ const bars = this.skeletonLines.children;
1889
+ for (let i = 0; i < bars.length; i++) {
1890
+ bars[i].style.width = `${widths[(offset + i) % widths.length]}%`;
1891
+ }
1892
+ }
1893
+ setBlock(block) {
1894
+ this.block = block;
1895
+ this.setAttribute("data-block-id", block.id);
1896
+ if (this.skeletonTitle) {
1897
+ this.skeletonTitle.textContent = getBlockLabel(block);
1898
+ }
1899
+ this.applyBarWidths();
1900
+ }
1901
+ };
1902
+ /**
1903
+ * Height-tiers for the placeholder bar count. Deliberately sparse: the bars
1904
+ * are a hint at "a block lives here", not a faithful line count, so we show
1905
+ * few and cap hard to avoid a noisy wall of identical bars on tall blocks.
1906
+ * Cells too short for >= 2 bars render header-only (no lonely single bar).
1907
+ */
1908
+ __publicField(_LayoutBlockWrapper, "SKELETON_LINE_H", 12);
1909
+ __publicField(_LayoutBlockWrapper, "SKELETON_GAP", 12);
1910
+ __publicField(_LayoutBlockWrapper, "SKELETON_MAX_BARS", 4);
1911
+ /**
1912
+ * Per-block width sequence. Widths are read from this palette starting at a
1913
+ * block-type-seeded offset so neighbouring blocks of different types get a
1914
+ * different width rhythm (deterministic — the same block type always renders
1915
+ * the same sequence). Chosen to look like varied text lines, none too short.
1916
+ */
1917
+ __publicField(_LayoutBlockWrapper, "SKELETON_BAR_WIDTHS", [
1918
+ 92,
1919
+ 68,
1920
+ 84,
1921
+ 58,
1922
+ 96,
1923
+ 74,
1924
+ 88,
1925
+ 62
1926
+ ]);
1927
+ let LayoutBlockWrapper = _LayoutBlockWrapper;
1928
+ if (customElements.get(LAYOUT_BLOCK_WRAPPER_TAG) == null) {
1929
+ customElements.define(LAYOUT_BLOCK_WRAPPER_TAG, LayoutBlockWrapper);
1930
+ }
1133
1931
  async function serializeBlock(block) {
1134
1932
  let initialValue = block.initialValue;
1135
1933
  if (typeof initialValue === "function") {
@@ -1157,7 +1955,7 @@ async function serializeBlock(block) {
1157
1955
  }
1158
1956
  })
1159
1957
  );
1160
- const loaded = entries.filter((e2) => e2 !== null);
1958
+ const loaded = entries.filter((e) => e !== null);
1161
1959
  if (loaded.length > 0) {
1162
1960
  locales = Object.fromEntries(loaded);
1163
1961
  }
@@ -1194,6 +1992,19 @@ async function setupEditorMode(deps) {
1194
1992
  const handler = createEditorHandler({
1195
1993
  themeState
1196
1994
  });
1995
+ setLayoutCommandEmitter(({ surface, id, rects, breakpoint }) => {
1996
+ editor.sendMessage(
1997
+ createLayoutCommandMessage({
1998
+ surface,
1999
+ id,
2000
+ command: { kind: "setRects", rects },
2001
+ breakpoint
2002
+ })
2003
+ );
2004
+ });
2005
+ setLayoutCommandDispatcher(({ surface, id, command }) => {
2006
+ editor.sendMessage(createLayoutCommandMessage({ surface, id, command }));
2007
+ });
1197
2008
  const { unsubscribe } = editor.onMessage((message) => {
1198
2009
  handler.handleMessage(message);
1199
2010
  });
@@ -1218,42 +2029,56 @@ async function setupEditorMode(deps) {
1218
2029
  unsubscribeRegister();
1219
2030
  unsubscribe();
1220
2031
  editor.destroy();
2032
+ setLayoutCommandEmitter(null);
2033
+ setLayoutCommandDispatcher(null);
2034
+ clearAllSurfaceContexts();
1221
2035
  };
1222
2036
  }
1223
2037
  export {
1224
2038
  BlockWrapper,
1225
2039
  EDITOR_BODY_BOTTOM_PADDING_PX,
1226
2040
  EDITOR_BODY_TOP_PADDING_PX,
2041
+ LayoutBlockWrapper,
1227
2042
  buildEditorCanvasStyleContent,
1228
- j as createAddBlockMenuClosedMessage,
1229
- k as createAddBlockMessage,
2043
+ canvasMode2 as canvasMode,
2044
+ k as createAddBlockMenuClosedMessage,
2045
+ l as createAddBlockMessage,
1230
2046
  createEditorBridge,
1231
2047
  createEditorHandler,
1232
- b as createFocusInlineTextMessage,
1233
- l as createMoveBlockMessage,
2048
+ m as createFocusInlineTextMessage,
2049
+ createLayoutCommandMessage,
2050
+ c as createLayoutingModeChangedMessage,
2051
+ n as createMoveBlockMessage,
1234
2052
  createOpenAddBlockMenuMessage,
1235
- m as createProvideGlobalStylesMessage,
1236
- n as createProvideInitialGlobalStylesMessage,
1237
- o as createProvideThemeStateMessage,
1238
- p as createProvideTranslationOverridesMessage,
1239
- c as createRegisterBlockTranslationKeysMessage,
2053
+ o as createProvideGlobalStylesMessage,
2054
+ p as createProvideInitialGlobalStylesMessage,
2055
+ q as createProvideLayoutDocumentMessage,
2056
+ s as createProvideLayoutMessage,
2057
+ t as createProvideThemeStateMessage,
2058
+ u as createProvideTranslationOverridesMessage,
2059
+ v as createRegisterBlockTranslationKeysMessage,
1240
2060
  createRegisterBlocksMessage,
1241
2061
  createRegisterRoutesMessage,
1242
2062
  createRemoveBlockMessage,
1243
- q as createReportContentHeightMessage,
1244
- r as createRequestGlobalStylesMessage,
1245
- s as createRequestThemeStateMessage,
1246
- t as createRequestTranslationOverridesMessage,
1247
- u as createScrollSyncMessage,
1248
- e as createSelectBlockMessage,
1249
- v as createSetBlockPresetMessage,
1250
- w as createSetGlobalStylesMessage,
1251
- x as createSetLocaleMessage,
1252
- y as createUpdateBlockPropsMessage,
1253
- d as createUpdateBlockTranslationMessage,
1254
- z as createUpsertThemeStateMessage,
1255
- A as getEditorBridge,
1256
- B as isEditorMessage,
1257
- C as isIframeMessage,
2063
+ w as createReportBlockContentSizeMessage,
2064
+ x as createReportContentHeightMessage,
2065
+ y as createRequestCloseAddBlockMenuMessage,
2066
+ z as createRequestGlobalStylesMessage,
2067
+ A as createRequestThemeStateMessage,
2068
+ B as createRequestTranslationOverridesMessage,
2069
+ C as createScrollSyncMessage,
2070
+ D as createSelectBlockMessage,
2071
+ E as createSetBlockPresetMessage,
2072
+ F as createSetGlobalStylesMessage,
2073
+ G as createSetLayoutingModeMessage,
2074
+ H as createSetLocaleMessage,
2075
+ I as createUpdateBlockPropsMessage,
2076
+ J as createUpdateBlockTranslationMessage,
2077
+ K as createUpsertThemeStateMessage,
2078
+ g as getEditorBridge,
2079
+ M as isEditorMessage,
2080
+ N as isIframeMessage,
2081
+ r as resetCanvasMode,
2082
+ setCanvasMode,
1258
2083
  setupEditorMode
1259
2084
  };