@vectoriox/iox-builder 9.5.1 → 9.5.3

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.
@@ -984,6 +984,23 @@ const HIDDEN_AT_REST_TYPES = ['Dialog'];
984
984
  function isHiddenAtRest(node) {
985
985
  return !!node?.type && HIDDEN_AT_REST_TYPES.includes(node.type);
986
986
  }
987
+ /**
988
+ * How the selection shows a node hidden at rest — the two approved rules, by what the node IS:
989
+ * - a PART of a composite (it carries a `role`: the mobile menu's drawer) follows R2 (board 3): the
990
+ * selection is the reveal, and moving it out of the part hides it again;
991
+ * - a free-standing overlay (a plain Dialog) follows board A4: selecting it opens it, and it stays open
992
+ * while the author works elsewhere, until its X, its backdrop or the eye closes it.
993
+ */
994
+ var SelectionReveal;
995
+ (function (SelectionReveal) {
996
+ SelectionReveal["WhileSelected"] = "while-selected";
997
+ SelectionReveal["UntilClosed"] = "until-closed";
998
+ })(SelectionReveal || (SelectionReveal = {}));
999
+ function selectionRevealOf(node) {
1000
+ if (!isHiddenAtRest(node))
1001
+ return null;
1002
+ return node?.role ? SelectionReveal.WhileSelected : SelectionReveal.UntilClosed;
1003
+ }
987
1004
  /** Root → `target`, inclusive, across `roots` (page layout and globals), or [] when it is not there. */
988
1005
  function pathToNode(roots, target) {
989
1006
  for (const root of roots) {
@@ -1654,6 +1671,13 @@ const BUILDER_CHROME_EN = Object.freeze({
1654
1671
  'builder.chrome.repeaterMoreRows': '{count} more rows from {alias}',
1655
1672
  'builder.chrome.repeaterMoreRow': '{count} more row from {alias}',
1656
1673
  'builder.chrome.repeaterMoreRowsHint': 'Rows from the source are not layers: edit the template above',
1674
+ 'builder.chrome.layersPage': 'Page',
1675
+ 'builder.chrome.layersGlobal': 'Global',
1676
+ 'builder.chrome.layersEmpty': 'No layers yet',
1677
+ 'builder.chrome.layersGlobalEmpty': 'No global elements',
1678
+ 'builder.chrome.layerRename': 'Rename',
1679
+ 'builder.chrome.layerDuplicate': 'Duplicate',
1680
+ 'builder.chrome.layerDelete': 'Delete',
1657
1681
  });
1658
1682
  /** The layer tree's eye-slash title on a part hidden at rest (a closed drawer, R2). */
1659
1683
  const HIDDEN_AT_REST_LABEL_KEY = 'builder.chrome.hiddenAtRest';
@@ -4016,7 +4040,7 @@ var CanvasShowState;
4016
4040
  */
4017
4041
  class CanvasDesignStateService {
4018
4042
  constructor() {
4019
- this._state$ = new BehaviorSubject({ revealed: new Set(), shown: new Map() });
4043
+ this._state$ = new BehaviorSubject({ revealed: new Set(), pinned: new Set(), shown: new Map() });
4020
4044
  this.state$ = this._state$.asObservable();
4021
4045
  this._pagePath$ = new BehaviorSubject(null);
4022
4046
  /** The app path of the page on the canvas (`/work`), or null when unknown. */
@@ -4033,13 +4057,31 @@ class CanvasDesignStateService {
4033
4057
  if (next !== this._pagePath$.value)
4034
4058
  this._pagePath$.next(next);
4035
4059
  }
4036
- /** The selection moved: the hidden-at-rest parts on its path are revealed, every other one is not. */
4060
+ /**
4061
+ * The selection moved. The hidden-at-rest PARTS on its path are revealed and every other part is not
4062
+ * (R2); a free-standing overlay on the path is pinned open until it is closed (A4).
4063
+ */
4037
4064
  setSelectionPath(path) {
4038
- const revealed = new Set(path.filter(isHiddenAtRest).map(n => n.id).filter((id) => !!id));
4039
- const current = this._state$.value.revealed;
4040
- if (revealed.size === current.size && [...revealed].every(id => current.has(id)))
4065
+ const ids = (reveal) => path.filter(n => selectionRevealOf(n) === reveal).map(n => n.id).filter((id) => !!id);
4066
+ const revealed = new Set(ids(SelectionReveal.WhileSelected));
4067
+ const { revealed: was, pinned: held } = this._state$.value;
4068
+ const pinned = new Set([...held, ...ids(SelectionReveal.UntilClosed)]);
4069
+ if (sameSet(revealed, was) && sameSet(pinned, held))
4070
+ return;
4071
+ this._state$.next({ ...this._state$.value, revealed, pinned });
4072
+ }
4073
+ /** Closed on the canvas (its X, its backdrop, the eye): no longer pinned or switched open. */
4074
+ dismiss(nodeId) {
4075
+ if (!nodeId)
4041
4076
  return;
4042
- this._state$.next({ ...this._state$.value, revealed });
4077
+ const { pinned, shown } = this._state$.value;
4078
+ if (!pinned.has(nodeId) && !shown.has(nodeId))
4079
+ return;
4080
+ const nextPinned = new Set(pinned);
4081
+ nextPinned.delete(nodeId);
4082
+ const nextShown = new Map(shown);
4083
+ nextShown.delete(nodeId);
4084
+ this._state$.next({ ...this._state$.value, pinned: nextPinned, shown: nextShown });
4043
4085
  }
4044
4086
  /** The switcher's choice for one part (null = back to the saved default). */
4045
4087
  setShown(nodeId, state) {
@@ -4058,8 +4100,8 @@ class CanvasDesignStateService {
4058
4100
  isShown(nodeId) {
4059
4101
  if (!nodeId)
4060
4102
  return false;
4061
- const { revealed, shown } = this._state$.value;
4062
- return revealed.has(nodeId) || shown.get(nodeId) === CanvasShowState.Open;
4103
+ const { revealed, pinned, shown } = this._state$.value;
4104
+ return revealed.has(nodeId) || pinned.has(nodeId) || shown.get(nodeId) === CanvasShowState.Open;
4063
4105
  }
4064
4106
  /** True when the part is shown only because the selection is inside it (the switcher's hint line). */
4065
4107
  isRevealedBySelection(nodeId) {
@@ -4067,7 +4109,7 @@ class CanvasDesignStateService {
4067
4109
  }
4068
4110
  /** A new page on the canvas: nothing is revealed or switched open. */
4069
4111
  reset() {
4070
- this._state$.next({ revealed: new Set(), shown: new Map() });
4112
+ this._state$.next({ revealed: new Set(), pinned: new Set(), shown: new Map() });
4071
4113
  }
4072
4114
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4073
4115
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService }); }
@@ -4075,6 +4117,9 @@ class CanvasDesignStateService {
4075
4117
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService, decorators: [{
4076
4118
  type: Injectable
4077
4119
  }] });
4120
+ function sameSet(a, b) {
4121
+ return a.size === b.size && [...a].every(id => b.has(id));
4122
+ }
4078
4123
 
4079
4124
  // Re-exported from the shared iox-ui package so iox-builder consumers
4080
4125
  // don't need to import from two different packages.
@@ -7172,6 +7217,11 @@ const MORE_ROW_KEY = 'builder.chrome.repeaterMoreRow';
7172
7217
  const MORE_ROWS_HINT_KEY = 'builder.chrome.repeaterMoreRowsHint';
7173
7218
  /** Panel events after which a row's extras change without the tree's STRUCTURE changing. */
7174
7219
  const EXTRAS_EVENTS = new Set([PanelEventTypes.REPEATER_ROWS_CHANGED, PanelEventTypes.BINDING_CHANGED]);
7220
+ const CHROME_WORDS = [
7221
+ ['page', 'builder.chrome.layersPage'], ['global', 'builder.chrome.layersGlobal'], ['empty', 'builder.chrome.layersEmpty'],
7222
+ ['globalEmpty', 'builder.chrome.layersGlobalEmpty'], ['rename', 'builder.chrome.layerRename'],
7223
+ ['duplicate', 'builder.chrome.layerDuplicate'], ['delete', 'builder.chrome.layerDelete'],
7224
+ ];
7175
7225
  class LayerTreeComponent {
7176
7226
  constructor(registry, overlayService, dragEngine) {
7177
7227
  this.registry = registry;
@@ -7196,6 +7246,8 @@ class LayerTreeComponent {
7196
7246
  this.templateLabel = this.labels(TEMPLATE_KEY, BUILDER_CHROME_EN[TEMPLATE_KEY]);
7197
7247
  this.templateTitle = this.labels(TEMPLATE_HINT_KEY, BUILDER_CHROME_EN[TEMPLATE_HINT_KEY]);
7198
7248
  this.moreRowsTitle = this.labels(MORE_ROWS_HINT_KEY, BUILDER_CHROME_EN[MORE_ROWS_HINT_KEY]);
7249
+ /** The tree's own chrome words, through the host's i18n (builder.chrome.*). */
7250
+ this.words = Object.fromEntries(CHROME_WORDS.map(([k, key]) => [k, this.labels(key, BUILDER_CHROME_EN[key])]));
7199
7251
  /** Cache: component type → icon class */
7200
7252
  this.iconMap = new Map();
7201
7253
  /** Last-seen structural signature — used by ngDoCheck to skip no-op rebuilds. */
@@ -7226,9 +7278,11 @@ class LayerTreeComponent {
7226
7278
  const key = more.count === 1 ? MORE_ROW_KEY : MORE_ROWS_KEY;
7227
7279
  return this.labels(key, BUILDER_CHROME_EN[key]).replace('{count}', String(more.count)).replace('{alias}', more.alias);
7228
7280
  }
7229
- /** The row's title: every binding a bound part holds. */
7230
- boundTitle(tn) {
7231
- return tn.bound ? bindingLabels(tn.node).join(' · ') : null;
7281
+ /** The name's title — the full text a narrow sidebar truncates: the name, its sub-label, and every
7282
+ * binding a bound part holds. */
7283
+ rowTitle(tn) {
7284
+ const bindings = tn.bound ? bindingLabels(tn.node) : [];
7285
+ return [tn.label, ...(bindings.length ? bindings : tn.sub ? [tn.sub] : [])].join(' · ');
7232
7286
  }
7233
7287
  // Runs on every CD cycle — exit immediately if structure hasn't changed.
7234
7288
  ngDoCheck() {
@@ -7363,11 +7417,11 @@ class LayerTreeComponent {
7363
7417
  return instance instanceof BuilderRepeaterComponent ? { alias: instance.source, count: instance.items.length } : null;
7364
7418
  }
7365
7419
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: LayerTreeComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: DragEngineService }], target: i0.ɵɵFactoryTarget.Component }); }
7366
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: LayerTreeComponent, isStandalone: false, selector: "app-layer-tree", inputs: { layout: "layout", globalElements: "globalElements" }, outputs: { nodeSelect: "nodeSelect", nodeAction: "nodeAction", nodeMove: "nodeMove" }, ngImport: i0, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'page'\" (click)=\"activeSection = 'page'\">\n <i class=\"ph-thin ph-stack\"></i> Page\n </button>\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'global'\" (click)=\"activeSection = 'global'\">\n <i class=\"ph-thin ph-globe\"></i> Global\n @if (flatGlobalTree.length) {\n <span class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n }\n </button>\n </div>\n\n <!-- A node's label with what the shipped Repeater adds to it (boards 4 / 8): its sub-label, the\n template chip, a composite part's role and lock, the hidden-at-rest glyph. -->\n <ng-template #rowLabel let-tn>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\" [attr.title]=\"boundTitle(tn)\">{{ tn.label }}@if (tn.sub) {<em class=\"node-sub\"> \u00B7 {{ tn.sub }}</em>}</span>\n @if (tn.template) {\n <span class=\"node-template\" [title]=\"templateTitle\">{{ templateLabel }}</span>\n }\n @if (tn.node.role) {\n <span class=\"node-role\" [title]=\"roleTitle + ': ' + tn.node.role\">{{ tn.node.role }}</span>\n }\n @if (tn.locked) {\n <i class=\"ph-thin ph-lock-simple node-lock\" [title]=\"lockedPartTitle\" [attr.aria-label]=\"lockedPartTitle\"></i>\n }\n @if (tn.hiddenAtRest) {\n <span class=\"node-rest\" [title]=\"hiddenAtRestTitle\" [attr.aria-label]=\"hiddenAtRestTitle\"><i class=\"ph-thin ph-eye-slash\"></i></span>\n }\n </ng-template>\n\n <!-- The rows a Repeater's source adds are not nodes: ONE muted line after its template, never selectable. -->\n <ng-template #moreRowsLine let-tn let-handle=\"handle\">\n <div class=\"tree-more\" aria-disabled=\"true\" [title]=\"moreRowsTitle\"\n [style.padding-inline-start.px]=\"12 + tn.moreRows.depth * 16\">\n @if (handle) {\n <i class=\"ph-thin ph-dots-six-vertical drag-handle tree-more-handle\" aria-hidden=\"true\"></i>\n }\n <span class=\"expand-spacer\"></span>\n <i class=\"ph-thin ph-rows node-icon\"></i>\n <span class=\"node-label\">{{ moreRowsText(tn.moreRows) }}</span>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500 Page elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeSection === 'page') {\n <div class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n @if (!flatTree.length) {\n <div class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n }\n\n @for (tn of flatTree; track tn.node) {\n <div class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n @if (tn.hasChildren) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n @if (editingNodeId === tn.node.id) {\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n } @else {\n <ng-container *ngTemplateOutlet=\"rowLabel; context: { $implicit: tn }\"></ng-container>\n }\n\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n @if (!tn.locked) {\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n }\n </span>\n </div>\n @if (tn.moreRows) {\n <ng-container *ngTemplateOutlet=\"moreRowsLine; context: { $implicit: tn, handle: true }\"></ng-container>\n }\n }\n </div>\n }\n\n <!-- \u2500\u2500 Global elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeSection === 'global') {\n <div class=\"tree-body\">\n @if (!flatGlobalTree.length) {\n <div class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n }\n\n @for (tn of flatGlobalTree; track tn.node) {\n <div class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n @if (tn.hasChildren) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngTemplateOutlet=\"rowLabel; context: { $implicit: tn }\"></ng-container>\n @if (tn.depth === 0) {\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n }\n\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n @if (tn.moreRows) {\n <ng-container *ngTemplateOutlet=\"moreRowsLine; context: { $implicit: tn }\"></ng-container>\n }\n }\n </div>\n }\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .section-tab{display:inline-flex;align-items:center;gap:4px;flex:1;justify-content:center;padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--p-surface-200);border-radius:4px;background:transparent;color:var(--p-text-muted-color);cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap}.layer-tree .section-tab i{font-size:11px}.layer-tree .section-tab:hover{background:var(--p-surface-100);color:var(--p-text-color)}.layer-tree .section-tab--active{background:#cb909026;border-color:#cb9090;color:#cb9090}.layer-tree .section-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;font-size:10px;font-weight:600;background:#cb9090;color:#fff;border-radius:8px}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1 1 auto;min-width:4.5em}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-sub{font-style:normal;font-weight:400;color:#535d6e}.layer-tree .node-template{flex:none;padding:0 4px;border-radius:4px;background:#8a3a72;color:#fff;font:500 10px Fira Code,Fira Mono,monospace;line-height:16px}.layer-tree .tree-more{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;font-size:12px;font-style:italic;color:#64748b;border-inline-start:2px solid transparent;cursor:default;-webkit-user-select:none;user-select:none}.layer-tree .tree-more-handle{visibility:hidden}.layer-tree .node-role{flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:0 4px;border:1px dashed #c9a1be;border-radius:4px;background:#f7eef5;color:#8a3a72;font:500 10px Fira Code,Fira Mono,monospace;line-height:16px}.layer-tree .node-lock{flex:none;color:#64748b;font-size:12px}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-rest{display:inline-flex;align-items:center;flex-shrink:0;font-size:12px;color:var(--p-text-muted-color)}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i5.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] }); }
7420
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: LayerTreeComponent, isStandalone: false, selector: "app-layer-tree", inputs: { layout: "layout", globalElements: "globalElements" }, outputs: { nodeSelect: "nodeSelect", nodeAction: "nodeAction", nodeMove: "nodeMove" }, ngImport: i0, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'page'\" (click)=\"activeSection = 'page'\">\n <i class=\"ph-thin ph-stack\"></i> {{ words.page }}\n </button>\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'global'\" (click)=\"activeSection = 'global'\">\n <i class=\"ph-thin ph-globe\"></i> {{ words.global }}\n @if (flatGlobalTree.length) {\n <span class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n }\n </button>\n </div>\n\n <!-- A node's label with what the shipped Repeater adds to it (boards 4 / 8): its sub-label, the\n template chip, a composite part's role and lock, the hidden-at-rest glyph. -->\n <ng-template #rowLabel let-tn>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\" [attr.title]=\"rowTitle(tn)\">{{ tn.label }}@if (tn.sub) {<em class=\"node-sub\"> \u00B7 {{ tn.sub }}</em>}</span>\n @if (tn.template) {\n <span class=\"node-template\" [title]=\"templateTitle\">{{ templateLabel }}</span>\n }\n @if (tn.node.role) {\n <span class=\"node-role\" [title]=\"roleTitle + ': ' + tn.node.role\">{{ tn.node.role }}</span>\n }\n @if (tn.locked) {\n <i class=\"ph-thin ph-lock-simple node-lock\" [title]=\"lockedPartTitle\" [attr.aria-label]=\"lockedPartTitle\"></i>\n }\n @if (tn.hiddenAtRest) {\n <span class=\"node-rest\" [title]=\"hiddenAtRestTitle\" [attr.aria-label]=\"hiddenAtRestTitle\"><i class=\"ph-thin ph-eye-slash\"></i></span>\n }\n </ng-template>\n\n <!-- The rows a Repeater's source adds are not nodes: ONE muted line after its template, never selectable. -->\n <ng-template #moreRowsLine let-tn let-handle=\"handle\">\n <div class=\"tree-more\" aria-disabled=\"true\" [title]=\"moreRowsTitle\"\n [style.padding-inline-start.px]=\"12 + tn.moreRows.depth * 16\">\n @if (handle) {\n <i class=\"ph-thin ph-dots-six-vertical drag-handle tree-more-handle\" aria-hidden=\"true\"></i>\n }\n <span class=\"expand-spacer\"></span>\n <i class=\"ph-thin ph-rows node-icon\"></i>\n <span class=\"node-label\">{{ moreRowsText(tn.moreRows) }}</span>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500 Page elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeSection === 'page') {\n <div class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n @if (!flatTree.length) {\n <div class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>{{ words.empty }}</span>\n </div>\n }\n\n @for (tn of flatTree; track tn.node) {\n <div class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n @if (tn.hasChildren) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n @if (editingNodeId === tn.node.id) {\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n } @else {\n <ng-container *ngTemplateOutlet=\"rowLabel; context: { $implicit: tn }\"></ng-container>\n }\n\n <span class=\"node-actions\">\n <button class=\"action-btn\" [title]=\"words.rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" [title]=\"words.duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n @if (!tn.locked) {\n <button class=\"action-btn action-btn--delete\" [title]=\"words.delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n }\n </span>\n </div>\n @if (tn.moreRows) {\n <ng-container *ngTemplateOutlet=\"moreRowsLine; context: { $implicit: tn, handle: true }\"></ng-container>\n }\n }\n </div>\n }\n\n <!-- \u2500\u2500 Global elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeSection === 'global') {\n <div class=\"tree-body\">\n @if (!flatGlobalTree.length) {\n <div class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>{{ words.globalEmpty }}</span>\n </div>\n }\n\n @for (tn of flatGlobalTree; track tn.node) {\n <div class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n @if (tn.hasChildren) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngTemplateOutlet=\"rowLabel; context: { $implicit: tn }\"></ng-container>\n @if (tn.depth === 0) {\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n }\n\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" [title]=\"words.delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n @if (tn.moreRows) {\n <ng-container *ngTemplateOutlet=\"moreRowsLine; context: { $implicit: tn }\"></ng-container>\n }\n }\n </div>\n }\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .section-tab{display:inline-flex;align-items:center;gap:4px;flex:1;justify-content:center;padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--p-surface-200);border-radius:4px;background:transparent;color:var(--p-text-muted-color);cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap}.layer-tree .section-tab i{font-size:11px}.layer-tree .section-tab:hover{background:var(--p-surface-100);color:var(--p-text-color)}.layer-tree .section-tab--active{background:#cb909026;border-color:#cb9090;color:#cb9090}.layer-tree .section-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;font-size:10px;font-weight:600;background:#cb9090;color:#fff;border-radius:8px}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:0 1 auto;min-width:4.5em}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-sub{font-style:normal;font-weight:400;color:#535d6e}.layer-tree .node-template{flex:none;padding:0 4px;border-radius:4px;background:#8a3a72;color:#fff;font:500 10px Fira Code,Fira Mono,monospace;line-height:16px}.layer-tree .tree-more{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;font-size:12px;font-style:italic;color:#64748b;border-inline-start:2px solid transparent;cursor:default;-webkit-user-select:none;user-select:none}.layer-tree .tree-more-handle{visibility:hidden}.layer-tree .node-role{flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:0 4px;border:1px dashed #c9a1be;border-radius:4px;background:#f7eef5;color:#8a3a72;font:500 10px Fira Code,Fira Mono,monospace;line-height:16px}.layer-tree .node-lock{flex:none;color:#64748b;font-size:12px}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-rest{display:inline-flex;align-items:center;flex-shrink:0;font-size:12px;color:var(--p-text-muted-color)}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i5.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] }); }
7367
7421
  }
7368
7422
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: LayerTreeComponent, decorators: [{
7369
7423
  type: Component,
7370
- args: [{ selector: 'app-layer-tree', standalone: false, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'page'\" (click)=\"activeSection = 'page'\">\n <i class=\"ph-thin ph-stack\"></i> Page\n </button>\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'global'\" (click)=\"activeSection = 'global'\">\n <i class=\"ph-thin ph-globe\"></i> Global\n @if (flatGlobalTree.length) {\n <span class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n }\n </button>\n </div>\n\n <!-- A node's label with what the shipped Repeater adds to it (boards 4 / 8): its sub-label, the\n template chip, a composite part's role and lock, the hidden-at-rest glyph. -->\n <ng-template #rowLabel let-tn>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\" [attr.title]=\"boundTitle(tn)\">{{ tn.label }}@if (tn.sub) {<em class=\"node-sub\"> \u00B7 {{ tn.sub }}</em>}</span>\n @if (tn.template) {\n <span class=\"node-template\" [title]=\"templateTitle\">{{ templateLabel }}</span>\n }\n @if (tn.node.role) {\n <span class=\"node-role\" [title]=\"roleTitle + ': ' + tn.node.role\">{{ tn.node.role }}</span>\n }\n @if (tn.locked) {\n <i class=\"ph-thin ph-lock-simple node-lock\" [title]=\"lockedPartTitle\" [attr.aria-label]=\"lockedPartTitle\"></i>\n }\n @if (tn.hiddenAtRest) {\n <span class=\"node-rest\" [title]=\"hiddenAtRestTitle\" [attr.aria-label]=\"hiddenAtRestTitle\"><i class=\"ph-thin ph-eye-slash\"></i></span>\n }\n </ng-template>\n\n <!-- The rows a Repeater's source adds are not nodes: ONE muted line after its template, never selectable. -->\n <ng-template #moreRowsLine let-tn let-handle=\"handle\">\n <div class=\"tree-more\" aria-disabled=\"true\" [title]=\"moreRowsTitle\"\n [style.padding-inline-start.px]=\"12 + tn.moreRows.depth * 16\">\n @if (handle) {\n <i class=\"ph-thin ph-dots-six-vertical drag-handle tree-more-handle\" aria-hidden=\"true\"></i>\n }\n <span class=\"expand-spacer\"></span>\n <i class=\"ph-thin ph-rows node-icon\"></i>\n <span class=\"node-label\">{{ moreRowsText(tn.moreRows) }}</span>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500 Page elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeSection === 'page') {\n <div class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n @if (!flatTree.length) {\n <div class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n }\n\n @for (tn of flatTree; track tn.node) {\n <div class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n @if (tn.hasChildren) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n @if (editingNodeId === tn.node.id) {\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n } @else {\n <ng-container *ngTemplateOutlet=\"rowLabel; context: { $implicit: tn }\"></ng-container>\n }\n\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n @if (!tn.locked) {\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n }\n </span>\n </div>\n @if (tn.moreRows) {\n <ng-container *ngTemplateOutlet=\"moreRowsLine; context: { $implicit: tn, handle: true }\"></ng-container>\n }\n }\n </div>\n }\n\n <!-- \u2500\u2500 Global elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeSection === 'global') {\n <div class=\"tree-body\">\n @if (!flatGlobalTree.length) {\n <div class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n }\n\n @for (tn of flatGlobalTree; track tn.node) {\n <div class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n @if (tn.hasChildren) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngTemplateOutlet=\"rowLabel; context: { $implicit: tn }\"></ng-container>\n @if (tn.depth === 0) {\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n }\n\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n @if (tn.moreRows) {\n <ng-container *ngTemplateOutlet=\"moreRowsLine; context: { $implicit: tn }\"></ng-container>\n }\n }\n </div>\n }\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .section-tab{display:inline-flex;align-items:center;gap:4px;flex:1;justify-content:center;padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--p-surface-200);border-radius:4px;background:transparent;color:var(--p-text-muted-color);cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap}.layer-tree .section-tab i{font-size:11px}.layer-tree .section-tab:hover{background:var(--p-surface-100);color:var(--p-text-color)}.layer-tree .section-tab--active{background:#cb909026;border-color:#cb9090;color:#cb9090}.layer-tree .section-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;font-size:10px;font-weight:600;background:#cb9090;color:#fff;border-radius:8px}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1 1 auto;min-width:4.5em}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-sub{font-style:normal;font-weight:400;color:#535d6e}.layer-tree .node-template{flex:none;padding:0 4px;border-radius:4px;background:#8a3a72;color:#fff;font:500 10px Fira Code,Fira Mono,monospace;line-height:16px}.layer-tree .tree-more{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;font-size:12px;font-style:italic;color:#64748b;border-inline-start:2px solid transparent;cursor:default;-webkit-user-select:none;user-select:none}.layer-tree .tree-more-handle{visibility:hidden}.layer-tree .node-role{flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:0 4px;border:1px dashed #c9a1be;border-radius:4px;background:#f7eef5;color:#8a3a72;font:500 10px Fira Code,Fira Mono,monospace;line-height:16px}.layer-tree .node-lock{flex:none;color:#64748b;font-size:12px}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-rest{display:inline-flex;align-items:center;flex-shrink:0;font-size:12px;color:var(--p-text-muted-color)}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"] }]
7424
+ args: [{ selector: 'app-layer-tree', standalone: false, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'page'\" (click)=\"activeSection = 'page'\">\n <i class=\"ph-thin ph-stack\"></i> {{ words.page }}\n </button>\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'global'\" (click)=\"activeSection = 'global'\">\n <i class=\"ph-thin ph-globe\"></i> {{ words.global }}\n @if (flatGlobalTree.length) {\n <span class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n }\n </button>\n </div>\n\n <!-- A node's label with what the shipped Repeater adds to it (boards 4 / 8): its sub-label, the\n template chip, a composite part's role and lock, the hidden-at-rest glyph. -->\n <ng-template #rowLabel let-tn>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\" [attr.title]=\"rowTitle(tn)\">{{ tn.label }}@if (tn.sub) {<em class=\"node-sub\"> \u00B7 {{ tn.sub }}</em>}</span>\n @if (tn.template) {\n <span class=\"node-template\" [title]=\"templateTitle\">{{ templateLabel }}</span>\n }\n @if (tn.node.role) {\n <span class=\"node-role\" [title]=\"roleTitle + ': ' + tn.node.role\">{{ tn.node.role }}</span>\n }\n @if (tn.locked) {\n <i class=\"ph-thin ph-lock-simple node-lock\" [title]=\"lockedPartTitle\" [attr.aria-label]=\"lockedPartTitle\"></i>\n }\n @if (tn.hiddenAtRest) {\n <span class=\"node-rest\" [title]=\"hiddenAtRestTitle\" [attr.aria-label]=\"hiddenAtRestTitle\"><i class=\"ph-thin ph-eye-slash\"></i></span>\n }\n </ng-template>\n\n <!-- The rows a Repeater's source adds are not nodes: ONE muted line after its template, never selectable. -->\n <ng-template #moreRowsLine let-tn let-handle=\"handle\">\n <div class=\"tree-more\" aria-disabled=\"true\" [title]=\"moreRowsTitle\"\n [style.padding-inline-start.px]=\"12 + tn.moreRows.depth * 16\">\n @if (handle) {\n <i class=\"ph-thin ph-dots-six-vertical drag-handle tree-more-handle\" aria-hidden=\"true\"></i>\n }\n <span class=\"expand-spacer\"></span>\n <i class=\"ph-thin ph-rows node-icon\"></i>\n <span class=\"node-label\">{{ moreRowsText(tn.moreRows) }}</span>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500 Page elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeSection === 'page') {\n <div class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n @if (!flatTree.length) {\n <div class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>{{ words.empty }}</span>\n </div>\n }\n\n @for (tn of flatTree; track tn.node) {\n <div class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n @if (tn.hasChildren) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n @if (editingNodeId === tn.node.id) {\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n } @else {\n <ng-container *ngTemplateOutlet=\"rowLabel; context: { $implicit: tn }\"></ng-container>\n }\n\n <span class=\"node-actions\">\n <button class=\"action-btn\" [title]=\"words.rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" [title]=\"words.duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n @if (!tn.locked) {\n <button class=\"action-btn action-btn--delete\" [title]=\"words.delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n }\n </span>\n </div>\n @if (tn.moreRows) {\n <ng-container *ngTemplateOutlet=\"moreRowsLine; context: { $implicit: tn, handle: true }\"></ng-container>\n }\n }\n </div>\n }\n\n <!-- \u2500\u2500 Global elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (activeSection === 'global') {\n <div class=\"tree-body\">\n @if (!flatGlobalTree.length) {\n <div class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>{{ words.globalEmpty }}</span>\n </div>\n }\n\n @for (tn of flatGlobalTree; track tn.node) {\n <div class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n @if (tn.hasChildren) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngTemplateOutlet=\"rowLabel; context: { $implicit: tn }\"></ng-container>\n @if (tn.depth === 0) {\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n }\n\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" [title]=\"words.delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n @if (tn.moreRows) {\n <ng-container *ngTemplateOutlet=\"moreRowsLine; context: { $implicit: tn }\"></ng-container>\n }\n }\n </div>\n }\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .section-tab{display:inline-flex;align-items:center;gap:4px;flex:1;justify-content:center;padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--p-surface-200);border-radius:4px;background:transparent;color:var(--p-text-muted-color);cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap}.layer-tree .section-tab i{font-size:11px}.layer-tree .section-tab:hover{background:var(--p-surface-100);color:var(--p-text-color)}.layer-tree .section-tab--active{background:#cb909026;border-color:#cb9090;color:#cb9090}.layer-tree .section-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;font-size:10px;font-weight:600;background:#cb9090;color:#fff;border-radius:8px}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:0 1 auto;min-width:4.5em}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-sub{font-style:normal;font-weight:400;color:#535d6e}.layer-tree .node-template{flex:none;padding:0 4px;border-radius:4px;background:#8a3a72;color:#fff;font:500 10px Fira Code,Fira Mono,monospace;line-height:16px}.layer-tree .tree-more{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;font-size:12px;font-style:italic;color:#64748b;border-inline-start:2px solid transparent;cursor:default;-webkit-user-select:none;user-select:none}.layer-tree .tree-more-handle{visibility:hidden}.layer-tree .node-role{flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:0 4px;border:1px dashed #c9a1be;border-radius:4px;background:#f7eef5;color:#8a3a72;font:500 10px Fira Code,Fira Mono,monospace;line-height:16px}.layer-tree .node-lock{flex:none;color:#64748b;font-size:12px}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-rest{display:inline-flex;align-items:center;flex-shrink:0;font-size:12px;color:var(--p-text-muted-color)}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"] }]
7371
7425
  }], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: DragEngineService }], propDecorators: { layout: [{
7372
7426
  type: Input
7373
7427
  }], globalElements: [{
@@ -9601,7 +9655,14 @@ class BuilderDialogComponent {
9601
9655
  this.cdr.markForCheck();
9602
9656
  }
9603
9657
  openRuntime() { this.runtimeOpen = true; this.dismissed = false; this.recompute(); }
9604
- closeRuntime() { this.runtimeOpen = false; this.dismissed = true; this.recompute(); }
9658
+ closeRuntime() {
9659
+ this.runtimeOpen = false;
9660
+ // Unpin it first (a free-standing dialog the selection opened, A4) — that emits and clears
9661
+ // `dismissed`; then hide it even while the selection still sits inside a revealed part (R2).
9662
+ this.designState?.dismiss(this.nodeId);
9663
+ this.dismissed = true;
9664
+ this.recompute();
9665
+ }
9605
9666
  ngOnInit() {
9606
9667
  // Board A4: "Closed later? Click it [in the tree] and it reopens." Selection IS the
9607
9668
  // reopen signal — tree row, breadcrumb crumb, target-picker jump all land here.
@@ -9620,8 +9681,10 @@ class BuilderDialogComponent {
9620
9681
  // stays shut because nothing is selected on load.
9621
9682
  //
9622
9683
  // CC1 (R2, board 3): with the canvas design state present, "selected" means the selection is the
9623
- // dialog OR ANY PART INSIDE IT (a drawer link picked from the tree), and DESELECTING closes it
9624
- // again — a closed overlay has no canvas presence. The "Showing on canvas" switcher holds it open.
9684
+ // dialog OR ANY PART INSIDE IT (a drawer link picked from the tree). A composite PART (the
9685
+ // drawer, it carries a role) closes again when the selection leaves it; a free-standing Dialog
9686
+ // stays open until its X / backdrop / the eye closes it, as A4 has it (selectionRevealOf).
9687
+ // The "Showing on canvas" switcher holds a part open.
9625
9688
  if (this.designState) {
9626
9689
  this.stateSub = this.designState.state$.subscribe(() => { this.dismissed = false; this.recompute(); });
9627
9690
  }
@@ -12480,5 +12543,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
12480
12543
  * Generated bundle index. Do not edit.
12481
12544
  */
12482
12545
 
12483
- export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BOUND_ICON, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_FONT_LINK_ATTR, BUILDER_TOKEN_SCOPE_CLASS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDialogComponent, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CANVAS_EMPTY_IMAGE_LABEL_EN, CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_ROOT_RESET_CSS, COMPOSITE_PRESETS, CanvasDesignStateService, CanvasShowState, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, CompositePresetId, DEFAULT_ESSENTIAL_TRAITS, DRAG_HANDLE_ATTR, DROP_REFUSED_CLASS, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, EDITOR_CHROME_ATTRIBUTE, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_STATE_BLOCK_TYPES, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, GalleryConfig, GroupStyleConfig, HIDDEN_AT_REST_LABEL_KEY, HIDDEN_AT_REST_TYPES, INSERT_REFUSAL_LABEL_KEYS, INTERACTION_STATES, INVERSE_ACTION, IOX_BUILDER_CANVAS, IOX_BUILDER_LABELS, IOX_DATA_SOURCE_ENDPOINTS, InsertRefusal, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LAYOUT_PRESETS, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PALETTE_PRESET_DEFS, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, REPEATER_SOURCE_PROP, REPEATER_TYPE, ROOT_ESCAPE_PX, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, RootZone, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StructureFix, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SubmitButtonComponentConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VALIDATION_STATES, VALIDATION_STATE_CLASS, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, auditNodes, bindingLabel, bindingLabels, buildCompositePreset, buildConsentPreset, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, builderCompositeFactory, canFluidise, canvasEmptyImageLabel, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropTargetLabel, escapesRoot, essentialTraitsFor, findNodeLocation, findParentOf, flattenLayerTree, flattenSchemaFields, fluidiseProps, generateNodeId, insertRefusal, insertRefusalLabel, isHiddenAt, isHiddenAtRest, isSpecimen, markSpecimen, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, nodeStyleMap, pathToNode, refreshSubtree, removalRefusal, reorderProps, repeaterSourceAlias, repeaterSubLabel, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, rootEscapeBand, rootZone, setStyleTrait, setTrait, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, syncNodeInputs, textNode, upsertNodeStyles, visibilityProps, walkNodes, wrapNode };
12546
+ export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BOUND_ICON, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_FONT_LINK_ATTR, BUILDER_TOKEN_SCOPE_CLASS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDialogComponent, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CANVAS_EMPTY_IMAGE_LABEL_EN, CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_ROOT_RESET_CSS, COMPOSITE_PRESETS, CanvasDesignStateService, CanvasShowState, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, CompositePresetId, DEFAULT_ESSENTIAL_TRAITS, DRAG_HANDLE_ATTR, DROP_REFUSED_CLASS, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, EDITOR_CHROME_ATTRIBUTE, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_STATE_BLOCK_TYPES, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, GalleryConfig, GroupStyleConfig, HIDDEN_AT_REST_LABEL_KEY, HIDDEN_AT_REST_TYPES, INSERT_REFUSAL_LABEL_KEYS, INTERACTION_STATES, INVERSE_ACTION, IOX_BUILDER_CANVAS, IOX_BUILDER_LABELS, IOX_DATA_SOURCE_ENDPOINTS, InsertRefusal, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LAYOUT_PRESETS, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PALETTE_PRESET_DEFS, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, REPEATER_SOURCE_PROP, REPEATER_TYPE, ROOT_ESCAPE_PX, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, RootZone, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SelectionReveal, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StructureFix, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SubmitButtonComponentConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VALIDATION_STATES, VALIDATION_STATE_CLASS, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, auditNodes, bindingLabel, bindingLabels, buildCompositePreset, buildConsentPreset, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, builderCompositeFactory, canFluidise, canvasEmptyImageLabel, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropTargetLabel, escapesRoot, essentialTraitsFor, findNodeLocation, findParentOf, flattenLayerTree, flattenSchemaFields, fluidiseProps, generateNodeId, insertRefusal, insertRefusalLabel, isHiddenAt, isHiddenAtRest, isSpecimen, markSpecimen, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, nodeStyleMap, pathToNode, refreshSubtree, removalRefusal, reorderProps, repeaterSourceAlias, repeaterSubLabel, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, rootEscapeBand, rootZone, selectionRevealOf, setStyleTrait, setTrait, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, syncNodeInputs, textNode, upsertNodeStyles, visibilityProps, walkNodes, wrapNode };
12484
12547
  //# sourceMappingURL=vectoriox-iox-builder.mjs.map