@vectoriox/iox-builder 9.11.4 → 10.0.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.
@@ -167,9 +167,17 @@ var PanelEventTypes;
167
167
  * would (`string` component type, or a block / symbol object). The builder resolves WHERE from
168
168
  * the current selection — see `resolveInsertTarget`. */
169
169
  PanelEventTypes["INSERT_COMPONENT"] = "insertcomponent";
170
- /** Insert prebuilt node(s) as GLOBAL elements (data = ComponentNode[]) — the consent preset's
171
- * path (D-C5: site-wide by construction). Mirrors the MakeGlobal toolbar action. */
170
+ /** Insert prebuilt node(s) as GLOBAL elements (data = ComponentNode[]) — the consent preset's click and
171
+ * the site header's click or drop (CC1.1). The HOST handles it (it owns the globals, their undo step and
172
+ * the one-header rule); the builder only routes a dropped global payload here (`globalInsert$`). */
172
173
  PanelEventTypes["INSERT_GLOBAL"] = "insertglobal";
174
+ /** A panel makes `data.node` a GLOBAL element at `data.position` (default before) — the same move as the
175
+ * toolbar's Make Global (the Style panel's Global switch, Bindings ▸ Phone menu ▸ "Make it the site header"). */
176
+ PanelEventTypes["MAKE_GLOBAL"] = "make-global";
177
+ /** A panel returns the global `data.node` to the page — the toolbar's Remove Global. */
178
+ PanelEventTypes["REMOVE_GLOBAL"] = "remove-global";
179
+ /** A panel moved the global `data.node` to `data.position` (before / after the page content). */
180
+ PanelEventTypes["GLOBAL_POSITION"] = "update-global-position";
173
181
  /** A click on a compound component's PART (Field label/control/help/…) — the style panel
174
182
  * activates that part (data = { part }). Doron 2026-10-10: "both shall be selectable". */
175
183
  PanelEventTypes["PART_SELECT"] = "partselect";
@@ -2523,6 +2531,8 @@ class DragEngineService {
2523
2531
  this.deepTarget$ = new BehaviorSubject(null);
2524
2532
  /** Emits the dropped/inserted ComponentNode after every successful drop. */
2525
2533
  this.dropComplete$ = new Subject();
2534
+ /** A fresh GLOBAL node from the palette (the site header) — dropped or clicked, never placed in a zone. */
2535
+ this.globalInsert$ = new Subject();
2526
2536
  // ── Active drag state ─────────────────────────────────────
2527
2537
  this._isDragging = false;
2528
2538
  this._payload = null;
@@ -2633,6 +2643,10 @@ class DragEngineService {
2633
2643
  // ── Shared drop handler — used by all layout components ───────────────────
2634
2644
  handleDrop(children, event, dropListId, cdr, afterDrop) {
2635
2645
  const { payload, insertIndex } = event;
2646
+ if (this.routeGlobalPayload(payload.data)) {
2647
+ afterDrop?.();
2648
+ return;
2649
+ }
2636
2650
  const changesParent = payload.type === 'external' || payload.sourceId !== dropListId;
2637
2651
  if (changesParent && this.refuseInsert(payload.data, this.dropzoneAncestors(dropListId))) {
2638
2652
  afterDrop?.();
@@ -2712,6 +2726,8 @@ class DragEngineService {
2712
2726
  * step through the same path. Returns the inserted node, or null if the payload was unusable.
2713
2727
  */
2714
2728
  insertPayload(children, index, data, ancestors = []) {
2729
+ if (this.routeGlobalPayload(data))
2730
+ return null;
2715
2731
  if (this.refuseInsert(data, ancestors))
2716
2732
  return null;
2717
2733
  const node = this.materialize(data);
@@ -2722,6 +2738,29 @@ class DragEngineService {
2722
2738
  this.dropComplete$.next(node);
2723
2739
  return node;
2724
2740
  }
2741
+ /**
2742
+ * A palette payload born GLOBAL (the site header, CC1.1) is not placed where it was dropped or clicked:
2743
+ * it is handed to `globalInsert$` (the builder hands it to the host, which owns the globals). True when
2744
+ * the payload was routed there. Only an EXTERNAL payload can be one — a node already on the canvas that
2745
+ * is global moves through the globals' own paths.
2746
+ */
2747
+ routeGlobalPayload(data) {
2748
+ if (!data || typeof data !== 'object' || !this.payloadNode(data)?.isGlobal)
2749
+ return false;
2750
+ if (this.isOnCanvas(data))
2751
+ return false;
2752
+ const node = this.materialize(data);
2753
+ if (!node)
2754
+ return false;
2755
+ this.globalInsert$.next(node);
2756
+ return true;
2757
+ }
2758
+ isOnCanvas(data) {
2759
+ for (const list of this.dataMap.values())
2760
+ if (list.includes(data))
2761
+ return true;
2762
+ return false;
2763
+ }
2725
2764
  /** True when `children` is a registered drop zone's array — i.e. a real container that can hold
2726
2765
  * children. Click-to-insert uses the same test a drag does, so it can't insert where a drop
2727
2766
  * couldn't. */
@@ -9077,6 +9116,12 @@ class BuilderComponent {
9077
9116
  this._selectAfterDrop(node);
9078
9117
  this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
9079
9118
  }));
9119
+ // A palette payload born GLOBAL (the site header) never lands where it was dropped: the host owns
9120
+ // the globals, their undo step and the one-header rule (CC1.1) — the same event a click sends.
9121
+ this.sub.add(this.dragEngine.globalInsert$.subscribe((node) => {
9122
+ this.autoLabel(node);
9123
+ this.panelEventService.emit(PanelEventTypes.INSERT_GLOBAL, [node]);
9124
+ }));
9080
9125
  this.panelEventService.emit(PanelEventTypes.PANEL_OPEN, PanelTypes.STYLES);
9081
9126
  this.viewportSub = this.viewportService.state$.subscribe(state => {
9082
9127
  this.activeBreakpoint = state.breakpoint;
@@ -9511,6 +9556,21 @@ class BuilderComponent {
9511
9556
  this.selectPanel(PanelTypes.STYLES);
9512
9557
  this._emitElementSelect(event.node, event.componentRef);
9513
9558
  }
9559
+ /**
9560
+ * Make / Remove global moved `node` between the page and the globals: keep it selected in its new place,
9561
+ * so the breadcrumb (its Global chip) and every panel describe where it is NOW. Clearing the selection
9562
+ * left the crumb and the Bindings panel on the old node until a reload (CC1.1, Doron's report).
9563
+ */
9564
+ reselectMoved(node) {
9565
+ this.overlayService.clearSelect();
9566
+ this.cdr.detectChanges();
9567
+ setTimeout(() => this._withNodeRef(node, ref => {
9568
+ this.selectedItem = node;
9569
+ this.overlayService.setSelect(ref.element, node.type, this.activeMode, node, ref.componentRef);
9570
+ this._emitElementSelect(node, ref.componentRef);
9571
+ this.cdr.markForCheck();
9572
+ }));
9573
+ }
9514
9574
  _selectAfterDrop(node) {
9515
9575
  // Wait one tick for Angular to render the new component and register it
9516
9576
  // in the overlay before attempting to select it.
@@ -9737,9 +9797,7 @@ class BuilderComponent {
9737
9797
  node.globalPosition = event.position ?? 'before';
9738
9798
  this.globalElements.push(node);
9739
9799
  this.globalElementsChange.emit([...this.globalElements]);
9740
- this.selectedItem = null;
9741
- this.overlayService.clearSelect();
9742
- this.cdr.markForCheck();
9800
+ this.reselectMoved(node);
9743
9801
  this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
9744
9802
  }
9745
9803
  break;
@@ -9752,9 +9810,7 @@ class BuilderComponent {
9752
9810
  node.globalPosition = undefined;
9753
9811
  this.layout.push(node);
9754
9812
  this.globalElementsChange.emit([...this.globalElements]);
9755
- this.selectedItem = null;
9756
- this.overlayService.clearSelect();
9757
- this.cdr.markForCheck();
9813
+ this.reselectMoved(node);
9758
9814
  this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
9759
9815
  }
9760
9816
  break;
@@ -10119,21 +10175,21 @@ class BuilderComponent {
10119
10175
  this.insertFromPanel(panelEvent.data);
10120
10176
  return;
10121
10177
  }
10122
- // Prebuilt roots straight into the GLOBAL elements (the consent preset, D-C5) — the same
10123
- // moves as the MakeGlobal toolbar action, minus the remove-from-layout step.
10124
- if (panelEvent.type === PanelEventTypes.INSERT_GLOBAL) {
10125
- const nodes = (Array.isArray(panelEvent.data) ? panelEvent.data : [panelEvent.data]);
10126
- for (const node of nodes) {
10127
- if (!node)
10128
- continue;
10129
- this.autoLabel(node);
10130
- node.isGlobal = true;
10131
- node.globalPosition = node.globalPosition ?? 'after';
10132
- this.globalElements.push(node);
10133
- }
10178
+ // A panel moves a node between the page and the globals — the toolbar's own Make / Remove Global.
10179
+ if (panelEvent.type === PanelEventTypes.MAKE_GLOBAL || panelEvent.type === PanelEventTypes.REMOVE_GLOBAL) {
10180
+ const node = panelEvent.data?.node;
10181
+ const action = panelEvent.type === PanelEventTypes.MAKE_GLOBAL ? ToolbarAction.MakeGlobal : ToolbarAction.RemoveGlobal;
10182
+ if (node)
10183
+ this.onToolbarAction({ action, entry: { node }, position: panelEvent.data?.position });
10184
+ return;
10185
+ }
10186
+ if (panelEvent.type === PanelEventTypes.GLOBAL_POSITION) {
10187
+ const node = panelEvent.data?.node;
10188
+ if (!node?.isGlobal || !this.globalElements.includes(node))
10189
+ return;
10190
+ node.globalPosition = panelEvent.data?.position === 'after' ? 'after' : 'before';
10134
10191
  this.globalElementsChange.emit([...this.globalElements]);
10135
- this.cdr.markForCheck();
10136
- this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
10192
+ this.reselectMoved(node);
10137
10193
  return;
10138
10194
  }
10139
10195
  // A panel rewrote a subtree in place (a Card's Parts / Layout / Link): re-render it, re-register