@vectoriox/iox-builder 9.5.2 → 9.5.4

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.
@@ -1671,6 +1671,13 @@ const BUILDER_CHROME_EN = Object.freeze({
1671
1671
  'builder.chrome.repeaterMoreRows': '{count} more rows from {alias}',
1672
1672
  'builder.chrome.repeaterMoreRow': '{count} more row from {alias}',
1673
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',
1674
1681
  });
1675
1682
  /** The layer tree's eye-slash title on a part hidden at rest (a closed drawer, R2). */
1676
1683
  const HIDDEN_AT_REST_LABEL_KEY = 'builder.chrome.hiddenAtRest';
@@ -7210,6 +7217,11 @@ const MORE_ROW_KEY = 'builder.chrome.repeaterMoreRow';
7210
7217
  const MORE_ROWS_HINT_KEY = 'builder.chrome.repeaterMoreRowsHint';
7211
7218
  /** Panel events after which a row's extras change without the tree's STRUCTURE changing. */
7212
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
+ ];
7213
7225
  class LayerTreeComponent {
7214
7226
  constructor(registry, overlayService, dragEngine) {
7215
7227
  this.registry = registry;
@@ -7234,6 +7246,8 @@ class LayerTreeComponent {
7234
7246
  this.templateLabel = this.labels(TEMPLATE_KEY, BUILDER_CHROME_EN[TEMPLATE_KEY]);
7235
7247
  this.templateTitle = this.labels(TEMPLATE_HINT_KEY, BUILDER_CHROME_EN[TEMPLATE_HINT_KEY]);
7236
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])]));
7237
7251
  /** Cache: component type → icon class */
7238
7252
  this.iconMap = new Map();
7239
7253
  /** Last-seen structural signature — used by ngDoCheck to skip no-op rebuilds. */
@@ -7264,9 +7278,11 @@ class LayerTreeComponent {
7264
7278
  const key = more.count === 1 ? MORE_ROW_KEY : MORE_ROWS_KEY;
7265
7279
  return this.labels(key, BUILDER_CHROME_EN[key]).replace('{count}', String(more.count)).replace('{alias}', more.alias);
7266
7280
  }
7267
- /** The row's title: every binding a bound part holds. */
7268
- boundTitle(tn) {
7269
- 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(' · ');
7270
7286
  }
7271
7287
  // Runs on every CD cycle — exit immediately if structure hasn't changed.
7272
7288
  ngDoCheck() {
@@ -7401,11 +7417,11 @@ class LayerTreeComponent {
7401
7417
  return instance instanceof BuilderRepeaterComponent ? { alias: instance.source, count: instance.items.length } : null;
7402
7418
  }
7403
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 }); }
7404
- 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:3em}.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:none;gap:2px;margin-inline-start:auto;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions,.layer-tree .tree-row:focus-within .node-actions{display:flex}.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"] }] }); }
7405
7421
  }
7406
7422
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: LayerTreeComponent, decorators: [{
7407
7423
  type: Component,
7408
- 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:3em}.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:none;gap:2px;margin-inline-start:auto;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions,.layer-tree .tree-row:focus-within .node-actions{display:flex}.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"] }]
7409
7425
  }], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: DragEngineService }], propDecorators: { layout: [{
7410
7426
  type: Input
7411
7427
  }], globalElements: [{