@vectoriox/iox-builder 9.1.0 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/vectoriox-iox-builder.mjs +651 -195
- package/fesm2022/vectoriox-iox-builder.mjs.map +1 -1
- package/index.d.ts +209 -16
- package/package.json +2 -2
|
@@ -2,12 +2,12 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { Injectable, InjectionToken, Optional, Inject, inject, NgZone, EventEmitter, createComponent, Output, Input, Directive, ChangeDetectionStrategy, Component, ViewChild, effect, ContentChildren, NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/common';
|
|
4
4
|
import { DOCUMENT, CommonModule } from '@angular/common';
|
|
5
|
-
import * as
|
|
5
|
+
import * as i17 from '@angular/forms';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
7
|
import * as i5 from '@angular/cdk/drag-drop';
|
|
8
8
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
9
9
|
import { AccordionModule } from 'primeng/accordion';
|
|
10
|
-
import * as
|
|
10
|
+
import * as i18 from 'primeng/button';
|
|
11
11
|
import { ButtonModule } from 'primeng/button';
|
|
12
12
|
import * as i2$1 from 'primeng/popover';
|
|
13
13
|
import { PopoverModule } from 'primeng/popover';
|
|
@@ -16,12 +16,13 @@ import { SelectModule } from 'primeng/select';
|
|
|
16
16
|
import { TooltipModule } from 'primeng/tooltip';
|
|
17
17
|
import { DialogModule } from 'primeng/dialog';
|
|
18
18
|
import * as i1 from '@vectoriox/iox-ui';
|
|
19
|
-
import { VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme,
|
|
19
|
+
import { VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, LinkType, MAX_LINES_KEY, A11yTrait, BREAKPOINTS, Breakpoint, countOverrides, setOverride, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, composeVirtualTraits, withDefaultHoverTransition, safeSetInput, NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, applyElementStyle, stripSuppressedProps, DeclarationContext, buildPartSelector, planResponsiveRules, resolveStyleProps, buildTargetedStateSelector, stateSelectorSuffix, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, breakpointDef, breakpointForWidth, FormRenderState, ContextStore, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, motionAllowed, finalKeyframe, keyframeStyleEntries, buildInteractionKeyframes, nodeTraitValue, syncNodeA11yAttributes, nodeA11yAttributes, stateNamesIn, partClassName, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, resolveBoundValue, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, buttonBlockHref, IoxPageModule, PALETTE_PRESETS, switchElementStyle, elementStyleById, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
|
|
20
20
|
export { ComponentInstanceRegistryService, VIRTUAL_TRAIT_KEYS, composeVirtualTraits } from '@vectoriox/iox-ui';
|
|
21
21
|
import Lenis from 'lenis';
|
|
22
22
|
import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
|
|
23
23
|
import { findFormContentInHosts, formContentInHost, isInteractiveHost } from '@vectoriox/iox-ui/forms-core';
|
|
24
24
|
import interact from 'interactjs';
|
|
25
|
+
import { structureRefusal, StructureEdit, isLockedPart, buildCardRecipe, buildBadge, buildChip } from '@vectoriox/iox-ui/composite-core';
|
|
25
26
|
import { BEHAVIOUR_ACTION_TYPES, SERVER_ELEMENT_ACTION_TYPES, CONSENT_ACTION_TYPES } from '@vectoriox/iox-ui/actions-core';
|
|
26
27
|
import Moveable from 'moveable';
|
|
27
28
|
import Selecto from 'selecto';
|
|
@@ -162,7 +163,14 @@ const TARGETED_STATE_BASE = {
|
|
|
162
163
|
const VALIDATION_STATES = [...VALIDATION_STATES$1];
|
|
163
164
|
/** The host class each validation state maps to. */
|
|
164
165
|
const VALIDATION_STATE_CLASS = VALIDATION_STATE_CLASS$1;
|
|
165
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Component states (CC1 N2; the Style panel's "Component state" group): a node is in them because of
|
|
168
|
+
* WHAT it is, not where the pointer is — so they compile to an attribute the renderers write
|
|
169
|
+
* (`stateSelectorSuffix` in @vectoriox/iox-ui), never a pseudo-class. Offered per type by
|
|
170
|
+
* `componentStatesFor`.
|
|
171
|
+
*/
|
|
172
|
+
const COMPONENT_STATES = ['current'];
|
|
173
|
+
const SUPPORTED_STATES = [...INTERACTION_STATES, ...STRUCTURAL_STATES, ...RELATIONAL_STATES, ...TARGETED_STATES, ...VALIDATION_STATES, ...COMPONENT_STATES];
|
|
166
174
|
/**
|
|
167
175
|
* Build a full StyleTraitGroup[] schema populated with values from a flat
|
|
168
176
|
* styleProps map. Used when editing a preset in the Style panel — the panel
|
|
@@ -665,6 +673,68 @@ class ListGroupStyleConfig extends GroupStyleConfig {
|
|
|
665
673
|
function buildColorSchemeTrait() {
|
|
666
674
|
return new TraitConfig(COLOR_SCHEME_TRAIT, 'Section colours', TraitInputType.Select, COLOR_SCHEMES.map(s => ({ label: s.label, value: s.id })), ColorScheme.Default);
|
|
667
675
|
}
|
|
676
|
+
const LINK_TARGETS = [
|
|
677
|
+
{ label: 'Same tab', value: '_self' },
|
|
678
|
+
{ label: 'New tab', value: '_blank' },
|
|
679
|
+
{ label: 'Parent frame', value: '_parent' },
|
|
680
|
+
{ label: 'Full window', value: '_top' },
|
|
681
|
+
];
|
|
682
|
+
/**
|
|
683
|
+
* ONE link model (composite-components plan T5 / §6c): Link Type (external URL or website page — or none),
|
|
684
|
+
* the URL, the page route and the target. Link, Linked Container, ButtonBlock and the Heading (Card v2's
|
|
685
|
+
* title) all declare their link through this; the Bindings panel's Link section edits exactly these.
|
|
686
|
+
*/
|
|
687
|
+
function linkTraits(options = {}) {
|
|
688
|
+
const urlTrait = options.urlTrait ?? 'url';
|
|
689
|
+
const types = [
|
|
690
|
+
...(options.allowNone ? [{ label: 'No link', value: LinkType.None }] : []),
|
|
691
|
+
{ label: 'External URL', value: LinkType.External },
|
|
692
|
+
{ label: 'Website Page', value: LinkType.Page },
|
|
693
|
+
];
|
|
694
|
+
return [
|
|
695
|
+
new TraitConfig('linkType', 'Link Type', TraitInputType.Select, types, options.allowNone ? LinkType.None : LinkType.External, false, options.showWhen),
|
|
696
|
+
new TraitConfig(urlTrait, 'URL', TraitInputType.Text, undefined, options.urlDefault ?? 'https://', false, { trait: 'linkType', values: LinkType.External }),
|
|
697
|
+
new TraitConfig('pageRoute', 'Page Route', TraitInputType.Text, undefined, '', false, { trait: 'linkType', values: LinkType.Page }),
|
|
698
|
+
new TraitConfig('target', 'Target', TraitInputType.Select, options.targets ?? LINK_TARGETS, '_self', false, options.showWhen ?? (options.allowNone ? { trait: 'linkType', values: [LinkType.External, LinkType.Page] } : undefined)),
|
|
699
|
+
];
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* "Max lines" (Card v2, board 8: opt-in, no clamp by default) — a Typography trait on the types that hold
|
|
703
|
+
* running words (Heading, Text). `none` clamps nothing; a number clamps the words to that many lines with
|
|
704
|
+
* an ellipsis, composed by the shared compiler (`composeVirtualTraits`), so both renderers clamp alike.
|
|
705
|
+
*/
|
|
706
|
+
function withMaxLines(groups) {
|
|
707
|
+
const typography = groups.find(g => g.category === StyleCategory.Typography);
|
|
708
|
+
typography?.traits.push(new TraitConfig(MAX_LINES_KEY, 'Max lines', TraitInputType.Select, [{ label: 'No limit', value: 'none' }, ...['1', '2', '3', '4', '5', '6'].map(n => ({ label: n, value: n }))], 'none'));
|
|
709
|
+
return groups;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* "Accessibility ▸ Accessible name" (CC1 N6, board 8): the name an icon-only control is announced by —
|
|
713
|
+
* rendered as `aria-label` by every renderer through the shared `nodeA11yAttributes`. Empty = the
|
|
714
|
+
* control's own text names it; the responsive audit flags an icon-only control left empty.
|
|
715
|
+
*/
|
|
716
|
+
function buildAccessibleNameTraits() {
|
|
717
|
+
return [
|
|
718
|
+
new TraitConfig('__accessibilityLabel', 'Accessibility', TraitInputType.SectionLabel),
|
|
719
|
+
new TraitConfig(A11yTrait.AccessibleName, 'Accessible name', TraitInputType.Text, undefined, ''),
|
|
720
|
+
];
|
|
721
|
+
}
|
|
722
|
+
/** The breakpoints a Dialog may close above, widest first, plus "Never" (the default, empty). */
|
|
723
|
+
const DIALOG_CLOSE_ABOVE_OPTIONS = [
|
|
724
|
+
{ label: 'Never', value: '' },
|
|
725
|
+
...BREAKPOINTS.filter(b => b.id !== Breakpoint.Base && b.maxWidth !== null)
|
|
726
|
+
.map(b => ({ label: `${b.label} (${b.maxWidth}px)`, value: b.id })),
|
|
727
|
+
];
|
|
728
|
+
/**
|
|
729
|
+
* "Responsive ▸ Close when wider than" (CC1 N4, board 8): a drawer opened on a phone closes itself when
|
|
730
|
+
* the window widens past this breakpoint. The engine listens on `matchMedia`; default Never.
|
|
731
|
+
*/
|
|
732
|
+
function buildDialogCloseAboveTraits() {
|
|
733
|
+
return [
|
|
734
|
+
new TraitConfig('__responsiveLabel', 'Responsive', TraitInputType.SectionLabel),
|
|
735
|
+
new TraitConfig(A11yTrait.CloseAbove, 'Close when wider than', TraitInputType.Select, DIALOG_CLOSE_ABOVE_OPTIONS, ''),
|
|
736
|
+
];
|
|
737
|
+
}
|
|
668
738
|
function buildFullStyleTraits() {
|
|
669
739
|
return [
|
|
670
740
|
new PositionGroupStyleConfig(),
|
|
@@ -902,6 +972,28 @@ function wrapNode(roots, target, wrapper) {
|
|
|
902
972
|
return wrapper;
|
|
903
973
|
}
|
|
904
974
|
|
|
975
|
+
/**
|
|
976
|
+
* Parts that are HIDDEN AT REST (complex-components R2, board 3): on the live site they are not on
|
|
977
|
+
* screen until something opens them, so on the canvas they show only while selected into or switched
|
|
978
|
+
* open ("Showing on canvas"). The layer tree marks them with the eye-slash glyph. Today: a Dialog (the
|
|
979
|
+
* mobile menu's drawer, a consent banner). CC2 adds a collapsed Accordion panel and an inactive tab.
|
|
980
|
+
*/
|
|
981
|
+
const HIDDEN_AT_REST_TYPES = ['Dialog'];
|
|
982
|
+
function isHiddenAtRest(node) {
|
|
983
|
+
return !!node?.type && HIDDEN_AT_REST_TYPES.includes(node.type);
|
|
984
|
+
}
|
|
985
|
+
/** Root → `target`, inclusive, across `roots` (page layout and globals), or [] when it is not there. */
|
|
986
|
+
function pathToNode(roots, target) {
|
|
987
|
+
for (const root of roots) {
|
|
988
|
+
if (root === target)
|
|
989
|
+
return [root];
|
|
990
|
+
const below = pathToNode(root.children ?? [], target);
|
|
991
|
+
if (below.length)
|
|
992
|
+
return [root, ...below];
|
|
993
|
+
}
|
|
994
|
+
return [];
|
|
995
|
+
}
|
|
996
|
+
|
|
905
997
|
var PanelEventTypes;
|
|
906
998
|
(function (PanelEventTypes) {
|
|
907
999
|
PanelEventTypes["ELEMENT_SELECT"] = "elementselect";
|
|
@@ -949,6 +1041,11 @@ var PanelEventTypes;
|
|
|
949
1041
|
* through also runs. This event is that path for code outside `BuilderComponent` itself,
|
|
950
1042
|
* mirroring what `onTreeNodeSelect`/`onCrumbSelect` already do internally. */
|
|
951
1043
|
PanelEventTypes["SELECT_NODE"] = "selectnode";
|
|
1044
|
+
/** A panel rewrote a node's SUBTREE in place — its children (a Card's Parts), its layout styles (Card ▸
|
|
1045
|
+
* Layout), a part's element style or scheme. Carries `{ node }`. The builder re-renders the subtree,
|
|
1046
|
+
* re-registers every node's rules and design classes, refreshes a Repeater holding it, and records ONE
|
|
1047
|
+
* undo step (LAYOUT_CHANGED). */
|
|
1048
|
+
PanelEventTypes["NODE_STRUCTURE_CHANGED"] = "nodestructurechanged";
|
|
952
1049
|
})(PanelEventTypes || (PanelEventTypes = {}));
|
|
953
1050
|
class PanelEventService {
|
|
954
1051
|
constructor() {
|
|
@@ -1342,6 +1439,8 @@ var AuditCode;
|
|
|
1342
1439
|
AuditCode["OffscreenAbsolute"] = "offscreen-absolute";
|
|
1343
1440
|
/** Form content inside a link or a button — a STRUCTURE finding, true at every width (form-nesting-audit.util). */
|
|
1344
1441
|
AuditCode["FormInLink"] = "form-in-link";
|
|
1442
|
+
/** An icon-only button or link with no Accessible name — announced as an unnamed control (CC1 N6). */
|
|
1443
|
+
AuditCode["UnnamedControl"] = "unnamed-control";
|
|
1345
1444
|
})(AuditCode || (AuditCode = {}));
|
|
1346
1445
|
/** A fix that changes the tree, not a style or a trait — applied as one page-history step. */
|
|
1347
1446
|
var StructureFix;
|
|
@@ -1443,7 +1542,15 @@ function auditNodes(nodes, viewportWidth) {
|
|
|
1443
1542
|
}
|
|
1444
1543
|
}
|
|
1445
1544
|
}
|
|
1446
|
-
// 7. An
|
|
1545
|
+
// 7. An icon-only control with no name (WCAG 4.1.2): a screen reader says "button" and nothing
|
|
1546
|
+
// else. No auto-fix — only the author knows what it does; the finding selects it.
|
|
1547
|
+
if (node.unnamedControl) {
|
|
1548
|
+
findings.push({
|
|
1549
|
+
nodeId: node.id, nodeName: name, code: AuditCode.UnnamedControl, severity: AuditSeverity.Error,
|
|
1550
|
+
message: `${name} shows only an icon and has no accessible name — screen readers announce it as an unnamed control. Name it in Bindings ▸ Accessibility.`,
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
// 8. An out-of-flow node that has left the frame. Measured, not inferred: a negative offset
|
|
1447
1554
|
// is legitimate design until it actually puts the content beyond the edge.
|
|
1448
1555
|
if (OUT_OF_FLOW.has(String(styles['position'] ?? '')) && node.left !== undefined && node.width > 0) {
|
|
1449
1556
|
const right = node.left + node.width;
|
|
@@ -1529,9 +1636,17 @@ const BUILDER_CHROME_EN = Object.freeze({
|
|
|
1529
1636
|
'builder.chrome.refuseNestedForm': 'A form can’t go inside another form. Place it outside the form.',
|
|
1530
1637
|
'builder.chrome.refuseStateBlock': 'Success and error messages belong to their form. They can’t be placed on their own.',
|
|
1531
1638
|
'builder.chrome.refuseFormInLink': 'A form can’t go inside a link or a button — a click in a field would follow the link.',
|
|
1639
|
+
'builder.chrome.refuseLockedPart': 'This part keeps the card together and can’t be deleted. Delete the whole card, or switch parts off in Card ▸ Parts.',
|
|
1640
|
+
'builder.chrome.refuseLockedPartMove': 'This part belongs where it is in its card — it can’t be moved out.',
|
|
1641
|
+
'builder.chrome.lockedPart': 'Locked part — can’t be deleted or moved out',
|
|
1642
|
+
'builder.chrome.compositeRole': 'Composite role',
|
|
1643
|
+
'builder.chrome.kindCard': 'Card',
|
|
1532
1644
|
'builder.chrome.auditFormInLink': 'Is inside {host} — a click in a field would follow the link.',
|
|
1533
1645
|
'builder.chrome.auditMoveFormOut': 'Move it out',
|
|
1646
|
+
'builder.chrome.hiddenAtRest': 'Hidden at rest — select to show it',
|
|
1534
1647
|
});
|
|
1648
|
+
/** The layer tree's eye-slash title on a part hidden at rest (a closed drawer, R2). */
|
|
1649
|
+
const HIDDEN_AT_REST_LABEL_KEY = 'builder.chrome.hiddenAtRest';
|
|
1535
1650
|
const CANVAS_EMPTY_IMAGE_LABEL_KEY = 'builder.chrome.addImage';
|
|
1536
1651
|
const CANVAS_EMPTY_IMAGE_LABEL_EN = BUILDER_CHROME_EN[CANVAS_EMPTY_IMAGE_LABEL_KEY];
|
|
1537
1652
|
/**
|
|
@@ -1711,6 +1826,95 @@ function preventNativeCanvasDrag(event) {
|
|
|
1711
1826
|
event.preventDefault();
|
|
1712
1827
|
}
|
|
1713
1828
|
|
|
1829
|
+
/** The input a component declares to receive its empty-image placeholder (the Image). */
|
|
1830
|
+
const EMPTY_SRC_INPUT = 'emptySrc';
|
|
1831
|
+
/** The editing canvas's "Add image" label, through the host's i18n. */
|
|
1832
|
+
function canvasEmptyImageLabel(labels) {
|
|
1833
|
+
return labels(CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_EMPTY_IMAGE_LABEL_EN);
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Push a node's empty-image placeholder (design-system DS3) to its rendered component — a no-op for
|
|
1837
|
+
* a component that declares no `emptySrc`. The placeholder depends on the MARKER only, never on the
|
|
1838
|
+
* source, so it is set once at render and again whenever the node's element style changes.
|
|
1839
|
+
*/
|
|
1840
|
+
function syncEmptyImagePlaceholder(ref, node, emptyLabel) {
|
|
1841
|
+
// Panels hold the selected node's ref untyped; anything without a component type has no inputs.
|
|
1842
|
+
if (!ref?.componentType || !declaredInputNames(ref.componentType).has(EMPTY_SRC_INPUT))
|
|
1843
|
+
return;
|
|
1844
|
+
ref.setInput(EMPTY_SRC_INPUT, emptyImagePlaceholder(node, emptyLabel));
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Everything a node's element style / colour scheme decides on its rendered host, in ONE call: the
|
|
1848
|
+
* design classes (marker + scheme) and the empty-image placeholder. Every place that switches or
|
|
1849
|
+
* resets a node's style on the canvas calls this, so an adopted Image gains its placeholder at once
|
|
1850
|
+
* instead of after a reload (DS3 D2).
|
|
1851
|
+
*/
|
|
1852
|
+
function syncNodeDesign(ref, node, emptyLabel) {
|
|
1853
|
+
syncNodeDesignClasses(ref?.location?.nativeElement ?? null, node);
|
|
1854
|
+
syncEmptyImagePlaceholder(ref, node, emptyLabel);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
/** States whose rule needs the LIVE DOM to scope (a parent's / a trigger's id) — the RenderDirective owns them. */
|
|
1858
|
+
const DOM_SCOPED_STATES = new Set([...RELATIONAL_STATES, ...TARGETED_STATES]);
|
|
1859
|
+
/** A node's base style map, from its style traits (the live values the Style panel edits). */
|
|
1860
|
+
function nodeStyleMap(node) {
|
|
1861
|
+
const base = {};
|
|
1862
|
+
for (const group of node.styleTraits ?? [])
|
|
1863
|
+
for (const trait of group.traits)
|
|
1864
|
+
if (trait.default !== undefined)
|
|
1865
|
+
base[trait.name] = trait.default;
|
|
1866
|
+
return base;
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Re-register a node's compiled rules from its CURRENT style values — the base rule (with the default
|
|
1870
|
+
* hover transition), its self-scoped state rules and its breakpoint layers — exactly as the
|
|
1871
|
+
* RenderDirective registers them at render. For edits that rewrite a node's styles from outside the Style
|
|
1872
|
+
* panel (a Card's layout preset, a reset to defaults).
|
|
1873
|
+
*/
|
|
1874
|
+
function upsertNodeStyles(styleRegistry, node) {
|
|
1875
|
+
const cssId = node.styleId ?? node.id;
|
|
1876
|
+
if (!cssId)
|
|
1877
|
+
return;
|
|
1878
|
+
const base = nodeStyleMap(node);
|
|
1879
|
+
styleRegistry.setNodeType(cssId, node.type);
|
|
1880
|
+
styleRegistry.upsert(cssId, composeVirtualTraits(withDefaultHoverTransition(base, node.stateStyles, node.elementStyle)));
|
|
1881
|
+
for (const [state, map] of Object.entries(node.stateStyles ?? {})) {
|
|
1882
|
+
if (DOM_SCOPED_STATES.has(state) || !map || !Object.keys(map).length)
|
|
1883
|
+
continue;
|
|
1884
|
+
styleRegistry.upsert(cssId, composeVirtualTraits(map, base), state);
|
|
1885
|
+
}
|
|
1886
|
+
styleRegistry.upsertResponsive(cssId, base, node.stateStyles, node.responsive);
|
|
1887
|
+
}
|
|
1888
|
+
/** Push every content trait of a node to its rendered component (a Card title's link switched on). */
|
|
1889
|
+
function syncNodeInputs(ref, node) {
|
|
1890
|
+
if (!ref)
|
|
1891
|
+
return;
|
|
1892
|
+
for (const trait of node.traits ?? []) {
|
|
1893
|
+
if (trait.type === 'sectionLabel')
|
|
1894
|
+
continue;
|
|
1895
|
+
safeSetInput(ref, trait.name, trait.default);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
/** Visit a node and every descendant. */
|
|
1899
|
+
function walkNodes(node, visit) {
|
|
1900
|
+
visit(node);
|
|
1901
|
+
for (const child of node.children ?? [])
|
|
1902
|
+
walkNodes(child, visit);
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* After a subtree was rewritten in place (PanelEventTypes.NODE_STRUCTURE_CHANGED): every node's rules,
|
|
1906
|
+
* design classes (marker, scheme, role) and inputs re-applied. `refOf` finds a node's rendered component.
|
|
1907
|
+
*/
|
|
1908
|
+
function refreshSubtree(node, styleRegistry, refOf, emptyImageLabel) {
|
|
1909
|
+
walkNodes(node, n => {
|
|
1910
|
+
upsertNodeStyles(styleRegistry, n);
|
|
1911
|
+
const ref = refOf(n);
|
|
1912
|
+
syncNodeDesign(ref, n, emptyImageLabel);
|
|
1913
|
+
syncNodeInputs(ref, n);
|
|
1914
|
+
ref?.changeDetectorRef.markForCheck();
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1714
1918
|
class ComponentRegistryService {
|
|
1715
1919
|
constructor() {
|
|
1716
1920
|
this.components = {};
|
|
@@ -2019,6 +2223,11 @@ var InsertRefusal;
|
|
|
2019
2223
|
/** Form content inside a link or a button (Doron, 2026-10-20) — HTML forbids interactive content in
|
|
2020
2224
|
* `<a>`/`<button>`, and a click in a field would follow the link. The table is forms-core's. */
|
|
2021
2225
|
InsertRefusal["FormInInteractive"] = "formInInteractive";
|
|
2226
|
+
/** A composite's LOCKED part (a card's Title or Content column) moved out of its own parent — the
|
|
2227
|
+
* structure guard (composite-core `structureRefusal`). Reordering inside the parent never asks. */
|
|
2228
|
+
InsertRefusal["LockedPartMove"] = "lockedPartMove";
|
|
2229
|
+
/** A composite's LOCKED part deleted (Delete key, toolbar, layer tree, Make global): refused. */
|
|
2230
|
+
InsertRefusal["LockedPart"] = "lockedPart";
|
|
2022
2231
|
})(InsertRefusal || (InsertRefusal = {}));
|
|
2023
2232
|
const FORM_STATE_BLOCK_TYPES = new Set(['FormSuccess', 'FormError']);
|
|
2024
2233
|
/** The builder-chrome label that says each refusal on screen (English in `BUILDER_CHROME_EN`). */
|
|
@@ -2026,6 +2235,8 @@ const INSERT_REFUSAL_LABEL_KEYS = Object.freeze({
|
|
|
2026
2235
|
[InsertRefusal.NestedForm]: 'builder.chrome.refuseNestedForm',
|
|
2027
2236
|
[InsertRefusal.StateBlockAlone]: 'builder.chrome.refuseStateBlock',
|
|
2028
2237
|
[InsertRefusal.FormInInteractive]: 'builder.chrome.refuseFormInLink',
|
|
2238
|
+
[InsertRefusal.LockedPartMove]: 'builder.chrome.refuseLockedPartMove',
|
|
2239
|
+
[InsertRefusal.LockedPart]: 'builder.chrome.refuseLockedPart',
|
|
2029
2240
|
});
|
|
2030
2241
|
/** The refusal in the host's language (the library's English when no host resolver is provided). */
|
|
2031
2242
|
function insertRefusalLabel(reason, labels = defaultBuilderLabels) {
|
|
@@ -2052,8 +2263,15 @@ function insertRefusal(node, ancestors) {
|
|
|
2052
2263
|
return InsertRefusal.NestedForm;
|
|
2053
2264
|
if (formContentInHost(node, ancestors.some(isInteractiveHost)))
|
|
2054
2265
|
return InsertRefusal.FormInInteractive;
|
|
2266
|
+
if (structureRefusal(node, StructureEdit.Move, ancestors))
|
|
2267
|
+
return InsertRefusal.LockedPartMove;
|
|
2055
2268
|
return null;
|
|
2056
2269
|
}
|
|
2270
|
+
/** Why `node` may not be REMOVED from where it is (deleted, cut, made global), or null when it may: a
|
|
2271
|
+
* composite's locked part stays — its Parts switch or deleting the whole composite are the ways out. */
|
|
2272
|
+
function removalRefusal(node) {
|
|
2273
|
+
return structureRefusal(node, StructureEdit.Delete) ? InsertRefusal.LockedPart : null;
|
|
2274
|
+
}
|
|
2057
2275
|
|
|
2058
2276
|
/**
|
|
2059
2277
|
* ROOT ESCAPE — near the top or bottom edge of a ROOT-level container the drop goes to the PAGE, between
|
|
@@ -2383,8 +2601,9 @@ class StyleRegistryService {
|
|
|
2383
2601
|
// Structural pseudo-classes (first-child, last-child, nth-child) target the
|
|
2384
2602
|
// outer wrapper element so they participate correctly in grid/flex layout.
|
|
2385
2603
|
const prefix = STRUCTURAL_STATES.includes(state) ? 'iox-outer' : 'iox-node';
|
|
2386
|
-
// `focus` compiles to :focus-visible (DS2)
|
|
2387
|
-
|
|
2604
|
+
// `focus` compiles to :focus-visible (DS2) and a component state to its attribute (`current` →
|
|
2605
|
+
// [aria-current="page"], CC1) — shared with the engine via stateSelectorSuffix.
|
|
2606
|
+
return `${prefix}-${nodeId}${stateSelectorSuffix(state)}`;
|
|
2388
2607
|
}
|
|
2389
2608
|
/**
|
|
2390
2609
|
* The inner + outer rule (joined) and the `::marker` rule for one composed style map.
|
|
@@ -2744,6 +2963,16 @@ class DragEngineService {
|
|
|
2744
2963
|
this.insertRefused$.next({ reason, message: insertRefusalLabel(reason, this.labels) });
|
|
2745
2964
|
return reason;
|
|
2746
2965
|
}
|
|
2966
|
+
/**
|
|
2967
|
+
* The ONE check every REMOVAL asks (Delete key, toolbar, layer tree, Make global): null when `node` may
|
|
2968
|
+
* leave its place; otherwise the refusal, announced on `insertRefused$` like any other.
|
|
2969
|
+
*/
|
|
2970
|
+
refuseRemoval(node) {
|
|
2971
|
+
const reason = removalRefusal(node);
|
|
2972
|
+
if (reason)
|
|
2973
|
+
this.insertRefused$.next({ reason, message: insertRefusalLabel(reason, this.labels) });
|
|
2974
|
+
return reason;
|
|
2975
|
+
}
|
|
2747
2976
|
/** The node a payload WOULD insert, without building it: a type string, a block/symbol, or a node. */
|
|
2748
2977
|
payloadNode(data) {
|
|
2749
2978
|
if (typeof data === 'string')
|
|
@@ -3743,6 +3972,88 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
3743
3972
|
type: Injectable
|
|
3744
3973
|
}], ctorParameters: () => [{ type: OverlayService }] });
|
|
3745
3974
|
|
|
3975
|
+
/** What a hidden-at-rest part shows on the canvas when the author picks it in "Showing on canvas". */
|
|
3976
|
+
var CanvasShowState;
|
|
3977
|
+
(function (CanvasShowState) {
|
|
3978
|
+
CanvasShowState["Closed"] = "closed";
|
|
3979
|
+
CanvasShowState["Open"] = "open";
|
|
3980
|
+
})(CanvasShowState || (CanvasShowState = {}));
|
|
3981
|
+
/**
|
|
3982
|
+
* The canvas's DESIGN state (complex-components R2; composite plan §5a's CompositeDesignStateService,
|
|
3983
|
+
* started here for the navigation and reused by Accordion in CC2): which parts that are hidden at rest
|
|
3984
|
+
* — a closed drawer today, a collapsed panel or an inactive tab next — the canvas shows right now.
|
|
3985
|
+
*
|
|
3986
|
+
* Two sources, the Dialog's "selection is the reveal" and the Form's switcher, generalised:
|
|
3987
|
+
* - **the selection** — selecting anything inside a hidden part (tree, breadcrumb, an Items row,
|
|
3988
|
+
* SELECT_NODE) shows it; deselecting hides it again (`setSelectionPath`);
|
|
3989
|
+
* - **"Showing on canvas"** — the Bindings panel's switcher, for the one case a selection cannot express:
|
|
3990
|
+
* the page with the menu open while a part OUTSIDE it is selected (`setShown`).
|
|
3991
|
+
*
|
|
3992
|
+
* Canvas-only: never saved, never a history step. Also carries the canvas page's path, so a link to the
|
|
3993
|
+
* page being edited renders as the current page (N2) — the same decision the live site makes.
|
|
3994
|
+
*/
|
|
3995
|
+
class CanvasDesignStateService {
|
|
3996
|
+
constructor() {
|
|
3997
|
+
this._state$ = new BehaviorSubject({ revealed: new Set(), shown: new Map() });
|
|
3998
|
+
this.state$ = this._state$.asObservable();
|
|
3999
|
+
this._pagePath$ = new BehaviorSubject(null);
|
|
4000
|
+
/** The app path of the page on the canvas (`/work`), or null when unknown. */
|
|
4001
|
+
this.pagePath$ = this._pagePath$.asObservable();
|
|
4002
|
+
}
|
|
4003
|
+
get snapshot() {
|
|
4004
|
+
return this._state$.value;
|
|
4005
|
+
}
|
|
4006
|
+
get pagePath() {
|
|
4007
|
+
return this._pagePath$.value;
|
|
4008
|
+
}
|
|
4009
|
+
setPagePath(path) {
|
|
4010
|
+
const next = path ?? null;
|
|
4011
|
+
if (next !== this._pagePath$.value)
|
|
4012
|
+
this._pagePath$.next(next);
|
|
4013
|
+
}
|
|
4014
|
+
/** The selection moved: the hidden-at-rest parts on its path are revealed, every other one is not. */
|
|
4015
|
+
setSelectionPath(path) {
|
|
4016
|
+
const revealed = new Set(path.filter(isHiddenAtRest).map(n => n.id).filter((id) => !!id));
|
|
4017
|
+
const current = this._state$.value.revealed;
|
|
4018
|
+
if (revealed.size === current.size && [...revealed].every(id => current.has(id)))
|
|
4019
|
+
return;
|
|
4020
|
+
this._state$.next({ ...this._state$.value, revealed });
|
|
4021
|
+
}
|
|
4022
|
+
/** The switcher's choice for one part (null = back to the saved default). */
|
|
4023
|
+
setShown(nodeId, state) {
|
|
4024
|
+
const shown = new Map(this._state$.value.shown);
|
|
4025
|
+
if (state && state !== CanvasShowState.Closed)
|
|
4026
|
+
shown.set(nodeId, state);
|
|
4027
|
+
else
|
|
4028
|
+
shown.delete(nodeId);
|
|
4029
|
+
this._state$.next({ ...this._state$.value, shown });
|
|
4030
|
+
}
|
|
4031
|
+
/** The switcher's current choice for one part. */
|
|
4032
|
+
shownState(nodeId) {
|
|
4033
|
+
return (nodeId && this._state$.value.shown.get(nodeId)) || CanvasShowState.Closed;
|
|
4034
|
+
}
|
|
4035
|
+
/** True when the canvas shows the part: selected into, or switched open. */
|
|
4036
|
+
isShown(nodeId) {
|
|
4037
|
+
if (!nodeId)
|
|
4038
|
+
return false;
|
|
4039
|
+
const { revealed, shown } = this._state$.value;
|
|
4040
|
+
return revealed.has(nodeId) || shown.get(nodeId) === CanvasShowState.Open;
|
|
4041
|
+
}
|
|
4042
|
+
/** True when the part is shown only because the selection is inside it (the switcher's hint line). */
|
|
4043
|
+
isRevealedBySelection(nodeId) {
|
|
4044
|
+
return !!nodeId && this._state$.value.revealed.has(nodeId);
|
|
4045
|
+
}
|
|
4046
|
+
/** A new page on the canvas: nothing is revealed or switched open. */
|
|
4047
|
+
reset() {
|
|
4048
|
+
this._state$.next({ revealed: new Set(), shown: new Map() });
|
|
4049
|
+
}
|
|
4050
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4051
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService }); }
|
|
4052
|
+
}
|
|
4053
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService, decorators: [{
|
|
4054
|
+
type: Injectable
|
|
4055
|
+
}] });
|
|
4056
|
+
|
|
3746
4057
|
// Re-exported from the shared iox-ui package so iox-builder consumers
|
|
3747
4058
|
// don't need to import from two different packages.
|
|
3748
4059
|
|
|
@@ -3774,12 +4085,10 @@ class InteractionEngineService {
|
|
|
3774
4085
|
* element's own attach() fires.
|
|
3775
4086
|
*/
|
|
3776
4087
|
this.pendingPreStates = new Map();
|
|
3777
|
-
/** Action types that animate an element from hidden → visible
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
/** Action types that animate an element from visible → hidden. */
|
|
3782
|
-
this.EXIT_TYPES = new Set(['fadeOut', 'scaleOut', 'hide']);
|
|
4088
|
+
/** Action types that animate an element from hidden → visible / visible → hidden — the SHARED sets
|
|
4089
|
+
* (@vectoriox/iox-ui), the same ones the live site's runtime reads. */
|
|
4090
|
+
this.ENTRANCE_TYPES = ENTRANCE_ACTION_TYPES;
|
|
4091
|
+
this.EXIT_TYPES = EXIT_ACTION_TYPES;
|
|
3783
4092
|
this.SWIPER_ACTIONS = new Set([
|
|
3784
4093
|
'swiperNext', 'swiperPrev', 'swiperGoToSlide',
|
|
3785
4094
|
'swiperAutoplayStart', 'swiperAutoplayStop', 'swiperAutoplayPause',
|
|
@@ -4305,9 +4614,22 @@ class InteractionEngineService {
|
|
|
4305
4614
|
return null;
|
|
4306
4615
|
}
|
|
4307
4616
|
executeAction(element, action) {
|
|
4308
|
-
|
|
4617
|
+
// toggleVisibility HIDES a visible element and SHOWS a hidden one — resolved against the element's
|
|
4618
|
+
// own state, the same rule the live site applies (shared `effectiveActionType`, CC1 N5).
|
|
4619
|
+
const type = effectiveActionType(action.type, element.hasAttribute(INTERACTION_HIDDEN_ATTRIBUTE));
|
|
4620
|
+
const keyframes = this.keyframesFor(type, action);
|
|
4309
4621
|
if (!keyframes.length)
|
|
4310
4622
|
return;
|
|
4623
|
+
if (this.ENTRANCE_TYPES.has(type))
|
|
4624
|
+
element.removeAttribute(INTERACTION_HIDDEN_ATTRIBUTE);
|
|
4625
|
+
else if (this.EXIT_TYPES.has(type))
|
|
4626
|
+
element.setAttribute(INTERACTION_HIDDEN_ATTRIBUTE, '');
|
|
4627
|
+
// Reduced motion (CC1 R1): no animation runs — the element takes its final frame at once.
|
|
4628
|
+
if (!motionAllowed()) {
|
|
4629
|
+
this.applyFrame(element, finalKeyframe(keyframes));
|
|
4630
|
+
this.settleAfter(element, type);
|
|
4631
|
+
return;
|
|
4632
|
+
}
|
|
4311
4633
|
// 'both' = backwards (holds first keyframe during delay) + forwards (holds last keyframe after end).
|
|
4312
4634
|
const anim = element.animate(keyframes, {
|
|
4313
4635
|
duration: action.duration,
|
|
@@ -4315,23 +4637,31 @@ class InteractionEngineService {
|
|
|
4315
4637
|
easing: action.easing,
|
|
4316
4638
|
fill: 'both',
|
|
4317
4639
|
});
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
// disable pointer events so it doesn't
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4640
|
+
anim.finished.then(() => this.settleAfter(element, type)).catch(() => { });
|
|
4641
|
+
}
|
|
4642
|
+
/** What an element is left as once an action is done: a revealed one interactive, a hidden one inert. */
|
|
4643
|
+
settleAfter(element, type) {
|
|
4644
|
+
if (this.ENTRANCE_TYPES.has(type)) {
|
|
4645
|
+
element.style.removeProperty('pointer-events');
|
|
4646
|
+
this.preStatedElements.delete(element);
|
|
4647
|
+
// Builder: clear inline pre-state so cancelling animations later
|
|
4648
|
+
// doesn't leave the element invisible after a play preview.
|
|
4649
|
+
if (this.isBuilderMode())
|
|
4650
|
+
this.clearInlineAnimationStyles(element);
|
|
4651
|
+
}
|
|
4652
|
+
else if (this.EXIT_TYPES.has(type)) {
|
|
4653
|
+
// Once the exit is done the element is invisible — disable pointer events so it doesn't
|
|
4654
|
+
// silently block clicks underneath it.
|
|
4655
|
+
element.style.setProperty('pointer-events', 'none');
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
/** Write a frame inline. On the canvas never a transform: one inside the scaled page breaks Lenis. */
|
|
4659
|
+
applyFrame(element, frame) {
|
|
4660
|
+
const canvas = this.isBuilderMode();
|
|
4661
|
+
for (const [prop, value] of keyframeStyleEntries(frame)) {
|
|
4662
|
+
if (canvas && prop === 'transform')
|
|
4663
|
+
continue;
|
|
4664
|
+
element.style.setProperty(prop, value);
|
|
4335
4665
|
}
|
|
4336
4666
|
}
|
|
4337
4667
|
/**
|
|
@@ -4345,7 +4675,7 @@ class InteractionEngineService {
|
|
|
4345
4675
|
* In production the full keyframe is applied + pointer-events:none.
|
|
4346
4676
|
*/
|
|
4347
4677
|
applyPreState(element, action) {
|
|
4348
|
-
const
|
|
4678
|
+
const keyframes = this.keyframesFor(action.type, action);
|
|
4349
4679
|
if (!keyframes.length)
|
|
4350
4680
|
return;
|
|
4351
4681
|
const first = keyframes[0];
|
|
@@ -4362,7 +4692,7 @@ class InteractionEngineService {
|
|
|
4362
4692
|
}
|
|
4363
4693
|
element.style.setProperty('pointer-events', 'none');
|
|
4364
4694
|
}
|
|
4365
|
-
element.setAttribute(
|
|
4695
|
+
element.setAttribute(INTERACTION_HIDDEN_ATTRIBUTE, '');
|
|
4366
4696
|
this.preStatedElements.add(element);
|
|
4367
4697
|
}
|
|
4368
4698
|
clearInlineAnimationStyles(element) {
|
|
@@ -4370,12 +4700,16 @@ class InteractionEngineService {
|
|
|
4370
4700
|
element.style.removeProperty('transform');
|
|
4371
4701
|
element.style.removeProperty('visibility');
|
|
4372
4702
|
element.style.removeProperty('pointer-events');
|
|
4373
|
-
element.removeAttribute(
|
|
4703
|
+
element.removeAttribute(INTERACTION_HIDDEN_ATTRIBUTE);
|
|
4374
4704
|
}
|
|
4375
4705
|
reverseAction(element, action) {
|
|
4376
|
-
const
|
|
4706
|
+
const keyframes = this.keyframesFor(effectiveActionType(action.type, element.hasAttribute(INTERACTION_HIDDEN_ATTRIBUTE)), action);
|
|
4377
4707
|
if (!keyframes.length)
|
|
4378
4708
|
return;
|
|
4709
|
+
if (!motionAllowed()) {
|
|
4710
|
+
this.applyFrame(element, keyframes[0]);
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4379
4713
|
// Reverse: play the keyframes backwards
|
|
4380
4714
|
element.animate([...keyframes].reverse(), {
|
|
4381
4715
|
duration: action.duration,
|
|
@@ -4383,58 +4717,9 @@ class InteractionEngineService {
|
|
|
4383
4717
|
fill: 'forwards',
|
|
4384
4718
|
});
|
|
4385
4719
|
}
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
switch (action.type) {
|
|
4390
|
-
case 'fadeIn':
|
|
4391
|
-
keyframes = [{ opacity: 0 }, { opacity: 1 }];
|
|
4392
|
-
break;
|
|
4393
|
-
case 'fadeOut':
|
|
4394
|
-
keyframes = [{ opacity: 1 }, { opacity: 0 }];
|
|
4395
|
-
break;
|
|
4396
|
-
case 'moveUp':
|
|
4397
|
-
keyframes = [{ transform: `translateY(${dist}px)`, opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }];
|
|
4398
|
-
break;
|
|
4399
|
-
case 'moveDown':
|
|
4400
|
-
keyframes = [{ transform: `translateY(-${dist}px)`, opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }];
|
|
4401
|
-
break;
|
|
4402
|
-
case 'moveLeft':
|
|
4403
|
-
keyframes = [{ transform: `translateX(${dist}px)`, opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }];
|
|
4404
|
-
break;
|
|
4405
|
-
case 'moveRight':
|
|
4406
|
-
keyframes = [{ transform: `translateX(-${dist}px)`, opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }];
|
|
4407
|
-
break;
|
|
4408
|
-
case 'scaleIn':
|
|
4409
|
-
keyframes = [{ transform: 'scale(0.8)', opacity: 0 }, { transform: 'scale(1)', opacity: 1 }];
|
|
4410
|
-
break;
|
|
4411
|
-
case 'scaleOut':
|
|
4412
|
-
keyframes = [{ transform: 'scale(1)', opacity: 1 }, { transform: 'scale(0.8)', opacity: 0 }];
|
|
4413
|
-
break;
|
|
4414
|
-
case 'rotate':
|
|
4415
|
-
const deg = action.params?.['degrees'] ?? 360;
|
|
4416
|
-
keyframes = [{ transform: `rotate(0deg)` }, { transform: `rotate(${deg}deg)` }];
|
|
4417
|
-
break;
|
|
4418
|
-
case 'show':
|
|
4419
|
-
keyframes = [{ opacity: 0, visibility: 'hidden' }, { opacity: 1, visibility: 'visible' }];
|
|
4420
|
-
break;
|
|
4421
|
-
case 'hide':
|
|
4422
|
-
keyframes = [{ opacity: 1, visibility: 'visible' }, { opacity: 0, visibility: 'hidden' }];
|
|
4423
|
-
break;
|
|
4424
|
-
case 'toggleVisibility':
|
|
4425
|
-
// Toggle is stateful — handled separately via element style
|
|
4426
|
-
keyframes = [{ opacity: 0 }, { opacity: 1 }];
|
|
4427
|
-
break;
|
|
4428
|
-
}
|
|
4429
|
-
return {
|
|
4430
|
-
keyframes,
|
|
4431
|
-
options: {
|
|
4432
|
-
duration: action.duration,
|
|
4433
|
-
delay: action.delay,
|
|
4434
|
-
easing: action.easing,
|
|
4435
|
-
fill: 'forwards',
|
|
4436
|
-
},
|
|
4437
|
-
};
|
|
4720
|
+
/** The action's keyframes from the ONE shared table both runtimes animate from (composite T2). */
|
|
4721
|
+
keyframesFor(type, action) {
|
|
4722
|
+
return buildInteractionKeyframes(type, action.params) ?? [];
|
|
4438
4723
|
}
|
|
4439
4724
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, deps: [{ token: OverlayService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4440
4725
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService }); }
|
|
@@ -4923,34 +5208,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
4923
5208
|
args: [{ providedIn: 'root' }]
|
|
4924
5209
|
}] });
|
|
4925
5210
|
|
|
4926
|
-
/** The input a component declares to receive its empty-image placeholder (the Image). */
|
|
4927
|
-
const EMPTY_SRC_INPUT = 'emptySrc';
|
|
4928
|
-
/** The editing canvas's "Add image" label, through the host's i18n. */
|
|
4929
|
-
function canvasEmptyImageLabel(labels) {
|
|
4930
|
-
return labels(CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_EMPTY_IMAGE_LABEL_EN);
|
|
4931
|
-
}
|
|
4932
|
-
/**
|
|
4933
|
-
* Push a node's empty-image placeholder (design-system DS3) to its rendered component — a no-op for
|
|
4934
|
-
* a component that declares no `emptySrc`. The placeholder depends on the MARKER only, never on the
|
|
4935
|
-
* source, so it is set once at render and again whenever the node's element style changes.
|
|
4936
|
-
*/
|
|
4937
|
-
function syncEmptyImagePlaceholder(ref, node, emptyLabel) {
|
|
4938
|
-
// Panels hold the selected node's ref untyped; anything without a component type has no inputs.
|
|
4939
|
-
if (!ref?.componentType || !declaredInputNames(ref.componentType).has(EMPTY_SRC_INPUT))
|
|
4940
|
-
return;
|
|
4941
|
-
ref.setInput(EMPTY_SRC_INPUT, emptyImagePlaceholder(node, emptyLabel));
|
|
4942
|
-
}
|
|
4943
|
-
/**
|
|
4944
|
-
* Everything a node's element style / colour scheme decides on its rendered host, in ONE call: the
|
|
4945
|
-
* design classes (marker + scheme) and the empty-image placeholder. Every place that switches or
|
|
4946
|
-
* resets a node's style on the canvas calls this, so an adopted Image gains its placeholder at once
|
|
4947
|
-
* instead of after a reload (DS3 D2).
|
|
4948
|
-
*/
|
|
4949
|
-
function syncNodeDesign(ref, node, emptyLabel) {
|
|
4950
|
-
syncNodeDesignClasses(ref?.location?.nativeElement ?? null, node);
|
|
4951
|
-
syncEmptyImagePlaceholder(ref, node, emptyLabel);
|
|
4952
|
-
}
|
|
4953
|
-
|
|
4954
5211
|
// Module-level counter — unique drop-list ID per Section instance across the whole app session.
|
|
4955
5212
|
let dropListCounter = 0;
|
|
4956
5213
|
/**
|
|
@@ -5015,6 +5272,7 @@ class RenderDirective {
|
|
|
5015
5272
|
this.childSubs = [];
|
|
5016
5273
|
this.onCanvas = inject(IOX_BUILDER_CANVAS, { optional: true }) ?? false;
|
|
5017
5274
|
this.labels = inject(IOX_BUILDER_LABELS);
|
|
5275
|
+
this.designState = inject(CanvasDesignStateService, { optional: true });
|
|
5018
5276
|
/** Host element drag was registered on (collapsed path) — unregistered on destroy. */
|
|
5019
5277
|
this.hostDragEl = null;
|
|
5020
5278
|
}
|
|
@@ -5121,6 +5379,21 @@ class RenderDirective {
|
|
|
5121
5379
|
}
|
|
5122
5380
|
return null;
|
|
5123
5381
|
}
|
|
5382
|
+
/** A link's target as the canvas shows it: the page route, else its URL. */
|
|
5383
|
+
canvasHref(node) {
|
|
5384
|
+
const linkType = nodeTraitValue(node, 'linkType');
|
|
5385
|
+
if (linkType === LinkType.Page)
|
|
5386
|
+
return String(nodeTraitValue(node, 'pageRoute') ?? '');
|
|
5387
|
+
return String(nodeTraitValue(node, 'url') ?? nodeTraitValue(node, 'href') ?? '');
|
|
5388
|
+
}
|
|
5389
|
+
syncA11y(host, node, cssId) {
|
|
5390
|
+
if (!host)
|
|
5391
|
+
return;
|
|
5392
|
+
const el = cssId && !host.classList?.contains(`iox-node-${cssId}`)
|
|
5393
|
+
? host.querySelector?.(`.iox-node-${cssId}`) ?? host
|
|
5394
|
+
: host;
|
|
5395
|
+
syncNodeA11yAttributes(el, nodeA11yAttributes(node, { currentPath: this.designState?.pagePath ?? null, href: this.canvasHref(node) }));
|
|
5396
|
+
}
|
|
5124
5397
|
render(node) {
|
|
5125
5398
|
if (!node)
|
|
5126
5399
|
return;
|
|
@@ -5293,6 +5566,9 @@ class RenderDirective {
|
|
|
5293
5566
|
// pre-DS2 look; the scheme class is where the token sheet re-declares the section's tokens.
|
|
5294
5567
|
// Clones carry both (they render the same look).
|
|
5295
5568
|
syncNodeDesignClasses(nativeEl, node);
|
|
5569
|
+
// The accessibility attributes (CC1: accessible names, aria-hidden icons, dialog openers, the
|
|
5570
|
+
// current page's links) — the SAME decision both engine paths apply, on the node's own element.
|
|
5571
|
+
this.syncA11y(nativeEl, node, cssId);
|
|
5296
5572
|
// Custom element host defaults to display:inline. Set it to block so it participates
|
|
5297
5573
|
// properly in layout and CDK can capture its bounding rect. SKIP for directive-hosts: the
|
|
5298
5574
|
// host is a real element with its own display, and an inline style here out-ranks both
|
|
@@ -5500,8 +5776,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
5500
5776
|
type: Input
|
|
5501
5777
|
}] } });
|
|
5502
5778
|
|
|
5779
|
+
/** The kind label a composite part's selection badge shows (Card v2: "Card · card.title"). */
|
|
5780
|
+
const COMPOSITE_KIND_KEYS = { card: 'builder.chrome.kindCard' };
|
|
5503
5781
|
class OverlayComponent {
|
|
5504
|
-
constructor(elRef, instanceRegistry, overlayService, viewportService, presetRegistry, symbolRegistry, interactionPresetRegistry, interactionEngine, clipboard, styleRegistry, panelEventService, cdr, dragEngine) {
|
|
5782
|
+
constructor(elRef, instanceRegistry, overlayService, viewportService, presetRegistry, symbolRegistry, interactionPresetRegistry, interactionEngine, clipboard, styleRegistry, panelEventService, cdr, dragEngine, labels) {
|
|
5505
5783
|
this.elRef = elRef;
|
|
5506
5784
|
this.instanceRegistry = instanceRegistry;
|
|
5507
5785
|
this.overlayService = overlayService;
|
|
@@ -5562,6 +5840,7 @@ class OverlayComponent {
|
|
|
5562
5840
|
this.boundUpdate = () => this.scheduleUpdate();
|
|
5563
5841
|
this.boundScrollUpdate = () => this.scheduleUpdate();
|
|
5564
5842
|
this.activeScrollContainer = null;
|
|
5843
|
+
this.builderLabels = labels ?? defaultBuilderLabels;
|
|
5565
5844
|
}
|
|
5566
5845
|
/** Resolves the selected node's OWNING dropzone id dynamically — the drag-handle isn't
|
|
5567
5846
|
* rendered inside any container (it's overlay chrome), so it has no source id of its own. */
|
|
@@ -5711,6 +5990,15 @@ class OverlayComponent {
|
|
|
5711
5990
|
get selectedNodeIsGlobal() {
|
|
5712
5991
|
return !!this.selectEntry?.node?.isGlobal;
|
|
5713
5992
|
}
|
|
5993
|
+
/** A composite part's kind + role under the selection (board 3/8 "Selection kind badge"), or null. */
|
|
5994
|
+
get selectedRoleBadge() {
|
|
5995
|
+
const role = this.selectEntry?.node?.role;
|
|
5996
|
+
if (!role)
|
|
5997
|
+
return null;
|
|
5998
|
+
const key = COMPOSITE_KIND_KEYS[role.split('.')[0]];
|
|
5999
|
+
const kind = key ? this.builderLabels(key, BUILDER_CHROME_EN[key]) : role.split('.')[0];
|
|
6000
|
+
return `${kind} · ${role}`;
|
|
6001
|
+
}
|
|
5714
6002
|
get selectedPresetName() {
|
|
5715
6003
|
const id = this.selectEntry?.node?.stylePresetId;
|
|
5716
6004
|
if (!id)
|
|
@@ -6026,13 +6314,18 @@ class OverlayComponent {
|
|
|
6026
6314
|
// is a zero-area line — prevents diagonal edge triangle artifacts.
|
|
6027
6315
|
return `polygon(evenodd, 0px 0px, ${w}px 0px, ${w}px ${h}px, 0px ${h}px, 0px 0px, ${x1}px ${y1}px, ${x2}px ${y1}px, ${x2}px ${y2}px, ${x1}px ${y2}px, ${x1}px ${y1}px)`;
|
|
6028
6316
|
}
|
|
6029
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: OverlayComponent, deps: [{ token: i0.ElementRef }, { token: i1.ComponentInstanceRegistryService }, { token: OverlayService }, { token: ViewportService }, { token: PresetRegistryService }, { token: SymbolRegistryService }, { token: InteractionPresetRegistryService }, { token: InteractionEngineService }, { token: BuilderClipboardService }, { token: StyleRegistryService }, { token: PanelEventService }, { token: i0.ChangeDetectorRef }, { token: DragEngineService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6030
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: OverlayComponent, isStandalone: false, selector: "app-overlay", outputs: { toolbarAction: "toolbarAction" }, ngImport: i0, template: "<div class=\"overlay-root\">\n\n <!-- HOVER \u2014 Chrome DevTools-style rings (content area stays transparent) -->\n <ng-container *ngIf=\"hoverEntry\">\n <div class=\"hi-outline\" [ngStyle]=\"hiOutlineStyle\"></div>\n <div class=\"hi-margin\" [ngStyle]=\"hiMarginStyle\"></div>\n <div class=\"hi-padding\" [ngStyle]=\"hiPaddingStyle\"></div>\n </ng-container>\n\n <!-- SELECT \u2014 IOX pink outline + toolbar -->\n <div class=\"select-outline\" *ngIf=\"selectEntry\" [ngStyle]=\"selectOutlineStyle\">\n\n <!-- Preset badges container \u2014 below the outline, start-aligned -->\n <div class=\"preset-badge\" *ngIf=\"selectedPresetName || interactions.length\">\n <div *ngIf=\"selectedPresetName\"\n class=\"preset-badge__item\"\n [class.preset-badge__item--active]=\"activeBadgeType === 'style'\"\n title=\"Style preset: {{ selectedPresetName }} \u2014 click to edit, right-click to copy\"\n (mousedown)=\"onEditPreset($event)\"\n (contextmenu)=\"onBadgeContextMenu($event, 'style')\">\n <i class=\"ph-fill ph-paint-bucket\"></i>\n <span>{{ selectedPresetName }}</span>\n </div>\n <div *ngFor=\"let ix of interactions\"\n class=\"preset-badge__item preset-badge__item--interaction\"\n [class.preset-badge__item--active]=\"activeBadgeType === 'interaction'\"\n [title]=\"triggerLabel(ix.trigger) + ': ' + actionLabel(ix) + ' \u2014 click to edit, right-click to remove'\"\n (mousedown)=\"onEditInteractionPreset($event, ix)\"\n (click)=\"onBadgeClickSwallow($event)\"\n (contextmenu)=\"onBadgeContextMenu($event, 'interaction', ix)\">\n <i [class]=\"triggerIcon(ix.trigger)\"></i>\n <span>{{ triggerLabel(ix.trigger) }}: {{ actionLabel(ix) }}</span>\n </div>\n </div>\n\n <div class=\"select-toolbar\" [class.select-toolbar--inside]=\"toolbarInside\" (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n\n <!-- Drag handle \u2014 grab here to move the selected element. A click-and-hold ANYWHERE\n on the element body would otherwise grab whatever nested child the pointer lands\n on; DragEngineService redirects any drag starting here to the selected node. -->\n <button *ngIf=\"selectEntry?.node\" class=\"toolbar-btn toolbar-btn--drag-handle\"\n ioxDraggable data-iox-drag-handle\n [ioxDragData]=\"selectEntry?.node\"\n [ioxDragSourceId]=\"dragHandleSourceId\"\n title=\"Drag to move\">\n <i class=\"ph-thin ph-dots-six-vertical\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Component name -->\n <span class=\"toolbar-name\">{{ selectEntry?.componentName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n <button *ngIf=\"isDialog\" class=\"toolbar-btn\"\n [title]=\"dialogDesignOpen ? 'Close the dialog (design)' : 'Open the dialog (design)'\"\n [attr.aria-label]=\"dialogDesignOpen ? 'Close the dialog for design' : 'Open the dialog for design'\"\n (mousedown)=\"onToggleDialogOpen($event)\">\n <i [class]=\"dialogDesignOpen ? 'ph-thin ph-eye-slash' : 'ph-thin ph-eye'\"></i>\n </button>\n <button *ngIf=\"isSliderContainer\" class=\"toolbar-btn toolbar-btn--add-slide\" title=\"Add slide\" (mousedown)=\"onAddSlide($event)\">\n <i class=\"ph-thin ph-plus\"></i>\n <span class=\"toolbar-btn__label\">Add slide</span>\n </button>\n <button class=\"toolbar-btn\" title=\"Add an element here\" aria-label=\"Add an element here\" (mousedown)=\"onAddElement($event)\">\n <i class=\"ph-thin ph-plus\"></i>\n </button>\n <button class=\"toolbar-btn\" title=\"Duplicate\" (mousedown)=\"onDuplicate($event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button *ngIf=\"hasInteractions\" class=\"toolbar-btn toolbar-btn--play\" title=\"Play animations\" (mousedown)=\"onPlay($event)\">\n <i class=\"ph-thin ph-play\"></i>\n </button>\n <button *ngIf=\"isLinkedSymbol\" class=\"toolbar-btn toolbar-btn--symbol\"\n [title]=\"(linkedSymbolName ? linkedSymbolName : 'Symbol') + ' \u2014 click to detach'\"\n (mousedown)=\"onDetachSymbol($event)\">\n <i class=\"ph-thin ph-link\"></i>\n <span class=\"toolbar-btn__label\">{{ linkedSymbolName || 'Symbol' }}</span>\n </button>\n <button *ngIf=\"isDetachedSymbol\" class=\"toolbar-btn toolbar-btn--resync\"\n title=\"Detached \u2014 click to re-link\"\n (mousedown)=\"onResyncSymbol($event)\">\n <i class=\"ph-thin ph-link-break\"></i>\n <span class=\"toolbar-btn__label\">Detached</span>\n </button>\n <button class=\"toolbar-btn toolbar-btn--danger\" title=\"Delete\" (mousedown)=\"onDelete($event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n <button class=\"toolbar-btn\" title=\"Select parent\" (mousedown)=\"onSelectParent($event)\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n </button>\n\n <!-- More menu trigger \u2014 opens a fixed-position dropdown at the cursor -->\n <button class=\"toolbar-btn toolbar-btn--more\" title=\"More actions\" (mousedown)=\"onToggleMore($event)\">\n <i class=\"ph-thin ph-dots-three\"></i>\n </button>\n </div>\n </div>\n\n <!-- Shared more/context menu \u2014 fixed-position, opened by \u22EF button or right-click -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" [ngStyle]=\"moreMenuStyle\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <button class=\"more-item\" (mousedown)=\"onSelectParent($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n Select parent\n </button>\n <button class=\"more-item\" (mousedown)=\"onDuplicate($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-copy\"></i>\n Duplicate\n </button>\n <div *ngIf=\"canWrap\" class=\"more-item-submenu\"\n (mouseenter)=\"wrapSubmenuOpen = true\" (mouseleave)=\"wrapSubmenuOpen = false\">\n <button class=\"more-item\" (mousedown)=\"$event.stopPropagation()\">\n <i class=\"ph-thin ph-selection-plus\"></i>\n Wrap in\n <i class=\"ph-thin ph-caret-right more-item__chevron\"></i>\n </button>\n <div *ngIf=\"wrapSubmenuOpen\" class=\"more-submenu-flyout\">\n <button *ngFor=\"let target of wrapTargets\" class=\"more-item\"\n (mousedown)=\"onWrap($event, target.type); closeMoreMenu()\">\n <i [class]=\"target.icon\"></i>\n {{ target.label }}\n </button>\n </div>\n </div>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onDelete($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-trash\"></i>\n Delete\n </button>\n <div class=\"more-divider\"></div>\n <button class=\"more-item\" (mousedown)=\"onCopyNode($event)\">\n <i class=\"ph-thin ph-scissors\"></i>\n Copy element\n </button>\n <button *ngIf=\"hasCopiedNode\" class=\"more-item\" (mousedown)=\"onPasteNode($event)\">\n <i class=\"ph-thin ph-clipboard\"></i>\n Paste element\n </button>\n <div class=\"more-divider\"></div>\n <button *ngIf=\"selectEntry?.node?.stylePresetId\" class=\"more-item\" (mousedown)=\"onCopyPreset($event)\">\n <i class=\"ph-thin ph-paint-bucket\"></i>\n Copy style preset\n </button>\n <button *ngIf=\"hasPresetInClipboard\" class=\"more-item\" (mousedown)=\"onPastePreset($event)\">\n <i class=\"ph-thin ph-paint-bucket\"></i>\n Paste style preset\n </button>\n <button *ngIf=\"selectEntry?.node?.interactionPresetId\" class=\"more-item\" (mousedown)=\"onCopyInteractionPreset($event)\">\n <i class=\"ph-thin ph-lightning\"></i>\n Copy interaction preset\n </button>\n <button *ngIf=\"hasInteractionPresetInClipboard\" class=\"more-item\" (mousedown)=\"onPasteInteractionPreset($event)\">\n <i class=\"ph-thin ph-lightning\"></i>\n Paste interaction preset\n </button>\n <div class=\"more-divider\"></div>\n <button *ngIf=\"isLinkedSymbol\" class=\"more-item\" (mousedown)=\"onDetachSymbol($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-link-break\"></i>\n Detach from symbol<ng-container *ngIf=\"linkedSymbolName\"> ({{ linkedSymbolName }})</ng-container>\n </button>\n <button *ngIf=\"isDetachedSymbol\" class=\"more-item more-item--resync\" (mousedown)=\"onResyncSymbol($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-link\"></i>\n Re-link to symbol\n </button>\n <button class=\"more-item\" (mousedown)=\"onSaveAsBlock($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n <button *ngIf=\"hasInteractions\" class=\"more-item more-item--play\" (mousedown)=\"onPlay($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-play\"></i>\n Play animations\n </button>\n <div class=\"more-divider\"></div>\n <ng-container *ngIf=\"!selectedNodeIsGlobal; else removeGlobalItem\">\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'before')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (Before Content)\n </button>\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'after')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (After Content)\n </button>\n </ng-container>\n <ng-template #removeGlobalItem>\n <button class=\"more-item\" (mousedown)=\"onRemoveGlobal($event)\">\n <i class=\"ph-thin ph-globe\"></i>\n Remove from Global\n </button>\n </ng-template>\n </div>\n\n <!-- Backdrop to close the more menu -->\n <div *ngIf=\"moreMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu()\">\n </div>\n\n <!-- Badge context menu \u2014 options differ by badge type -->\n <div *ngIf=\"badgeMenuVisible\" class=\"badge-context-menu\"\n [ngStyle]=\"{ top: badgeMenuPos.y + 'px', left: badgeMenuPos.x + 'px' }\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <ng-container *ngIf=\"badgeMenuType === 'style'\">\n <button class=\"more-item\" (mousedown)=\"onBadgeCopy()\">\n <i class=\"ph-thin ph-copy\"></i>\n Copy preset\n </button>\n <button class=\"more-item\" (mousedown)=\"onBadgeEdit()\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n Edit preset\n </button>\n </ng-container>\n <ng-container *ngIf=\"badgeMenuType === 'interaction'\">\n <button class=\"more-item\" (mousedown)=\"onBadgeEdit()\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n Edit in panel\n </button>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onBadgeRemoveInteraction()\">\n <i class=\"ph-thin ph-trash\"></i>\n Remove interaction\n </button>\n </ng-container>\n </div>\n <div *ngIf=\"badgeMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeBadgeMenu()\">\n </div>\n\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:absolute;inset:0;pointer-events:none;z-index:12;overflow:visible}.hi-outline,.hi-margin,.hi-padding{position:fixed;pointer-events:none;transition:none;box-sizing:border-box}.hi-outline{box-shadow:inset 0 0 0 1.5px #cb9090}.hi-margin{background:#f6b23361}.hi-padding{background:#60c38366}.select-outline{position:fixed;z-index:999;box-shadow:inset 0 0 0 1px #cb9090;pointer-events:none;transition:none;overflow:visible;will-change:transform;box-sizing:border-box}.select-toolbar{position:absolute;top:0;left:0;transform:translateY(-100%)}.select-toolbar.select-toolbar--inside{transform:translateY(0);border-radius:0 0 3px 3px}.select-toolbar{display:flex;align-items:center;background:#cb9090;border-radius:3px 3px 0 0;pointer-events:all;white-space:nowrap;overflow:visible}.select-toolbar .toolbar-name{padding:0 8px;font-size:11px;font-weight:500;color:#fff;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px}.select-toolbar .toolbar-sep{width:1px;height:14px;background:#ffffff59;margin:0 2px;flex-shrink:0}.select-toolbar .toolbar-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:none;background:transparent;color:#fff;font-size:13px;cursor:pointer;transition:background .15s;flex-shrink:0}.select-toolbar .toolbar-btn:hover{background:#ffffff2e}.select-toolbar .toolbar-btn--danger:hover{background:#c8323259}.select-toolbar .toolbar-btn--drag-handle{cursor:grab}.select-toolbar .toolbar-btn--drag-handle:active{cursor:grabbing}.select-toolbar .toolbar-btn--symbol{width:auto;padding:0 7px;gap:4px;color:#fff;background:#f59e0b;border-radius:3px;font-size:11px}.select-toolbar .toolbar-btn--symbol:hover{background:#d97706}.select-toolbar .toolbar-btn--resync{width:auto;padding:0 7px;gap:4px;color:#fff;background:var(--iox-success-fill, #2E7D45);border-radius:3px;font-size:11px}.select-toolbar .toolbar-btn--resync:hover{background:color-mix(in srgb,var(--iox-success-fill, #2E7D45) 86%,#000)}.select-toolbar .toolbar-btn .toolbar-btn__label{font-size:11px;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select-toolbar .toolbar-btn--more{border-radius:0 3px 0 0}.toolbar-more-dropdown{position:fixed;z-index:1000;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all}.toolbar-more-dropdown .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:start;white-space:nowrap}.toolbar-more-dropdown .more-item i{font-size:14px}.toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item--danger{color:#c84040}.toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.toolbar-more-dropdown .more-item--resync{color:var(--iox-success-text, #1B5E32)}.toolbar-more-dropdown .more-item--resync:hover{background:var(--iox-success-bg, #E8F3EC)}.toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.toolbar-more-dropdown .more-item-submenu{position:relative}.toolbar-more-dropdown .more-item-submenu .more-item__chevron{margin-inline-start:auto;font-size:12px;opacity:.6}.toolbar-more-dropdown .more-item-submenu:hover>.more-item{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item-submenu .more-submenu-flyout{position:absolute;top:-4px;inset-inline-start:100%;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:180px;z-index:1001}.preset-badge{position:absolute;bottom:-26px;inset-inline-start:0;display:flex;align-items:center;gap:4px;pointer-events:none;white-space:nowrap}.preset-badge__item{display:flex;align-items:center;gap:5px;padding:3px 9px;background:#cb9090;color:#fff;font-size:11px;font-weight:500;border-radius:20px;pointer-events:all;cursor:pointer;box-shadow:0 2px 6px #0000002e;transition:background .15s,outline-color .15s;outline:2px solid transparent;outline-offset:2px}.preset-badge__item i{font-size:12px}.preset-badge__item:hover{background:#be7474}.preset-badge__item--active{outline-color:#ffffffd9}.preset-badge__item--interaction{background:#7a9ecb}.preset-badge__item--interaction:hover{background:#5d89c0}.badge-context-menu{position:fixed;z-index:1001;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:160px;pointer-events:all}.badge-context-menu .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 14px;border:none;background:transparent;font-size:12px;color:#333;cursor:pointer;text-align:start;white-space:nowrap}.badge-context-menu .more-item:hover{background:var(--p-surface-50, #f9fafb)}.badge-context-menu .more-item i{font-size:13px;color:#888}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId", "ioxDragDisabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6317
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: OverlayComponent, deps: [{ token: i0.ElementRef }, { token: i1.ComponentInstanceRegistryService }, { token: OverlayService }, { token: ViewportService }, { token: PresetRegistryService }, { token: SymbolRegistryService }, { token: InteractionPresetRegistryService }, { token: InteractionEngineService }, { token: BuilderClipboardService }, { token: StyleRegistryService }, { token: PanelEventService }, { token: i0.ChangeDetectorRef }, { token: DragEngineService }, { token: IOX_BUILDER_LABELS, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6318
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: OverlayComponent, isStandalone: false, selector: "app-overlay", outputs: { toolbarAction: "toolbarAction" }, ngImport: i0, template: "<div class=\"overlay-root\">\n\n <!-- HOVER \u2014 Chrome DevTools-style rings (content area stays transparent) -->\n <ng-container *ngIf=\"hoverEntry\">\n <div class=\"hi-outline\" [ngStyle]=\"hiOutlineStyle\"></div>\n <div class=\"hi-margin\" [ngStyle]=\"hiMarginStyle\"></div>\n <div class=\"hi-padding\" [ngStyle]=\"hiPaddingStyle\"></div>\n </ng-container>\n\n <!-- SELECT \u2014 IOX pink outline + toolbar -->\n <div class=\"select-outline\" *ngIf=\"selectEntry\" [ngStyle]=\"selectOutlineStyle\">\n\n <!-- Preset badges container \u2014 below the outline, start-aligned -->\n <div class=\"preset-badge\" *ngIf=\"selectedRoleBadge || selectedPresetName || interactions.length\">\n <!-- A composite part (Card v2): its kind and role, read-only \u2014 what the layer tree's chip says. -->\n <div *ngIf=\"selectedRoleBadge\" class=\"preset-badge__item preset-badge__item--kind\" [title]=\"selectedRoleBadge\">\n <i class=\"ph-fill ph-stack\"></i>\n <span>{{ selectedRoleBadge }}</span>\n </div>\n <div *ngIf=\"selectedPresetName\"\n class=\"preset-badge__item\"\n [class.preset-badge__item--active]=\"activeBadgeType === 'style'\"\n title=\"Style preset: {{ selectedPresetName }} \u2014 click to edit, right-click to copy\"\n (mousedown)=\"onEditPreset($event)\"\n (contextmenu)=\"onBadgeContextMenu($event, 'style')\">\n <i class=\"ph-fill ph-paint-bucket\"></i>\n <span>{{ selectedPresetName }}</span>\n </div>\n <div *ngFor=\"let ix of interactions\"\n class=\"preset-badge__item preset-badge__item--interaction\"\n [class.preset-badge__item--active]=\"activeBadgeType === 'interaction'\"\n [title]=\"triggerLabel(ix.trigger) + ': ' + actionLabel(ix) + ' \u2014 click to edit, right-click to remove'\"\n (mousedown)=\"onEditInteractionPreset($event, ix)\"\n (click)=\"onBadgeClickSwallow($event)\"\n (contextmenu)=\"onBadgeContextMenu($event, 'interaction', ix)\">\n <i [class]=\"triggerIcon(ix.trigger)\"></i>\n <span>{{ triggerLabel(ix.trigger) }}: {{ actionLabel(ix) }}</span>\n </div>\n </div>\n\n <div class=\"select-toolbar\" [class.select-toolbar--inside]=\"toolbarInside\" (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n\n <!-- Drag handle \u2014 grab here to move the selected element. A click-and-hold ANYWHERE\n on the element body would otherwise grab whatever nested child the pointer lands\n on; DragEngineService redirects any drag starting here to the selected node. -->\n <button *ngIf=\"selectEntry?.node\" class=\"toolbar-btn toolbar-btn--drag-handle\"\n ioxDraggable data-iox-drag-handle\n [ioxDragData]=\"selectEntry?.node\"\n [ioxDragSourceId]=\"dragHandleSourceId\"\n title=\"Drag to move\">\n <i class=\"ph-thin ph-dots-six-vertical\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Component name -->\n <span class=\"toolbar-name\">{{ selectEntry?.componentName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n <button *ngIf=\"isDialog\" class=\"toolbar-btn\"\n [title]=\"dialogDesignOpen ? 'Close the dialog (design)' : 'Open the dialog (design)'\"\n [attr.aria-label]=\"dialogDesignOpen ? 'Close the dialog for design' : 'Open the dialog for design'\"\n (mousedown)=\"onToggleDialogOpen($event)\">\n <i [class]=\"dialogDesignOpen ? 'ph-thin ph-eye-slash' : 'ph-thin ph-eye'\"></i>\n </button>\n <button *ngIf=\"isSliderContainer\" class=\"toolbar-btn toolbar-btn--add-slide\" title=\"Add slide\" (mousedown)=\"onAddSlide($event)\">\n <i class=\"ph-thin ph-plus\"></i>\n <span class=\"toolbar-btn__label\">Add slide</span>\n </button>\n <button class=\"toolbar-btn\" title=\"Add an element here\" aria-label=\"Add an element here\" (mousedown)=\"onAddElement($event)\">\n <i class=\"ph-thin ph-plus\"></i>\n </button>\n <button class=\"toolbar-btn\" title=\"Duplicate\" (mousedown)=\"onDuplicate($event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button *ngIf=\"hasInteractions\" class=\"toolbar-btn toolbar-btn--play\" title=\"Play animations\" (mousedown)=\"onPlay($event)\">\n <i class=\"ph-thin ph-play\"></i>\n </button>\n <button *ngIf=\"isLinkedSymbol\" class=\"toolbar-btn toolbar-btn--symbol\"\n [title]=\"(linkedSymbolName ? linkedSymbolName : 'Symbol') + ' \u2014 click to detach'\"\n (mousedown)=\"onDetachSymbol($event)\">\n <i class=\"ph-thin ph-link\"></i>\n <span class=\"toolbar-btn__label\">{{ linkedSymbolName || 'Symbol' }}</span>\n </button>\n <button *ngIf=\"isDetachedSymbol\" class=\"toolbar-btn toolbar-btn--resync\"\n title=\"Detached \u2014 click to re-link\"\n (mousedown)=\"onResyncSymbol($event)\">\n <i class=\"ph-thin ph-link-break\"></i>\n <span class=\"toolbar-btn__label\">Detached</span>\n </button>\n <button class=\"toolbar-btn toolbar-btn--danger\" title=\"Delete\" (mousedown)=\"onDelete($event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n <button class=\"toolbar-btn\" title=\"Select parent\" (mousedown)=\"onSelectParent($event)\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n </button>\n\n <!-- More menu trigger \u2014 opens a fixed-position dropdown at the cursor -->\n <button class=\"toolbar-btn toolbar-btn--more\" title=\"More actions\" (mousedown)=\"onToggleMore($event)\">\n <i class=\"ph-thin ph-dots-three\"></i>\n </button>\n </div>\n </div>\n\n <!-- Shared more/context menu \u2014 fixed-position, opened by \u22EF button or right-click -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" [ngStyle]=\"moreMenuStyle\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <button class=\"more-item\" (mousedown)=\"onSelectParent($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n Select parent\n </button>\n <button class=\"more-item\" (mousedown)=\"onDuplicate($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-copy\"></i>\n Duplicate\n </button>\n <div *ngIf=\"canWrap\" class=\"more-item-submenu\"\n (mouseenter)=\"wrapSubmenuOpen = true\" (mouseleave)=\"wrapSubmenuOpen = false\">\n <button class=\"more-item\" (mousedown)=\"$event.stopPropagation()\">\n <i class=\"ph-thin ph-selection-plus\"></i>\n Wrap in\n <i class=\"ph-thin ph-caret-right more-item__chevron\"></i>\n </button>\n <div *ngIf=\"wrapSubmenuOpen\" class=\"more-submenu-flyout\">\n <button *ngFor=\"let target of wrapTargets\" class=\"more-item\"\n (mousedown)=\"onWrap($event, target.type); closeMoreMenu()\">\n <i [class]=\"target.icon\"></i>\n {{ target.label }}\n </button>\n </div>\n </div>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onDelete($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-trash\"></i>\n Delete\n </button>\n <div class=\"more-divider\"></div>\n <button class=\"more-item\" (mousedown)=\"onCopyNode($event)\">\n <i class=\"ph-thin ph-scissors\"></i>\n Copy element\n </button>\n <button *ngIf=\"hasCopiedNode\" class=\"more-item\" (mousedown)=\"onPasteNode($event)\">\n <i class=\"ph-thin ph-clipboard\"></i>\n Paste element\n </button>\n <div class=\"more-divider\"></div>\n <button *ngIf=\"selectEntry?.node?.stylePresetId\" class=\"more-item\" (mousedown)=\"onCopyPreset($event)\">\n <i class=\"ph-thin ph-paint-bucket\"></i>\n Copy style preset\n </button>\n <button *ngIf=\"hasPresetInClipboard\" class=\"more-item\" (mousedown)=\"onPastePreset($event)\">\n <i class=\"ph-thin ph-paint-bucket\"></i>\n Paste style preset\n </button>\n <button *ngIf=\"selectEntry?.node?.interactionPresetId\" class=\"more-item\" (mousedown)=\"onCopyInteractionPreset($event)\">\n <i class=\"ph-thin ph-lightning\"></i>\n Copy interaction preset\n </button>\n <button *ngIf=\"hasInteractionPresetInClipboard\" class=\"more-item\" (mousedown)=\"onPasteInteractionPreset($event)\">\n <i class=\"ph-thin ph-lightning\"></i>\n Paste interaction preset\n </button>\n <div class=\"more-divider\"></div>\n <button *ngIf=\"isLinkedSymbol\" class=\"more-item\" (mousedown)=\"onDetachSymbol($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-link-break\"></i>\n Detach from symbol<ng-container *ngIf=\"linkedSymbolName\"> ({{ linkedSymbolName }})</ng-container>\n </button>\n <button *ngIf=\"isDetachedSymbol\" class=\"more-item more-item--resync\" (mousedown)=\"onResyncSymbol($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-link\"></i>\n Re-link to symbol\n </button>\n <button class=\"more-item\" (mousedown)=\"onSaveAsBlock($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n <button *ngIf=\"hasInteractions\" class=\"more-item more-item--play\" (mousedown)=\"onPlay($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-play\"></i>\n Play animations\n </button>\n <div class=\"more-divider\"></div>\n <ng-container *ngIf=\"!selectedNodeIsGlobal; else removeGlobalItem\">\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'before')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (Before Content)\n </button>\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'after')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (After Content)\n </button>\n </ng-container>\n <ng-template #removeGlobalItem>\n <button class=\"more-item\" (mousedown)=\"onRemoveGlobal($event)\">\n <i class=\"ph-thin ph-globe\"></i>\n Remove from Global\n </button>\n </ng-template>\n </div>\n\n <!-- Backdrop to close the more menu -->\n <div *ngIf=\"moreMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu()\">\n </div>\n\n <!-- Badge context menu \u2014 options differ by badge type -->\n <div *ngIf=\"badgeMenuVisible\" class=\"badge-context-menu\"\n [ngStyle]=\"{ top: badgeMenuPos.y + 'px', left: badgeMenuPos.x + 'px' }\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <ng-container *ngIf=\"badgeMenuType === 'style'\">\n <button class=\"more-item\" (mousedown)=\"onBadgeCopy()\">\n <i class=\"ph-thin ph-copy\"></i>\n Copy preset\n </button>\n <button class=\"more-item\" (mousedown)=\"onBadgeEdit()\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n Edit preset\n </button>\n </ng-container>\n <ng-container *ngIf=\"badgeMenuType === 'interaction'\">\n <button class=\"more-item\" (mousedown)=\"onBadgeEdit()\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n Edit in panel\n </button>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onBadgeRemoveInteraction()\">\n <i class=\"ph-thin ph-trash\"></i>\n Remove interaction\n </button>\n </ng-container>\n </div>\n <div *ngIf=\"badgeMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeBadgeMenu()\">\n </div>\n\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:absolute;inset:0;pointer-events:none;z-index:12;overflow:visible}.hi-outline,.hi-margin,.hi-padding{position:fixed;pointer-events:none;transition:none;box-sizing:border-box}.hi-outline{box-shadow:inset 0 0 0 1.5px #cb9090}.hi-margin{background:#f6b23361}.hi-padding{background:#60c38366}.select-outline{position:fixed;z-index:999;box-shadow:inset 0 0 0 1px #cb9090;pointer-events:none;transition:none;overflow:visible;will-change:transform;box-sizing:border-box}.select-toolbar{position:absolute;top:0;left:0;transform:translateY(-100%)}.select-toolbar.select-toolbar--inside{transform:translateY(0);border-radius:0 0 3px 3px}.select-toolbar{display:flex;align-items:center;background:#cb9090;border-radius:3px 3px 0 0;pointer-events:all;white-space:nowrap;overflow:visible}.select-toolbar .toolbar-name{padding:0 8px;font-size:11px;font-weight:500;color:#fff;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px}.select-toolbar .toolbar-sep{width:1px;height:14px;background:#ffffff59;margin:0 2px;flex-shrink:0}.select-toolbar .toolbar-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:none;background:transparent;color:#fff;font-size:13px;cursor:pointer;transition:background .15s;flex-shrink:0}.select-toolbar .toolbar-btn:hover{background:#ffffff2e}.select-toolbar .toolbar-btn--danger:hover{background:#c8323259}.select-toolbar .toolbar-btn--drag-handle{cursor:grab}.select-toolbar .toolbar-btn--drag-handle:active{cursor:grabbing}.select-toolbar .toolbar-btn--symbol{width:auto;padding:0 7px;gap:4px;color:#fff;background:#f59e0b;border-radius:3px;font-size:11px}.select-toolbar .toolbar-btn--symbol:hover{background:#d97706}.select-toolbar .toolbar-btn--resync{width:auto;padding:0 7px;gap:4px;color:#fff;background:var(--iox-success-fill, #2E7D45);border-radius:3px;font-size:11px}.select-toolbar .toolbar-btn--resync:hover{background:color-mix(in srgb,var(--iox-success-fill, #2E7D45) 86%,#000)}.select-toolbar .toolbar-btn .toolbar-btn__label{font-size:11px;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select-toolbar .toolbar-btn--more{border-radius:0 3px 0 0}.toolbar-more-dropdown{position:fixed;z-index:1000;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all}.toolbar-more-dropdown .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:start;white-space:nowrap}.toolbar-more-dropdown .more-item i{font-size:14px}.toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item--danger{color:#c84040}.toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.toolbar-more-dropdown .more-item--resync{color:var(--iox-success-text, #1B5E32)}.toolbar-more-dropdown .more-item--resync:hover{background:var(--iox-success-bg, #E8F3EC)}.toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.toolbar-more-dropdown .more-item-submenu{position:relative}.toolbar-more-dropdown .more-item-submenu .more-item__chevron{margin-inline-start:auto;font-size:12px;opacity:.6}.toolbar-more-dropdown .more-item-submenu:hover>.more-item{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item-submenu .more-submenu-flyout{position:absolute;top:-4px;inset-inline-start:100%;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:180px;z-index:1001}.preset-badge{position:absolute;bottom:-26px;inset-inline-start:0;display:flex;align-items:center;gap:4px;pointer-events:none;white-space:nowrap}.preset-badge__item{display:flex;align-items:center;gap:5px;padding:3px 9px;background:#cb9090;color:#fff;font-size:11px;font-weight:500;border-radius:20px;pointer-events:all;cursor:pointer;box-shadow:0 2px 6px #0000002e;transition:background .15s,outline-color .15s;outline:2px solid transparent;outline-offset:2px}.preset-badge__item i{font-size:12px}.preset-badge__item:hover{background:#be7474}.preset-badge__item--active{outline-color:#ffffffd9}.preset-badge__item--interaction{background:#7a9ecb}.preset-badge__item--interaction:hover{background:#5d89c0}.preset-badge__item--kind{background:#8a3a72;cursor:default}.preset-badge__item--kind:hover{background:#8a3a72}.badge-context-menu{position:fixed;z-index:1001;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:160px;pointer-events:all}.badge-context-menu .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 14px;border:none;background:transparent;font-size:12px;color:#333;cursor:pointer;text-align:start;white-space:nowrap}.badge-context-menu .more-item:hover{background:var(--p-surface-50, #f9fafb)}.badge-context-menu .more-item i{font-size:13px;color:#888}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId", "ioxDragDisabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6031
6319
|
}
|
|
6032
6320
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: OverlayComponent, decorators: [{
|
|
6033
6321
|
type: Component,
|
|
6034
|
-
args: [{ selector: 'app-overlay', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"overlay-root\">\n\n <!-- HOVER \u2014 Chrome DevTools-style rings (content area stays transparent) -->\n <ng-container *ngIf=\"hoverEntry\">\n <div class=\"hi-outline\" [ngStyle]=\"hiOutlineStyle\"></div>\n <div class=\"hi-margin\" [ngStyle]=\"hiMarginStyle\"></div>\n <div class=\"hi-padding\" [ngStyle]=\"hiPaddingStyle\"></div>\n </ng-container>\n\n <!-- SELECT \u2014 IOX pink outline + toolbar -->\n <div class=\"select-outline\" *ngIf=\"selectEntry\" [ngStyle]=\"selectOutlineStyle\">\n\n <!-- Preset badges container \u2014 below the outline, start-aligned -->\n <div class=\"preset-badge\" *ngIf=\"selectedPresetName || interactions.length\">\n <div *ngIf=\"selectedPresetName\"\n class=\"preset-badge__item\"\n [class.preset-badge__item--active]=\"activeBadgeType === 'style'\"\n title=\"Style preset: {{ selectedPresetName }} \u2014 click to edit, right-click to copy\"\n (mousedown)=\"onEditPreset($event)\"\n (contextmenu)=\"onBadgeContextMenu($event, 'style')\">\n <i class=\"ph-fill ph-paint-bucket\"></i>\n <span>{{ selectedPresetName }}</span>\n </div>\n <div *ngFor=\"let ix of interactions\"\n class=\"preset-badge__item preset-badge__item--interaction\"\n [class.preset-badge__item--active]=\"activeBadgeType === 'interaction'\"\n [title]=\"triggerLabel(ix.trigger) + ': ' + actionLabel(ix) + ' \u2014 click to edit, right-click to remove'\"\n (mousedown)=\"onEditInteractionPreset($event, ix)\"\n (click)=\"onBadgeClickSwallow($event)\"\n (contextmenu)=\"onBadgeContextMenu($event, 'interaction', ix)\">\n <i [class]=\"triggerIcon(ix.trigger)\"></i>\n <span>{{ triggerLabel(ix.trigger) }}: {{ actionLabel(ix) }}</span>\n </div>\n </div>\n\n <div class=\"select-toolbar\" [class.select-toolbar--inside]=\"toolbarInside\" (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n\n <!-- Drag handle \u2014 grab here to move the selected element. A click-and-hold ANYWHERE\n on the element body would otherwise grab whatever nested child the pointer lands\n on; DragEngineService redirects any drag starting here to the selected node. -->\n <button *ngIf=\"selectEntry?.node\" class=\"toolbar-btn toolbar-btn--drag-handle\"\n ioxDraggable data-iox-drag-handle\n [ioxDragData]=\"selectEntry?.node\"\n [ioxDragSourceId]=\"dragHandleSourceId\"\n title=\"Drag to move\">\n <i class=\"ph-thin ph-dots-six-vertical\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Component name -->\n <span class=\"toolbar-name\">{{ selectEntry?.componentName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n <button *ngIf=\"isDialog\" class=\"toolbar-btn\"\n [title]=\"dialogDesignOpen ? 'Close the dialog (design)' : 'Open the dialog (design)'\"\n [attr.aria-label]=\"dialogDesignOpen ? 'Close the dialog for design' : 'Open the dialog for design'\"\n (mousedown)=\"onToggleDialogOpen($event)\">\n <i [class]=\"dialogDesignOpen ? 'ph-thin ph-eye-slash' : 'ph-thin ph-eye'\"></i>\n </button>\n <button *ngIf=\"isSliderContainer\" class=\"toolbar-btn toolbar-btn--add-slide\" title=\"Add slide\" (mousedown)=\"onAddSlide($event)\">\n <i class=\"ph-thin ph-plus\"></i>\n <span class=\"toolbar-btn__label\">Add slide</span>\n </button>\n <button class=\"toolbar-btn\" title=\"Add an element here\" aria-label=\"Add an element here\" (mousedown)=\"onAddElement($event)\">\n <i class=\"ph-thin ph-plus\"></i>\n </button>\n <button class=\"toolbar-btn\" title=\"Duplicate\" (mousedown)=\"onDuplicate($event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button *ngIf=\"hasInteractions\" class=\"toolbar-btn toolbar-btn--play\" title=\"Play animations\" (mousedown)=\"onPlay($event)\">\n <i class=\"ph-thin ph-play\"></i>\n </button>\n <button *ngIf=\"isLinkedSymbol\" class=\"toolbar-btn toolbar-btn--symbol\"\n [title]=\"(linkedSymbolName ? linkedSymbolName : 'Symbol') + ' \u2014 click to detach'\"\n (mousedown)=\"onDetachSymbol($event)\">\n <i class=\"ph-thin ph-link\"></i>\n <span class=\"toolbar-btn__label\">{{ linkedSymbolName || 'Symbol' }}</span>\n </button>\n <button *ngIf=\"isDetachedSymbol\" class=\"toolbar-btn toolbar-btn--resync\"\n title=\"Detached \u2014 click to re-link\"\n (mousedown)=\"onResyncSymbol($event)\">\n <i class=\"ph-thin ph-link-break\"></i>\n <span class=\"toolbar-btn__label\">Detached</span>\n </button>\n <button class=\"toolbar-btn toolbar-btn--danger\" title=\"Delete\" (mousedown)=\"onDelete($event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n <button class=\"toolbar-btn\" title=\"Select parent\" (mousedown)=\"onSelectParent($event)\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n </button>\n\n <!-- More menu trigger \u2014 opens a fixed-position dropdown at the cursor -->\n <button class=\"toolbar-btn toolbar-btn--more\" title=\"More actions\" (mousedown)=\"onToggleMore($event)\">\n <i class=\"ph-thin ph-dots-three\"></i>\n </button>\n </div>\n </div>\n\n <!-- Shared more/context menu \u2014 fixed-position, opened by \u22EF button or right-click -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" [ngStyle]=\"moreMenuStyle\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <button class=\"more-item\" (mousedown)=\"onSelectParent($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n Select parent\n </button>\n <button class=\"more-item\" (mousedown)=\"onDuplicate($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-copy\"></i>\n Duplicate\n </button>\n <div *ngIf=\"canWrap\" class=\"more-item-submenu\"\n (mouseenter)=\"wrapSubmenuOpen = true\" (mouseleave)=\"wrapSubmenuOpen = false\">\n <button class=\"more-item\" (mousedown)=\"$event.stopPropagation()\">\n <i class=\"ph-thin ph-selection-plus\"></i>\n Wrap in\n <i class=\"ph-thin ph-caret-right more-item__chevron\"></i>\n </button>\n <div *ngIf=\"wrapSubmenuOpen\" class=\"more-submenu-flyout\">\n <button *ngFor=\"let target of wrapTargets\" class=\"more-item\"\n (mousedown)=\"onWrap($event, target.type); closeMoreMenu()\">\n <i [class]=\"target.icon\"></i>\n {{ target.label }}\n </button>\n </div>\n </div>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onDelete($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-trash\"></i>\n Delete\n </button>\n <div class=\"more-divider\"></div>\n <button class=\"more-item\" (mousedown)=\"onCopyNode($event)\">\n <i class=\"ph-thin ph-scissors\"></i>\n Copy element\n </button>\n <button *ngIf=\"hasCopiedNode\" class=\"more-item\" (mousedown)=\"onPasteNode($event)\">\n <i class=\"ph-thin ph-clipboard\"></i>\n Paste element\n </button>\n <div class=\"more-divider\"></div>\n <button *ngIf=\"selectEntry?.node?.stylePresetId\" class=\"more-item\" (mousedown)=\"onCopyPreset($event)\">\n <i class=\"ph-thin ph-paint-bucket\"></i>\n Copy style preset\n </button>\n <button *ngIf=\"hasPresetInClipboard\" class=\"more-item\" (mousedown)=\"onPastePreset($event)\">\n <i class=\"ph-thin ph-paint-bucket\"></i>\n Paste style preset\n </button>\n <button *ngIf=\"selectEntry?.node?.interactionPresetId\" class=\"more-item\" (mousedown)=\"onCopyInteractionPreset($event)\">\n <i class=\"ph-thin ph-lightning\"></i>\n Copy interaction preset\n </button>\n <button *ngIf=\"hasInteractionPresetInClipboard\" class=\"more-item\" (mousedown)=\"onPasteInteractionPreset($event)\">\n <i class=\"ph-thin ph-lightning\"></i>\n Paste interaction preset\n </button>\n <div class=\"more-divider\"></div>\n <button *ngIf=\"isLinkedSymbol\" class=\"more-item\" (mousedown)=\"onDetachSymbol($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-link-break\"></i>\n Detach from symbol<ng-container *ngIf=\"linkedSymbolName\"> ({{ linkedSymbolName }})</ng-container>\n </button>\n <button *ngIf=\"isDetachedSymbol\" class=\"more-item more-item--resync\" (mousedown)=\"onResyncSymbol($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-link\"></i>\n Re-link to symbol\n </button>\n <button class=\"more-item\" (mousedown)=\"onSaveAsBlock($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n <button *ngIf=\"hasInteractions\" class=\"more-item more-item--play\" (mousedown)=\"onPlay($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-play\"></i>\n Play animations\n </button>\n <div class=\"more-divider\"></div>\n <ng-container *ngIf=\"!selectedNodeIsGlobal; else removeGlobalItem\">\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'before')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (Before Content)\n </button>\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'after')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (After Content)\n </button>\n </ng-container>\n <ng-template #removeGlobalItem>\n <button class=\"more-item\" (mousedown)=\"onRemoveGlobal($event)\">\n <i class=\"ph-thin ph-globe\"></i>\n Remove from Global\n </button>\n </ng-template>\n </div>\n\n <!-- Backdrop to close the more menu -->\n <div *ngIf=\"moreMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu()\">\n </div>\n\n <!-- Badge context menu \u2014 options differ by badge type -->\n <div *ngIf=\"badgeMenuVisible\" class=\"badge-context-menu\"\n [ngStyle]=\"{ top: badgeMenuPos.y + 'px', left: badgeMenuPos.x + 'px' }\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <ng-container *ngIf=\"badgeMenuType === 'style'\">\n <button class=\"more-item\" (mousedown)=\"onBadgeCopy()\">\n <i class=\"ph-thin ph-copy\"></i>\n Copy preset\n </button>\n <button class=\"more-item\" (mousedown)=\"onBadgeEdit()\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n Edit preset\n </button>\n </ng-container>\n <ng-container *ngIf=\"badgeMenuType === 'interaction'\">\n <button class=\"more-item\" (mousedown)=\"onBadgeEdit()\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n Edit in panel\n </button>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onBadgeRemoveInteraction()\">\n <i class=\"ph-thin ph-trash\"></i>\n Remove interaction\n </button>\n </ng-container>\n </div>\n <div *ngIf=\"badgeMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeBadgeMenu()\">\n </div>\n\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:absolute;inset:0;pointer-events:none;z-index:12;overflow:visible}.hi-outline,.hi-margin,.hi-padding{position:fixed;pointer-events:none;transition:none;box-sizing:border-box}.hi-outline{box-shadow:inset 0 0 0 1.5px #cb9090}.hi-margin{background:#f6b23361}.hi-padding{background:#60c38366}.select-outline{position:fixed;z-index:999;box-shadow:inset 0 0 0 1px #cb9090;pointer-events:none;transition:none;overflow:visible;will-change:transform;box-sizing:border-box}.select-toolbar{position:absolute;top:0;left:0;transform:translateY(-100%)}.select-toolbar.select-toolbar--inside{transform:translateY(0);border-radius:0 0 3px 3px}.select-toolbar{display:flex;align-items:center;background:#cb9090;border-radius:3px 3px 0 0;pointer-events:all;white-space:nowrap;overflow:visible}.select-toolbar .toolbar-name{padding:0 8px;font-size:11px;font-weight:500;color:#fff;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px}.select-toolbar .toolbar-sep{width:1px;height:14px;background:#ffffff59;margin:0 2px;flex-shrink:0}.select-toolbar .toolbar-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:none;background:transparent;color:#fff;font-size:13px;cursor:pointer;transition:background .15s;flex-shrink:0}.select-toolbar .toolbar-btn:hover{background:#ffffff2e}.select-toolbar .toolbar-btn--danger:hover{background:#c8323259}.select-toolbar .toolbar-btn--drag-handle{cursor:grab}.select-toolbar .toolbar-btn--drag-handle:active{cursor:grabbing}.select-toolbar .toolbar-btn--symbol{width:auto;padding:0 7px;gap:4px;color:#fff;background:#f59e0b;border-radius:3px;font-size:11px}.select-toolbar .toolbar-btn--symbol:hover{background:#d97706}.select-toolbar .toolbar-btn--resync{width:auto;padding:0 7px;gap:4px;color:#fff;background:var(--iox-success-fill, #2E7D45);border-radius:3px;font-size:11px}.select-toolbar .toolbar-btn--resync:hover{background:color-mix(in srgb,var(--iox-success-fill, #2E7D45) 86%,#000)}.select-toolbar .toolbar-btn .toolbar-btn__label{font-size:11px;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select-toolbar .toolbar-btn--more{border-radius:0 3px 0 0}.toolbar-more-dropdown{position:fixed;z-index:1000;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all}.toolbar-more-dropdown .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:start;white-space:nowrap}.toolbar-more-dropdown .more-item i{font-size:14px}.toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item--danger{color:#c84040}.toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.toolbar-more-dropdown .more-item--resync{color:var(--iox-success-text, #1B5E32)}.toolbar-more-dropdown .more-item--resync:hover{background:var(--iox-success-bg, #E8F3EC)}.toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.toolbar-more-dropdown .more-item-submenu{position:relative}.toolbar-more-dropdown .more-item-submenu .more-item__chevron{margin-inline-start:auto;font-size:12px;opacity:.6}.toolbar-more-dropdown .more-item-submenu:hover>.more-item{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item-submenu .more-submenu-flyout{position:absolute;top:-4px;inset-inline-start:100%;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:180px;z-index:1001}.preset-badge{position:absolute;bottom:-26px;inset-inline-start:0;display:flex;align-items:center;gap:4px;pointer-events:none;white-space:nowrap}.preset-badge__item{display:flex;align-items:center;gap:5px;padding:3px 9px;background:#cb9090;color:#fff;font-size:11px;font-weight:500;border-radius:20px;pointer-events:all;cursor:pointer;box-shadow:0 2px 6px #0000002e;transition:background .15s,outline-color .15s;outline:2px solid transparent;outline-offset:2px}.preset-badge__item i{font-size:12px}.preset-badge__item:hover{background:#be7474}.preset-badge__item--active{outline-color:#ffffffd9}.preset-badge__item--interaction{background:#7a9ecb}.preset-badge__item--interaction:hover{background:#5d89c0}.badge-context-menu{position:fixed;z-index:1001;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:160px;pointer-events:all}.badge-context-menu .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 14px;border:none;background:transparent;font-size:12px;color:#333;cursor:pointer;text-align:start;white-space:nowrap}.badge-context-menu .more-item:hover{background:var(--p-surface-50, #f9fafb)}.badge-context-menu .more-item i{font-size:13px;color:#888}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}\n"] }]
|
|
6035
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: ViewportService }, { type: PresetRegistryService }, { type: SymbolRegistryService }, { type: InteractionPresetRegistryService }, { type: InteractionEngineService }, { type: BuilderClipboardService }, { type: StyleRegistryService }, { type: PanelEventService }, { type: i0.ChangeDetectorRef }, { type: DragEngineService }
|
|
6322
|
+
args: [{ selector: 'app-overlay', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"overlay-root\">\n\n <!-- HOVER \u2014 Chrome DevTools-style rings (content area stays transparent) -->\n <ng-container *ngIf=\"hoverEntry\">\n <div class=\"hi-outline\" [ngStyle]=\"hiOutlineStyle\"></div>\n <div class=\"hi-margin\" [ngStyle]=\"hiMarginStyle\"></div>\n <div class=\"hi-padding\" [ngStyle]=\"hiPaddingStyle\"></div>\n </ng-container>\n\n <!-- SELECT \u2014 IOX pink outline + toolbar -->\n <div class=\"select-outline\" *ngIf=\"selectEntry\" [ngStyle]=\"selectOutlineStyle\">\n\n <!-- Preset badges container \u2014 below the outline, start-aligned -->\n <div class=\"preset-badge\" *ngIf=\"selectedRoleBadge || selectedPresetName || interactions.length\">\n <!-- A composite part (Card v2): its kind and role, read-only \u2014 what the layer tree's chip says. -->\n <div *ngIf=\"selectedRoleBadge\" class=\"preset-badge__item preset-badge__item--kind\" [title]=\"selectedRoleBadge\">\n <i class=\"ph-fill ph-stack\"></i>\n <span>{{ selectedRoleBadge }}</span>\n </div>\n <div *ngIf=\"selectedPresetName\"\n class=\"preset-badge__item\"\n [class.preset-badge__item--active]=\"activeBadgeType === 'style'\"\n title=\"Style preset: {{ selectedPresetName }} \u2014 click to edit, right-click to copy\"\n (mousedown)=\"onEditPreset($event)\"\n (contextmenu)=\"onBadgeContextMenu($event, 'style')\">\n <i class=\"ph-fill ph-paint-bucket\"></i>\n <span>{{ selectedPresetName }}</span>\n </div>\n <div *ngFor=\"let ix of interactions\"\n class=\"preset-badge__item preset-badge__item--interaction\"\n [class.preset-badge__item--active]=\"activeBadgeType === 'interaction'\"\n [title]=\"triggerLabel(ix.trigger) + ': ' + actionLabel(ix) + ' \u2014 click to edit, right-click to remove'\"\n (mousedown)=\"onEditInteractionPreset($event, ix)\"\n (click)=\"onBadgeClickSwallow($event)\"\n (contextmenu)=\"onBadgeContextMenu($event, 'interaction', ix)\">\n <i [class]=\"triggerIcon(ix.trigger)\"></i>\n <span>{{ triggerLabel(ix.trigger) }}: {{ actionLabel(ix) }}</span>\n </div>\n </div>\n\n <div class=\"select-toolbar\" [class.select-toolbar--inside]=\"toolbarInside\" (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n\n <!-- Drag handle \u2014 grab here to move the selected element. A click-and-hold ANYWHERE\n on the element body would otherwise grab whatever nested child the pointer lands\n on; DragEngineService redirects any drag starting here to the selected node. -->\n <button *ngIf=\"selectEntry?.node\" class=\"toolbar-btn toolbar-btn--drag-handle\"\n ioxDraggable data-iox-drag-handle\n [ioxDragData]=\"selectEntry?.node\"\n [ioxDragSourceId]=\"dragHandleSourceId\"\n title=\"Drag to move\">\n <i class=\"ph-thin ph-dots-six-vertical\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Component name -->\n <span class=\"toolbar-name\">{{ selectEntry?.componentName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n <button *ngIf=\"isDialog\" class=\"toolbar-btn\"\n [title]=\"dialogDesignOpen ? 'Close the dialog (design)' : 'Open the dialog (design)'\"\n [attr.aria-label]=\"dialogDesignOpen ? 'Close the dialog for design' : 'Open the dialog for design'\"\n (mousedown)=\"onToggleDialogOpen($event)\">\n <i [class]=\"dialogDesignOpen ? 'ph-thin ph-eye-slash' : 'ph-thin ph-eye'\"></i>\n </button>\n <button *ngIf=\"isSliderContainer\" class=\"toolbar-btn toolbar-btn--add-slide\" title=\"Add slide\" (mousedown)=\"onAddSlide($event)\">\n <i class=\"ph-thin ph-plus\"></i>\n <span class=\"toolbar-btn__label\">Add slide</span>\n </button>\n <button class=\"toolbar-btn\" title=\"Add an element here\" aria-label=\"Add an element here\" (mousedown)=\"onAddElement($event)\">\n <i class=\"ph-thin ph-plus\"></i>\n </button>\n <button class=\"toolbar-btn\" title=\"Duplicate\" (mousedown)=\"onDuplicate($event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button *ngIf=\"hasInteractions\" class=\"toolbar-btn toolbar-btn--play\" title=\"Play animations\" (mousedown)=\"onPlay($event)\">\n <i class=\"ph-thin ph-play\"></i>\n </button>\n <button *ngIf=\"isLinkedSymbol\" class=\"toolbar-btn toolbar-btn--symbol\"\n [title]=\"(linkedSymbolName ? linkedSymbolName : 'Symbol') + ' \u2014 click to detach'\"\n (mousedown)=\"onDetachSymbol($event)\">\n <i class=\"ph-thin ph-link\"></i>\n <span class=\"toolbar-btn__label\">{{ linkedSymbolName || 'Symbol' }}</span>\n </button>\n <button *ngIf=\"isDetachedSymbol\" class=\"toolbar-btn toolbar-btn--resync\"\n title=\"Detached \u2014 click to re-link\"\n (mousedown)=\"onResyncSymbol($event)\">\n <i class=\"ph-thin ph-link-break\"></i>\n <span class=\"toolbar-btn__label\">Detached</span>\n </button>\n <button class=\"toolbar-btn toolbar-btn--danger\" title=\"Delete\" (mousedown)=\"onDelete($event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n <button class=\"toolbar-btn\" title=\"Select parent\" (mousedown)=\"onSelectParent($event)\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n </button>\n\n <!-- More menu trigger \u2014 opens a fixed-position dropdown at the cursor -->\n <button class=\"toolbar-btn toolbar-btn--more\" title=\"More actions\" (mousedown)=\"onToggleMore($event)\">\n <i class=\"ph-thin ph-dots-three\"></i>\n </button>\n </div>\n </div>\n\n <!-- Shared more/context menu \u2014 fixed-position, opened by \u22EF button or right-click -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" [ngStyle]=\"moreMenuStyle\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <button class=\"more-item\" (mousedown)=\"onSelectParent($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n Select parent\n </button>\n <button class=\"more-item\" (mousedown)=\"onDuplicate($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-copy\"></i>\n Duplicate\n </button>\n <div *ngIf=\"canWrap\" class=\"more-item-submenu\"\n (mouseenter)=\"wrapSubmenuOpen = true\" (mouseleave)=\"wrapSubmenuOpen = false\">\n <button class=\"more-item\" (mousedown)=\"$event.stopPropagation()\">\n <i class=\"ph-thin ph-selection-plus\"></i>\n Wrap in\n <i class=\"ph-thin ph-caret-right more-item__chevron\"></i>\n </button>\n <div *ngIf=\"wrapSubmenuOpen\" class=\"more-submenu-flyout\">\n <button *ngFor=\"let target of wrapTargets\" class=\"more-item\"\n (mousedown)=\"onWrap($event, target.type); closeMoreMenu()\">\n <i [class]=\"target.icon\"></i>\n {{ target.label }}\n </button>\n </div>\n </div>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onDelete($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-trash\"></i>\n Delete\n </button>\n <div class=\"more-divider\"></div>\n <button class=\"more-item\" (mousedown)=\"onCopyNode($event)\">\n <i class=\"ph-thin ph-scissors\"></i>\n Copy element\n </button>\n <button *ngIf=\"hasCopiedNode\" class=\"more-item\" (mousedown)=\"onPasteNode($event)\">\n <i class=\"ph-thin ph-clipboard\"></i>\n Paste element\n </button>\n <div class=\"more-divider\"></div>\n <button *ngIf=\"selectEntry?.node?.stylePresetId\" class=\"more-item\" (mousedown)=\"onCopyPreset($event)\">\n <i class=\"ph-thin ph-paint-bucket\"></i>\n Copy style preset\n </button>\n <button *ngIf=\"hasPresetInClipboard\" class=\"more-item\" (mousedown)=\"onPastePreset($event)\">\n <i class=\"ph-thin ph-paint-bucket\"></i>\n Paste style preset\n </button>\n <button *ngIf=\"selectEntry?.node?.interactionPresetId\" class=\"more-item\" (mousedown)=\"onCopyInteractionPreset($event)\">\n <i class=\"ph-thin ph-lightning\"></i>\n Copy interaction preset\n </button>\n <button *ngIf=\"hasInteractionPresetInClipboard\" class=\"more-item\" (mousedown)=\"onPasteInteractionPreset($event)\">\n <i class=\"ph-thin ph-lightning\"></i>\n Paste interaction preset\n </button>\n <div class=\"more-divider\"></div>\n <button *ngIf=\"isLinkedSymbol\" class=\"more-item\" (mousedown)=\"onDetachSymbol($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-link-break\"></i>\n Detach from symbol<ng-container *ngIf=\"linkedSymbolName\"> ({{ linkedSymbolName }})</ng-container>\n </button>\n <button *ngIf=\"isDetachedSymbol\" class=\"more-item more-item--resync\" (mousedown)=\"onResyncSymbol($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-link\"></i>\n Re-link to symbol\n </button>\n <button class=\"more-item\" (mousedown)=\"onSaveAsBlock($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n <button *ngIf=\"hasInteractions\" class=\"more-item more-item--play\" (mousedown)=\"onPlay($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-play\"></i>\n Play animations\n </button>\n <div class=\"more-divider\"></div>\n <ng-container *ngIf=\"!selectedNodeIsGlobal; else removeGlobalItem\">\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'before')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (Before Content)\n </button>\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'after')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (After Content)\n </button>\n </ng-container>\n <ng-template #removeGlobalItem>\n <button class=\"more-item\" (mousedown)=\"onRemoveGlobal($event)\">\n <i class=\"ph-thin ph-globe\"></i>\n Remove from Global\n </button>\n </ng-template>\n </div>\n\n <!-- Backdrop to close the more menu -->\n <div *ngIf=\"moreMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu()\">\n </div>\n\n <!-- Badge context menu \u2014 options differ by badge type -->\n <div *ngIf=\"badgeMenuVisible\" class=\"badge-context-menu\"\n [ngStyle]=\"{ top: badgeMenuPos.y + 'px', left: badgeMenuPos.x + 'px' }\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n <ng-container *ngIf=\"badgeMenuType === 'style'\">\n <button class=\"more-item\" (mousedown)=\"onBadgeCopy()\">\n <i class=\"ph-thin ph-copy\"></i>\n Copy preset\n </button>\n <button class=\"more-item\" (mousedown)=\"onBadgeEdit()\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n Edit preset\n </button>\n </ng-container>\n <ng-container *ngIf=\"badgeMenuType === 'interaction'\">\n <button class=\"more-item\" (mousedown)=\"onBadgeEdit()\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n Edit in panel\n </button>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onBadgeRemoveInteraction()\">\n <i class=\"ph-thin ph-trash\"></i>\n Remove interaction\n </button>\n </ng-container>\n </div>\n <div *ngIf=\"badgeMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeBadgeMenu()\">\n </div>\n\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;position:absolute;inset:0;pointer-events:none;z-index:12;overflow:visible}.hi-outline,.hi-margin,.hi-padding{position:fixed;pointer-events:none;transition:none;box-sizing:border-box}.hi-outline{box-shadow:inset 0 0 0 1.5px #cb9090}.hi-margin{background:#f6b23361}.hi-padding{background:#60c38366}.select-outline{position:fixed;z-index:999;box-shadow:inset 0 0 0 1px #cb9090;pointer-events:none;transition:none;overflow:visible;will-change:transform;box-sizing:border-box}.select-toolbar{position:absolute;top:0;left:0;transform:translateY(-100%)}.select-toolbar.select-toolbar--inside{transform:translateY(0);border-radius:0 0 3px 3px}.select-toolbar{display:flex;align-items:center;background:#cb9090;border-radius:3px 3px 0 0;pointer-events:all;white-space:nowrap;overflow:visible}.select-toolbar .toolbar-name{padding:0 8px;font-size:11px;font-weight:500;color:#fff;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px}.select-toolbar .toolbar-sep{width:1px;height:14px;background:#ffffff59;margin:0 2px;flex-shrink:0}.select-toolbar .toolbar-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:none;background:transparent;color:#fff;font-size:13px;cursor:pointer;transition:background .15s;flex-shrink:0}.select-toolbar .toolbar-btn:hover{background:#ffffff2e}.select-toolbar .toolbar-btn--danger:hover{background:#c8323259}.select-toolbar .toolbar-btn--drag-handle{cursor:grab}.select-toolbar .toolbar-btn--drag-handle:active{cursor:grabbing}.select-toolbar .toolbar-btn--symbol{width:auto;padding:0 7px;gap:4px;color:#fff;background:#f59e0b;border-radius:3px;font-size:11px}.select-toolbar .toolbar-btn--symbol:hover{background:#d97706}.select-toolbar .toolbar-btn--resync{width:auto;padding:0 7px;gap:4px;color:#fff;background:var(--iox-success-fill, #2E7D45);border-radius:3px;font-size:11px}.select-toolbar .toolbar-btn--resync:hover{background:color-mix(in srgb,var(--iox-success-fill, #2E7D45) 86%,#000)}.select-toolbar .toolbar-btn .toolbar-btn__label{font-size:11px;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select-toolbar .toolbar-btn--more{border-radius:0 3px 0 0}.toolbar-more-dropdown{position:fixed;z-index:1000;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all}.toolbar-more-dropdown .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:start;white-space:nowrap}.toolbar-more-dropdown .more-item i{font-size:14px}.toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item--danger{color:#c84040}.toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.toolbar-more-dropdown .more-item--resync{color:var(--iox-success-text, #1B5E32)}.toolbar-more-dropdown .more-item--resync:hover{background:var(--iox-success-bg, #E8F3EC)}.toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.toolbar-more-dropdown .more-item-submenu{position:relative}.toolbar-more-dropdown .more-item-submenu .more-item__chevron{margin-inline-start:auto;font-size:12px;opacity:.6}.toolbar-more-dropdown .more-item-submenu:hover>.more-item{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item-submenu .more-submenu-flyout{position:absolute;top:-4px;inset-inline-start:100%;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:180px;z-index:1001}.preset-badge{position:absolute;bottom:-26px;inset-inline-start:0;display:flex;align-items:center;gap:4px;pointer-events:none;white-space:nowrap}.preset-badge__item{display:flex;align-items:center;gap:5px;padding:3px 9px;background:#cb9090;color:#fff;font-size:11px;font-weight:500;border-radius:20px;pointer-events:all;cursor:pointer;box-shadow:0 2px 6px #0000002e;transition:background .15s,outline-color .15s;outline:2px solid transparent;outline-offset:2px}.preset-badge__item i{font-size:12px}.preset-badge__item:hover{background:#be7474}.preset-badge__item--active{outline-color:#ffffffd9}.preset-badge__item--interaction{background:#7a9ecb}.preset-badge__item--interaction:hover{background:#5d89c0}.preset-badge__item--kind{background:#8a3a72;cursor:default}.preset-badge__item--kind:hover{background:#8a3a72}.badge-context-menu{position:fixed;z-index:1001;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:160px;pointer-events:all}.badge-context-menu .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 14px;border:none;background:transparent;font-size:12px;color:#333;cursor:pointer;text-align:start;white-space:nowrap}.badge-context-menu .more-item:hover{background:var(--p-surface-50, #f9fafb)}.badge-context-menu .more-item i{font-size:13px;color:#888}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}\n"] }]
|
|
6323
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: ViewportService }, { type: PresetRegistryService }, { type: SymbolRegistryService }, { type: InteractionPresetRegistryService }, { type: InteractionEngineService }, { type: BuilderClipboardService }, { type: StyleRegistryService }, { type: PanelEventService }, { type: i0.ChangeDetectorRef }, { type: DragEngineService }, { type: undefined, decorators: [{
|
|
6324
|
+
type: Optional
|
|
6325
|
+
}, {
|
|
6326
|
+
type: Inject,
|
|
6327
|
+
args: [IOX_BUILDER_LABELS]
|
|
6328
|
+
}] }], propDecorators: { toolbarAction: [{
|
|
6036
6329
|
type: Output
|
|
6037
6330
|
}] } });
|
|
6038
6331
|
|
|
@@ -6209,6 +6502,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
6209
6502
|
args: ['widthPopover']
|
|
6210
6503
|
}] } });
|
|
6211
6504
|
|
|
6505
|
+
const LOCKED_PART_KEY = 'builder.chrome.lockedPart';
|
|
6506
|
+
const COMPOSITE_ROLE_KEY = 'builder.chrome.compositeRole';
|
|
6212
6507
|
class LayerTreeComponent {
|
|
6213
6508
|
constructor(registry, overlayService, dragEngine) {
|
|
6214
6509
|
this.registry = registry;
|
|
@@ -6226,11 +6521,16 @@ class LayerTreeComponent {
|
|
|
6226
6521
|
this.editingNodeId = null;
|
|
6227
6522
|
this.editingValue = '';
|
|
6228
6523
|
this.activeSection = 'page';
|
|
6524
|
+
this.labels = inject(IOX_BUILDER_LABELS);
|
|
6525
|
+
this.lockedPartTitle = this.labels(LOCKED_PART_KEY, BUILDER_CHROME_EN[LOCKED_PART_KEY]);
|
|
6526
|
+
this.roleTitle = this.labels(COMPOSITE_ROLE_KEY, BUILDER_CHROME_EN[COMPOSITE_ROLE_KEY]);
|
|
6229
6527
|
/** Cache: component type → icon class */
|
|
6230
6528
|
this.iconMap = new Map();
|
|
6231
6529
|
/** Last-seen structural signature — used by ngDoCheck to skip no-op rebuilds. */
|
|
6232
6530
|
this._layoutSig = '';
|
|
6233
6531
|
this._globalSig = '';
|
|
6532
|
+
/** The eye-slash glyph's title, through the host's i18n (builder.chrome.*). */
|
|
6533
|
+
this.hiddenAtRestTitle = this.labels(HIDDEN_AT_REST_LABEL_KEY, BUILDER_CHROME_EN[HIDDEN_AT_REST_LABEL_KEY]);
|
|
6234
6534
|
this.expandedState = new Map();
|
|
6235
6535
|
}
|
|
6236
6536
|
ngOnInit() {
|
|
@@ -6379,11 +6679,13 @@ class LayerTreeComponent {
|
|
|
6379
6679
|
node,
|
|
6380
6680
|
icon: this.iconMap.get(node.type) || 'ph-thin ph-cube',
|
|
6381
6681
|
label: node.label ?? node.type,
|
|
6682
|
+
hiddenAtRest: isHiddenAtRest(node),
|
|
6382
6683
|
depth,
|
|
6383
6684
|
expanded,
|
|
6384
6685
|
hasChildren,
|
|
6385
6686
|
parentArray: arr,
|
|
6386
6687
|
indexInParent: i,
|
|
6688
|
+
locked: isLockedPart(node),
|
|
6387
6689
|
};
|
|
6388
6690
|
out.push(treeNode);
|
|
6389
6691
|
if (node.children && expanded) {
|
|
@@ -6392,11 +6694,11 @@ class LayerTreeComponent {
|
|
|
6392
6694
|
}
|
|
6393
6695
|
}
|
|
6394
6696
|
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 }); }
|
|
6395
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.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 <span *ngIf=\"flatGlobalTree.length\" class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n </button>\n </div>\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 <div *ngIf=\"activeSection === 'page'\"\n class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n 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 <button *ngIf=\"tn.hasChildren\" 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 <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\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 </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\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 <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 </div>\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 <div *ngIf=\"activeSection === 'global'\" class=\"tree-body\">\n <div *ngIf=\"!flatGlobalTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n\n <div *ngFor=\"let tn of flatGlobalTree\"\n 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 <button *ngIf=\"tn.hasChildren\" 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 <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\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 </div>\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;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.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-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.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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"] }] }); }
|
|
6697
|
+
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 <span *ngIf=\"flatGlobalTree.length\" class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n </button>\n </div>\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 <div *ngIf=\"activeSection === 'page'\"\n class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n 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 <button *ngIf=\"tn.hasChildren\" 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 <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\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 </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\n <!-- A composite part's role (Card v2, board 8 / R2) and, on a locked part, a lock where Delete was. -->\n <span *ngIf=\"tn.node.role\" class=\"node-role\" [title]=\"roleTitle\">{{ tn.node.role }}</span>\n <i *ngIf=\"tn.locked\" class=\"ph-thin ph-lock-simple node-lock\" [title]=\"lockedPartTitle\" [attr.aria-label]=\"lockedPartTitle\"></i>\n <span *ngIf=\"tn.hiddenAtRest\" class=\"node-rest\" [title]=\"hiddenAtRestTitle\" [attr.aria-label]=\"hiddenAtRestTitle\"><i class=\"ph-thin ph-eye-slash\"></i></span>\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 <button *ngIf=\"!tn.locked\" 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 </div>\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 <div *ngIf=\"activeSection === 'global'\" class=\"tree-body\">\n <div *ngIf=\"!flatGlobalTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n\n <div *ngFor=\"let tn of flatGlobalTree\"\n 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 <button *ngIf=\"tn.hasChildren\" 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 <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span *ngIf=\"tn.hiddenAtRest\" class=\"node-rest\" [title]=\"hiddenAtRestTitle\" [attr.aria-label]=\"hiddenAtRestTitle\"><i class=\"ph-thin ph-eye-slash\"></i></span>\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 </div>\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;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-role{flex:none;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.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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"] }] }); }
|
|
6396
6698
|
}
|
|
6397
6699
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: LayerTreeComponent, decorators: [{
|
|
6398
6700
|
type: Component,
|
|
6399
|
-
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 <span *ngIf=\"flatGlobalTree.length\" class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n </button>\n </div>\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 <div *ngIf=\"activeSection === 'page'\"\n class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n 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 <button *ngIf=\"tn.hasChildren\" 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 <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\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 </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\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 <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 </div>\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 <div *ngIf=\"activeSection === 'global'\" class=\"tree-body\">\n <div *ngIf=\"!flatGlobalTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n\n <div *ngFor=\"let tn of flatGlobalTree\"\n 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 <button *ngIf=\"tn.hasChildren\" 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 <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\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 </div>\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;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.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-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"] }]
|
|
6701
|
+
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 <span *ngIf=\"flatGlobalTree.length\" class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n </button>\n </div>\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 <div *ngIf=\"activeSection === 'page'\"\n class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n 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 <button *ngIf=\"tn.hasChildren\" 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 <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\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 </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\n <!-- A composite part's role (Card v2, board 8 / R2) and, on a locked part, a lock where Delete was. -->\n <span *ngIf=\"tn.node.role\" class=\"node-role\" [title]=\"roleTitle\">{{ tn.node.role }}</span>\n <i *ngIf=\"tn.locked\" class=\"ph-thin ph-lock-simple node-lock\" [title]=\"lockedPartTitle\" [attr.aria-label]=\"lockedPartTitle\"></i>\n <span *ngIf=\"tn.hiddenAtRest\" class=\"node-rest\" [title]=\"hiddenAtRestTitle\" [attr.aria-label]=\"hiddenAtRestTitle\"><i class=\"ph-thin ph-eye-slash\"></i></span>\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 <button *ngIf=\"!tn.locked\" 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 </div>\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 <div *ngIf=\"activeSection === 'global'\" class=\"tree-body\">\n <div *ngIf=\"!flatGlobalTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n\n <div *ngFor=\"let tn of flatGlobalTree\"\n 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 <button *ngIf=\"tn.hasChildren\" 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 <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span *ngIf=\"tn.hiddenAtRest\" class=\"node-rest\" [title]=\"hiddenAtRestTitle\" [attr.aria-label]=\"hiddenAtRestTitle\"><i class=\"ph-thin ph-eye-slash\"></i></span>\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 </div>\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;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-role{flex:none;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"] }]
|
|
6400
6702
|
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: DragEngineService }], propDecorators: { layout: [{
|
|
6401
6703
|
type: Input
|
|
6402
6704
|
}], globalElements: [{
|
|
@@ -6576,6 +6878,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
6576
6878
|
* fitting (the frame is clipped by exactly the difference).
|
|
6577
6879
|
*/
|
|
6578
6880
|
const CANVAS_INSET = 20;
|
|
6881
|
+
/** How long a selection waits for a just-revealed part (a closed drawer) to render its children. */
|
|
6882
|
+
const REVEAL_RETRIES = 10;
|
|
6883
|
+
const REVEAL_RETRY_MS = 30;
|
|
6579
6884
|
/** The page-style declarations a child INHERITS (text colour, typeface) — never the background. */
|
|
6580
6885
|
const INHERITED_PAGE_PROPS = ['color', 'font-family'];
|
|
6581
6886
|
function pickInherited(css) {
|
|
@@ -6641,6 +6946,7 @@ class BuilderComponent {
|
|
|
6641
6946
|
height: rect.height / scale,
|
|
6642
6947
|
left: (rect.left - frame.left) / scale,
|
|
6643
6948
|
top: (rect.top - frame.top) / scale,
|
|
6949
|
+
unnamedControl: needsAccessibleName(node),
|
|
6644
6950
|
});
|
|
6645
6951
|
}
|
|
6646
6952
|
if (node.children?.length)
|
|
@@ -6736,14 +7042,53 @@ class BuilderComponent {
|
|
|
6736
7042
|
for (const finding of this.auditFindings.filter(f => !!f.fix || !!f.inputFix))
|
|
6737
7043
|
this.applyAuditFix(finding);
|
|
6738
7044
|
}
|
|
7045
|
+
/** The canvas reserve for a bottom bar among the globals (the same class the live site keys on). */
|
|
7046
|
+
refreshCanvasBarReserve() {
|
|
7047
|
+
let reserve = null;
|
|
7048
|
+
for (const g of this.globalElements)
|
|
7049
|
+
reserve = reserve ?? barReserveClass({ ...g, styleTraits: g.styleTraits });
|
|
7050
|
+
this.canvasBarReserve = reserve;
|
|
7051
|
+
}
|
|
6739
7052
|
/** Select the node a finding is about. */
|
|
6740
7053
|
selectAuditNode(finding) {
|
|
6741
7054
|
const node = this.findNodeById(finding.nodeId, this.layout) ?? this.findNodeById(finding.nodeId, this.globalElements);
|
|
6742
|
-
|
|
6743
|
-
if (!node || !ref)
|
|
7055
|
+
if (!node)
|
|
6744
7056
|
return;
|
|
6745
|
-
this.
|
|
6746
|
-
|
|
7057
|
+
this._withNodeRef(node, ref => {
|
|
7058
|
+
this.overlayService.setSelect(ref.element, node.type, this.activeMode, node, ref.componentRef);
|
|
7059
|
+
this._emitElementSelect(node, ref.componentRef);
|
|
7060
|
+
});
|
|
7061
|
+
}
|
|
7062
|
+
/**
|
|
7063
|
+
* Run `select` with the node's rendered ref — at once when it is on the canvas, else after REVEALING
|
|
7064
|
+
* the part hidden at rest it sits in (R2, CC1: a link inside the closed drawer, picked from the layer
|
|
7065
|
+
* tree, the breadcrumb or an Items row). The selection path is set first, which shows the drawer
|
|
7066
|
+
* through the canvas design state; its children render on the next pass, and the selection lands.
|
|
7067
|
+
*/
|
|
7068
|
+
_withNodeRef(node, select) {
|
|
7069
|
+
const ref = this.overlayService.getNodeRef(node);
|
|
7070
|
+
if (ref) {
|
|
7071
|
+
select(ref);
|
|
7072
|
+
return;
|
|
7073
|
+
}
|
|
7074
|
+
const path = nodeAncestorPathInEither(this.layout, this.globalElements, node);
|
|
7075
|
+
if (!path?.some(isHiddenAtRest))
|
|
7076
|
+
return;
|
|
7077
|
+
this.selectedItem = node;
|
|
7078
|
+
this.selectedPath = path;
|
|
7079
|
+
this.cdr.markForCheck();
|
|
7080
|
+
let tries = 0;
|
|
7081
|
+
const retry = () => {
|
|
7082
|
+
const revealed = this.overlayService.getNodeRef(node);
|
|
7083
|
+
if (revealed) {
|
|
7084
|
+
select(revealed);
|
|
7085
|
+
this.cdr.markForCheck();
|
|
7086
|
+
return;
|
|
7087
|
+
}
|
|
7088
|
+
if (++tries < REVEAL_RETRIES)
|
|
7089
|
+
setTimeout(retry, REVEAL_RETRY_MS);
|
|
7090
|
+
};
|
|
7091
|
+
setTimeout(retry);
|
|
6747
7092
|
}
|
|
6748
7093
|
/** A node's full base style map, flattened from its styleTraits. */
|
|
6749
7094
|
baseStyleMapOf(node) {
|
|
@@ -6776,6 +7121,8 @@ class BuilderComponent {
|
|
|
6776
7121
|
set selectedPath(path) {
|
|
6777
7122
|
this._selectedPath = path;
|
|
6778
7123
|
this.formStateService.setTarget(nearestFormNode(path));
|
|
7124
|
+
// R2 (CC1): a part hidden at rest that the selection sits in (a closed drawer) shows while selected.
|
|
7125
|
+
this.canvasDesignState.setSelectionPath(path);
|
|
6779
7126
|
}
|
|
6780
7127
|
// Type-safe event forwarding for toolbar outputs
|
|
6781
7128
|
handleToolbarModeChange(mode) {
|
|
@@ -6843,7 +7190,7 @@ class BuilderComponent {
|
|
|
6843
7190
|
get canvasMinHeight() {
|
|
6844
7191
|
return this.viewportService.getState().height;
|
|
6845
7192
|
}
|
|
6846
|
-
constructor(registry, overlayService, panelEventService, dragEngine, dsRegistry, viewportService, formStateService, interactionEngine, interactionPresetRegistry, clipboard, symbolRegistry, cdr, appRef, moveableCanvas, selectoCanvas, styleRegistry) {
|
|
7193
|
+
constructor(registry, overlayService, panelEventService, dragEngine, dsRegistry, viewportService, formStateService, canvasDesignState, interactionEngine, interactionPresetRegistry, clipboard, symbolRegistry, cdr, appRef, moveableCanvas, selectoCanvas, styleRegistry) {
|
|
6847
7194
|
this.registry = registry;
|
|
6848
7195
|
this.overlayService = overlayService;
|
|
6849
7196
|
this.panelEventService = panelEventService;
|
|
@@ -6851,6 +7198,7 @@ class BuilderComponent {
|
|
|
6851
7198
|
this.dsRegistry = dsRegistry;
|
|
6852
7199
|
this.viewportService = viewportService;
|
|
6853
7200
|
this.formStateService = formStateService;
|
|
7201
|
+
this.canvasDesignState = canvasDesignState;
|
|
6854
7202
|
this.interactionEngine = interactionEngine;
|
|
6855
7203
|
this.interactionPresetRegistry = interactionPresetRegistry;
|
|
6856
7204
|
this.clipboard = clipboard;
|
|
@@ -6887,6 +7235,8 @@ class BuilderComponent {
|
|
|
6887
7235
|
this.pageStatus = 'draft';
|
|
6888
7236
|
this.pageSettings = null;
|
|
6889
7237
|
this.globalElements = [];
|
|
7238
|
+
/** The app path of the page on the canvas (`/work`): its links render as the current page (CC1 N2). */
|
|
7239
|
+
this.pagePath = '';
|
|
6890
7240
|
this.canUndo = false;
|
|
6891
7241
|
this.canRedo = false;
|
|
6892
7242
|
/** Feature flag: drive drag/select/resize via Moveable + Selecto instead of interact.js.
|
|
@@ -6951,6 +7301,9 @@ class BuilderComponent {
|
|
|
6951
7301
|
* the site global elements sit inside <body> and inherit it; on the canvas they are a sibling
|
|
6952
7302
|
* layer of the page, so they get it explicitly. Never the background (it would cover the page). */
|
|
6953
7303
|
this.globalLayerCss = {};
|
|
7304
|
+
/** The page reserves a bottom bar's height on the canvas where the bar shows (CC1 R1) — design-core's
|
|
7305
|
+
* `barReserveClass` of the bar among the globals, or null. A field, recomputed when globals change. */
|
|
7306
|
+
this.canvasBarReserve = null;
|
|
6954
7307
|
/** Canvas frame height in canvas-space pixels (= previewScroll.clientHeight / scale).
|
|
6955
7308
|
* Used as iox-page-component's height so position:fixed top:0;bottom:0 fills the
|
|
6956
7309
|
* same area as height:100vh (both resolve to this value). */
|
|
@@ -7015,7 +7368,13 @@ class BuilderComponent {
|
|
|
7015
7368
|
this.pageStyleCss = compilePageStyle(this.pageSettings?.style, { designSystem: this.pageSettings?.designSystem === true });
|
|
7016
7369
|
this.globalLayerCss = pickInherited(this.pageStyleCss);
|
|
7017
7370
|
}
|
|
7371
|
+
if (changes['pagePath']) {
|
|
7372
|
+
// A different page: its own current links, and no part left revealed from the last one.
|
|
7373
|
+
this.canvasDesignState.reset();
|
|
7374
|
+
this.canvasDesignState.setPagePath(this.pagePath);
|
|
7375
|
+
}
|
|
7018
7376
|
if (changes['globalElements']) {
|
|
7377
|
+
this.refreshCanvasBarReserve();
|
|
7019
7378
|
// The *ngIf on the global overlay renders after this hook — defer so
|
|
7020
7379
|
// globalOverlayRef is in the DOM before we try to set CSS vars on it.
|
|
7021
7380
|
setTimeout(() => this.updatePreviewVh());
|
|
@@ -7458,12 +7817,11 @@ class BuilderComponent {
|
|
|
7458
7817
|
}
|
|
7459
7818
|
/** Breadcrumb crumb clicked — select that ancestor. Mirrors a layer-tree click. */
|
|
7460
7819
|
onCrumbSelect(node) {
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
this._emitElementSelect(node, ref.componentRef);
|
|
7820
|
+
this._withNodeRef(node, ref => {
|
|
7821
|
+
this.selectedItem = node;
|
|
7822
|
+
this.overlayService.setSelect(ref.element, node.type, this.activeMode, node, ref.componentRef);
|
|
7823
|
+
this._emitElementSelect(node, ref.componentRef);
|
|
7824
|
+
});
|
|
7467
7825
|
}
|
|
7468
7826
|
/**
|
|
7469
7827
|
* Body crumb clicked — the page surface itself, which is not a node in the layout tree.
|
|
@@ -7491,10 +7849,10 @@ class BuilderComponent {
|
|
|
7491
7849
|
}
|
|
7492
7850
|
onTreeNodeSelect(node) {
|
|
7493
7851
|
this.selectedItem = node;
|
|
7494
|
-
|
|
7495
|
-
|
|
7852
|
+
this._withNodeRef(node, ref => {
|
|
7853
|
+
this.overlayService.setSelect(ref.element, node.type, this.activeMode, node, ref.componentRef);
|
|
7496
7854
|
this._emitElementSelect(node, ref.componentRef);
|
|
7497
|
-
}
|
|
7855
|
+
});
|
|
7498
7856
|
this.selectPanel(PanelTypes.STYLES);
|
|
7499
7857
|
}
|
|
7500
7858
|
onTreeNodeAction(event) {
|
|
@@ -7514,6 +7872,8 @@ class BuilderComponent {
|
|
|
7514
7872
|
if (!result)
|
|
7515
7873
|
return;
|
|
7516
7874
|
if (action === NodeAction.Delete) {
|
|
7875
|
+
if (this.dragEngine.refuseRemoval(node))
|
|
7876
|
+
return;
|
|
7517
7877
|
result.parent.splice(result.index, 1);
|
|
7518
7878
|
this.selectedItem = null;
|
|
7519
7879
|
this.overlayService.clearSelect();
|
|
@@ -7549,7 +7909,7 @@ class BuilderComponent {
|
|
|
7549
7909
|
}
|
|
7550
7910
|
case ToolbarAction.Delete: {
|
|
7551
7911
|
const result = this.findNodeInTree(node, this.layout) ?? this.findNodeInTree(node, this.globalElements);
|
|
7552
|
-
if (result) {
|
|
7912
|
+
if (result && !this.dragEngine.refuseRemoval(node)) {
|
|
7553
7913
|
result.parent.splice(result.index, 1);
|
|
7554
7914
|
this.selectedItem = null;
|
|
7555
7915
|
this.overlayService.clearSelect();
|
|
@@ -7629,7 +7989,7 @@ class BuilderComponent {
|
|
|
7629
7989
|
}
|
|
7630
7990
|
case ToolbarAction.MakeGlobal: {
|
|
7631
7991
|
const result = this.findNodeInTree(node, this.layout);
|
|
7632
|
-
if (result) {
|
|
7992
|
+
if (result && !this.dragEngine.refuseRemoval(node)) {
|
|
7633
7993
|
result.parent.splice(result.index, 1);
|
|
7634
7994
|
node.isGlobal = true;
|
|
7635
7995
|
node.globalPosition = event.position ?? 'before';
|
|
@@ -7861,6 +8221,16 @@ class BuilderComponent {
|
|
|
7861
8221
|
/** If `node` lives inside a Repeater template, tell that repeater to rebuild its preview rows so
|
|
7862
8222
|
* a structural change (e.g. wrapping a template child) propagates to every repeated row. No-op
|
|
7863
8223
|
* when the node is not inside a repeater. */
|
|
8224
|
+
/** NODE_STRUCTURE_CHANGED: the subtree's children render (CD), then every node's rules, classes and
|
|
8225
|
+
* inputs are re-applied (a moved node's rule may have been dropped by its old host's teardown). */
|
|
8226
|
+
applyStructureChange(node) {
|
|
8227
|
+
this.cdr.detectChanges();
|
|
8228
|
+
refreshSubtree(node, this.styleRegistry, n => this.overlayService.getNodeRef(n)?.componentRef, canvasEmptyImageLabel(this.labels));
|
|
8229
|
+
this._refreshRepeaterFor(node);
|
|
8230
|
+
this.cdr.detectChanges();
|
|
8231
|
+
this.overlayService.refreshSelect();
|
|
8232
|
+
this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
|
|
8233
|
+
}
|
|
7864
8234
|
_refreshRepeaterFor(node) {
|
|
7865
8235
|
const repeater = this.findRepeaterAncestorOf(node, this.layout)
|
|
7866
8236
|
?? this.findRepeaterAncestorOf(node, this.globalElements);
|
|
@@ -7948,23 +8318,32 @@ class BuilderComponent {
|
|
|
7948
8318
|
this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
|
|
7949
8319
|
return;
|
|
7950
8320
|
}
|
|
8321
|
+
// A panel rewrote a subtree in place (a Card's Parts / Layout / Link): re-render it, re-register
|
|
8322
|
+
// every node's rules and classes, refresh a Repeater holding it, and record ONE undo step.
|
|
8323
|
+
if (panelEvent.type === PanelEventTypes.NODE_STRUCTURE_CHANGED) {
|
|
8324
|
+
const node = panelEvent.data?.node;
|
|
8325
|
+
if (node)
|
|
8326
|
+
this.applyStructureChange(node);
|
|
8327
|
+
return;
|
|
8328
|
+
}
|
|
7951
8329
|
// A panel (e.g. the Form's nested field list) wants to select a node it already holds a
|
|
7952
8330
|
// reference to — same path onTreeNodeSelect/onCrumbSelect use, so selectedItem/selectedPath
|
|
7953
8331
|
// and every panel stay in sync, not just the overlay's own visual selection box.
|
|
7954
8332
|
if (panelEvent.type === PanelEventTypes.SELECT_NODE) {
|
|
7955
8333
|
const node = panelEvent.data?.node;
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
8334
|
+
if (node)
|
|
8335
|
+
this._withNodeRef(node, ref => {
|
|
8336
|
+
this.selectedItem = node;
|
|
8337
|
+
this.overlayService.setSelect(ref.element, node.type, this.activeMode, node, ref.componentRef);
|
|
8338
|
+
this._emitElementSelect(node, ref.componentRef);
|
|
8339
|
+
});
|
|
7962
8340
|
return;
|
|
7963
8341
|
}
|
|
7964
8342
|
// Every style or structural edit — including a breakpoint override — emits LAYOUT_CHANGED.
|
|
7965
8343
|
if (panelEvent.type === PanelEventTypes.LAYOUT_CHANGED) {
|
|
7966
8344
|
this.refreshBreakpointCounts();
|
|
7967
8345
|
this.refreshStructureFindings();
|
|
8346
|
+
this.refreshCanvasBarReserve();
|
|
7968
8347
|
return;
|
|
7969
8348
|
}
|
|
7970
8349
|
if (panelEvent.type === PanelEventTypes.PANEL_CLOSE) {
|
|
@@ -7975,13 +8354,13 @@ class BuilderComponent {
|
|
|
7975
8354
|
this.isPanelOpen = true;
|
|
7976
8355
|
}
|
|
7977
8356
|
}
|
|
7978
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: PanelEventService }, { token: DragEngineService }, { token: DataSourceRegistryService }, { token: ViewportService }, { token: FormStateService }, { token: InteractionEngineService }, { token: InteractionPresetRegistryService }, { token: BuilderClipboardService }, { token: SymbolRegistryService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }, { token: MoveableCanvasService }, { token: SelectoCanvasService }, { token: StyleRegistryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7979
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length || structureFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\"\n (dragstart)=\"onNativeDragStart($event)\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i16.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i16.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i16.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i17.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i1.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "rootSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
8357
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: PanelEventService }, { token: DragEngineService }, { token: DataSourceRegistryService }, { token: ViewportService }, { token: FormStateService }, { token: CanvasDesignStateService }, { token: InteractionEngineService }, { token: InteractionPresetRegistryService }, { token: BuilderClipboardService }, { token: SymbolRegistryService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }, { token: MoveableCanvasService }, { token: SelectoCanvasService }, { token: StyleRegistryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8358
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements", pagePath: "pagePath", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length || structureFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\"\n (dragstart)=\"onNativeDragStart($event)\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [attr.data-bar-reserve]=\"canvasBarReserve\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder #canvas-preview[data-bar-reserve=iox-bar-reserve]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}@container iox-canvas (max-width: 1200px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-laptop]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 991px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-tablet]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 767px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-mobile]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 479px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-small]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i17.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i17.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i17.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i18.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i1.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "rootSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
7980
8359
|
}
|
|
7981
8360
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, decorators: [{
|
|
7982
8361
|
type: Component,
|
|
7983
|
-
args: [{ selector: 'app-builder', standalone: false, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length || structureFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\"\n (dragstart)=\"onNativeDragStart($event)\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
7984
|
-
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: FormStateService }, { type: InteractionEngineService }, { type: InteractionPresetRegistryService }, { type: BuilderClipboardService }, { type: SymbolRegistryService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: MoveableCanvasService }, { type: SelectoCanvasService }, { type: StyleRegistryService }], propDecorators: { layout: [{
|
|
8362
|
+
args: [{ selector: 'app-builder', standalone: false, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length || structureFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\"\n (dragstart)=\"onNativeDragStart($event)\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [attr.data-bar-reserve]=\"canvasBarReserve\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder #canvas-preview[data-bar-reserve=iox-bar-reserve]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}@container iox-canvas (max-width: 1200px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-laptop]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 991px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-tablet]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 767px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-mobile]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 479px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-small]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
8363
|
+
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: FormStateService }, { type: CanvasDesignStateService }, { type: InteractionEngineService }, { type: InteractionPresetRegistryService }, { type: BuilderClipboardService }, { type: SymbolRegistryService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: MoveableCanvasService }, { type: SelectoCanvasService }, { type: StyleRegistryService }], propDecorators: { layout: [{
|
|
7985
8364
|
type: Input
|
|
7986
8365
|
}], dataSources: [{
|
|
7987
8366
|
type: Input
|
|
@@ -7999,6 +8378,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
7999
8378
|
type: Input
|
|
8000
8379
|
}], globalElements: [{
|
|
8001
8380
|
type: Input
|
|
8381
|
+
}], pagePath: [{
|
|
8382
|
+
type: Input
|
|
8002
8383
|
}], canUndo: [{
|
|
8003
8384
|
type: Input
|
|
8004
8385
|
}], canRedo: [{
|
|
@@ -9071,11 +9452,12 @@ class BuilderDialogComponent {
|
|
|
9071
9452
|
const allowed = ['center', 'top', 'bottom', 'start', 'end'];
|
|
9072
9453
|
return allowed.indexOf(String(this.placement)) >= 0 ? String(this.placement) : 'center';
|
|
9073
9454
|
}
|
|
9074
|
-
constructor(dragEngine, instanceRegistry, overlayService, cdr) {
|
|
9455
|
+
constructor(dragEngine, instanceRegistry, overlayService, cdr, designState) {
|
|
9075
9456
|
this.dragEngine = dragEngine;
|
|
9076
9457
|
this.instanceRegistry = instanceRegistry;
|
|
9077
9458
|
this.overlayService = overlayService;
|
|
9078
9459
|
this.cdr = cdr;
|
|
9460
|
+
this.designState = designState;
|
|
9079
9461
|
this.children = [];
|
|
9080
9462
|
this.nodeId = '';
|
|
9081
9463
|
this.dropListId = '';
|
|
@@ -9087,7 +9469,21 @@ class BuilderDialogComponent {
|
|
|
9087
9469
|
this.childMouseLeave = new EventEmitter();
|
|
9088
9470
|
/** Canvas-only visibility. Never serialised; the live site always starts closed. */
|
|
9089
9471
|
this.designOpen = false;
|
|
9472
|
+
/** Opened by an action on the canvas (the canvas is a live runtime): stays until something closes it. */
|
|
9473
|
+
this.runtimeOpen = false;
|
|
9474
|
+
/** Closed by its X / backdrop while the selection or the switcher still shows it: hidden until either changes. */
|
|
9475
|
+
this.dismissed = false;
|
|
9476
|
+
}
|
|
9477
|
+
/** Shown when an action opened it, or the canvas design state shows it (selection inside / switched open). */
|
|
9478
|
+
recompute() {
|
|
9479
|
+
const shown = !this.dismissed && (this.runtimeOpen || !!this.designState?.isShown(this.nodeId));
|
|
9480
|
+
if (shown === this.designOpen)
|
|
9481
|
+
return;
|
|
9482
|
+
this.designOpen = shown;
|
|
9483
|
+
this.cdr.markForCheck();
|
|
9090
9484
|
}
|
|
9485
|
+
openRuntime() { this.runtimeOpen = true; this.dismissed = false; this.recompute(); }
|
|
9486
|
+
closeRuntime() { this.runtimeOpen = false; this.dismissed = true; this.recompute(); }
|
|
9091
9487
|
ngOnInit() {
|
|
9092
9488
|
// Board A4: "Closed later? Click it [in the tree] and it reopens." Selection IS the
|
|
9093
9489
|
// reopen signal — tree row, breadcrumb crumb, target-picker jump all land here.
|
|
@@ -9104,23 +9500,31 @@ class BuilderDialogComponent {
|
|
|
9104
9500
|
// node a tick after it renders, and `select$` is a BehaviorSubject — so a dialog dropped
|
|
9105
9501
|
// right now still lands open, through the line below, while one that was merely loaded
|
|
9106
9502
|
// stays shut because nothing is selected on load.
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
}
|
|
9113
|
-
}
|
|
9503
|
+
//
|
|
9504
|
+
// CC1 (R2, board 3): with the canvas design state present, "selected" means the selection is the
|
|
9505
|
+
// dialog OR ANY PART INSIDE IT (a drawer link picked from the tree), and DESELECTING closes it
|
|
9506
|
+
// again — a closed overlay has no canvas presence. The "Showing on canvas" switcher holds it open.
|
|
9507
|
+
if (this.designState) {
|
|
9508
|
+
this.stateSub = this.designState.state$.subscribe(() => { this.dismissed = false; this.recompute(); });
|
|
9509
|
+
}
|
|
9510
|
+
else {
|
|
9511
|
+
this.selectSub = this.overlayService.select$?.subscribe?.((sel) => {
|
|
9512
|
+
const id = sel?.node?.id ?? sel?.nodeId;
|
|
9513
|
+
if (id && id === this.nodeId && !this.designOpen)
|
|
9514
|
+
this.openRuntime();
|
|
9515
|
+
});
|
|
9516
|
+
}
|
|
9114
9517
|
// Open/close/toggle actions (and A1c's panel) reach the canvas twin through here.
|
|
9115
9518
|
this.instanceRegistry.register(this.nodeId, {
|
|
9116
|
-
open: () =>
|
|
9117
|
-
close: () =>
|
|
9118
|
-
toggle: () =>
|
|
9519
|
+
open: () => this.openRuntime(),
|
|
9520
|
+
close: () => this.closeRuntime(),
|
|
9521
|
+
toggle: () => (this.designOpen ? this.closeRuntime() : this.openRuntime()),
|
|
9119
9522
|
isOpen: () => this.designOpen,
|
|
9120
9523
|
});
|
|
9121
9524
|
}
|
|
9122
9525
|
ngOnDestroy() {
|
|
9123
9526
|
this.selectSub?.unsubscribe();
|
|
9527
|
+
this.stateSub?.unsubscribe();
|
|
9124
9528
|
this.instanceRegistry.unregister(this.nodeId);
|
|
9125
9529
|
}
|
|
9126
9530
|
/** A click on the stage ITSELF is the backdrop click — children swallow everything else.
|
|
@@ -9132,21 +9536,21 @@ class BuilderDialogComponent {
|
|
|
9132
9536
|
return;
|
|
9133
9537
|
if (this.closeOnBackdrop !== false && this.closeOnBackdrop !== 'false') {
|
|
9134
9538
|
event.stopPropagation();
|
|
9135
|
-
this.
|
|
9539
|
+
this.closeRuntime();
|
|
9136
9540
|
}
|
|
9137
9541
|
}
|
|
9138
9542
|
onCloseClick(event) {
|
|
9139
9543
|
event.stopPropagation();
|
|
9140
|
-
this.
|
|
9544
|
+
this.closeRuntime();
|
|
9141
9545
|
}
|
|
9142
9546
|
onBackdrop() {
|
|
9143
9547
|
if (this.closeOnBackdrop !== false && this.closeOnBackdrop !== 'false')
|
|
9144
|
-
this.
|
|
9548
|
+
this.closeRuntime();
|
|
9145
9549
|
}
|
|
9146
9550
|
onDrop(event) {
|
|
9147
9551
|
this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
|
|
9148
9552
|
}
|
|
9149
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, deps: [{ token: DragEngineService }, { token: i1.ComponentInstanceRegistryService }, { token: OverlayService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9553
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, deps: [{ token: DragEngineService }, { token: i1.ComponentInstanceRegistryService }, { token: OverlayService }, { token: i0.ChangeDetectorRef }, { token: CanvasDesignStateService, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9150
9554
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: BuilderDialogComponent, isStandalone: false, selector: "app-builder-dialog", inputs: { children: "children", nodeId: "nodeId", dropListId: "dropListId", closeOnBackdrop: "closeOnBackdrop", showCloseButton: "showCloseButton", placement: "placement" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, ngImport: i0, template: `
|
|
9151
9555
|
<div *ngIf="designOpen" class="dialog-stage" [attr.data-placement]="placementValue"
|
|
9152
9556
|
(click)="onStageClick($event)">
|
|
@@ -9179,7 +9583,7 @@ class BuilderDialogComponent {
|
|
|
9179
9583
|
</ng-container>
|
|
9180
9584
|
</div>
|
|
9181
9585
|
</div>
|
|
9182
|
-
`, isInline: true, styles: [".dialog-stage{position:fixed;inset:0;z-index:50;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);place-items:center;padding:24px;box-sizing:border-box;background:#0f172a73}:host(.iox-es) .dialog-stage{background:var(--iox-scrim)}.dialog-stage[data-placement=top]{align-items:start}.dialog-stage[data-placement=bottom]{align-items:end}.dialog-stage[data-placement=start]{justify-items:start}.dialog-stage[data-placement=end]{justify-items:end}:where(.dialog-surface){box-sizing:border-box;max-width:100%;max-height:100%;overflow:auto;contain:layout}.dialog-close{position:absolute;inset-inline-end:10px;top:10px;width:26px;height:26px;border:none;border-radius:999px;background:#0f172a0f;color:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}:host(.iox-es) .dialog-close{border-radius:var(--iox-radius-pill);background:var(--iox-surface-2);color:var(--iox-text)}.dialog-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:90px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.dialog-placeholder i{font-size:18px}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
|
|
9586
|
+
`, isInline: true, styles: [".dialog-stage{position:fixed;inset:0;z-index:50;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);place-items:center;padding:24px;box-sizing:border-box;background:#0f172a73}:host(.iox-es) .dialog-stage{background:var(--iox-scrim)}.dialog-stage[data-placement=top]{align-items:start}.dialog-stage[data-placement=bottom]{align-items:end}.dialog-stage[data-placement=start]{justify-items:start}.dialog-stage[data-placement=end]{justify-items:end}:host(.iox-es) .dialog-stage:is([data-placement=end],[data-placement=start]){padding:0}:host(.iox-es) .dialog-stage:is([data-placement=end],[data-placement=start]) .dialog-surface{height:100%;max-height:100%}:host(.iox-es) .dialog-stage[data-placement=end] .dialog-surface{border-start-end-radius:0;border-end-end-radius:0}:host(.iox-es) .dialog-stage[data-placement=start] .dialog-surface{border-start-start-radius:0;border-end-start-radius:0}:where(.dialog-surface){box-sizing:border-box;max-width:100%;max-height:100%;overflow:auto;contain:layout}.dialog-close{position:absolute;inset-inline-end:10px;top:10px;width:26px;height:26px;border:none;border-radius:999px;background:#0f172a0f;color:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}:host(.iox-es) .dialog-close{border-radius:var(--iox-radius-pill);background:var(--iox-surface-2);color:var(--iox-text)}.dialog-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:90px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.dialog-placeholder i{font-size:18px}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
|
|
9183
9587
|
}
|
|
9184
9588
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, decorators: [{
|
|
9185
9589
|
type: Component,
|
|
@@ -9215,8 +9619,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
9215
9619
|
</ng-container>
|
|
9216
9620
|
</div>
|
|
9217
9621
|
</div>
|
|
9218
|
-
`, standalone: false, styles: [".dialog-stage{position:fixed;inset:0;z-index:50;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);place-items:center;padding:24px;box-sizing:border-box;background:#0f172a73}:host(.iox-es) .dialog-stage{background:var(--iox-scrim)}.dialog-stage[data-placement=top]{align-items:start}.dialog-stage[data-placement=bottom]{align-items:end}.dialog-stage[data-placement=start]{justify-items:start}.dialog-stage[data-placement=end]{justify-items:end}:where(.dialog-surface){box-sizing:border-box;max-width:100%;max-height:100%;overflow:auto;contain:layout}.dialog-close{position:absolute;inset-inline-end:10px;top:10px;width:26px;height:26px;border:none;border-radius:999px;background:#0f172a0f;color:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}:host(.iox-es) .dialog-close{border-radius:var(--iox-radius-pill);background:var(--iox-surface-2);color:var(--iox-text)}.dialog-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:90px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.dialog-placeholder i{font-size:18px}\n"] }]
|
|
9219
|
-
}], ctorParameters: () => [{ type: DragEngineService }, { type: i1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: i0.ChangeDetectorRef }
|
|
9622
|
+
`, standalone: false, styles: [".dialog-stage{position:fixed;inset:0;z-index:50;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);place-items:center;padding:24px;box-sizing:border-box;background:#0f172a73}:host(.iox-es) .dialog-stage{background:var(--iox-scrim)}.dialog-stage[data-placement=top]{align-items:start}.dialog-stage[data-placement=bottom]{align-items:end}.dialog-stage[data-placement=start]{justify-items:start}.dialog-stage[data-placement=end]{justify-items:end}:host(.iox-es) .dialog-stage:is([data-placement=end],[data-placement=start]){padding:0}:host(.iox-es) .dialog-stage:is([data-placement=end],[data-placement=start]) .dialog-surface{height:100%;max-height:100%}:host(.iox-es) .dialog-stage[data-placement=end] .dialog-surface{border-start-end-radius:0;border-end-end-radius:0}:host(.iox-es) .dialog-stage[data-placement=start] .dialog-surface{border-start-start-radius:0;border-end-start-radius:0}:where(.dialog-surface){box-sizing:border-box;max-width:100%;max-height:100%;overflow:auto;contain:layout}.dialog-close{position:absolute;inset-inline-end:10px;top:10px;width:26px;height:26px;border:none;border-radius:999px;background:#0f172a0f;color:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}:host(.iox-es) .dialog-close{border-radius:var(--iox-radius-pill);background:var(--iox-surface-2);color:var(--iox-text)}.dialog-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:90px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.dialog-placeholder i{font-size:18px}\n"] }]
|
|
9623
|
+
}], ctorParameters: () => [{ type: DragEngineService }, { type: i1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: i0.ChangeDetectorRef }, { type: CanvasDesignStateService, decorators: [{
|
|
9624
|
+
type: Optional
|
|
9625
|
+
}] }], propDecorators: { children: [{
|
|
9220
9626
|
type: Input
|
|
9221
9627
|
}], nodeId: [{
|
|
9222
9628
|
type: Input
|
|
@@ -10334,6 +10740,7 @@ class IoxBuilderModule {
|
|
|
10334
10740
|
InteractionEngineService,
|
|
10335
10741
|
ViewportService,
|
|
10336
10742
|
FormStateService,
|
|
10743
|
+
CanvasDesignStateService,
|
|
10337
10744
|
StyleRegistryService,
|
|
10338
10745
|
PanelEventService,
|
|
10339
10746
|
PresetRegistryService,
|
|
@@ -10391,6 +10798,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
10391
10798
|
InteractionEngineService,
|
|
10392
10799
|
ViewportService,
|
|
10393
10800
|
FormStateService,
|
|
10801
|
+
CanvasDesignStateService,
|
|
10394
10802
|
StyleRegistryService,
|
|
10395
10803
|
PanelEventService,
|
|
10396
10804
|
PresetRegistryService,
|
|
@@ -10638,6 +11046,62 @@ function buildPalettePreset(def, createConfig) {
|
|
|
10638
11046
|
return node;
|
|
10639
11047
|
}
|
|
10640
11048
|
|
|
11049
|
+
/**
|
|
11050
|
+
* CONTENT BLOCKS (complex-components board 8, composite T23 / T25) — Card, Badge, Chip. Not component
|
|
11051
|
+
* types: each is a composite-core recipe of ordinary nodes (the Card's parts carry roles), stamped here
|
|
11052
|
+
* with the builder's own node factory — the same recipe generation-core's composer stamps with its own.
|
|
11053
|
+
*/
|
|
11054
|
+
var CompositePresetId;
|
|
11055
|
+
(function (CompositePresetId) {
|
|
11056
|
+
CompositePresetId["Card"] = "card";
|
|
11057
|
+
CompositePresetId["Badge"] = "badge";
|
|
11058
|
+
CompositePresetId["Chip"] = "chip";
|
|
11059
|
+
})(CompositePresetId || (CompositePresetId = {}));
|
|
11060
|
+
const COMPOSITE_PRESETS = Object.freeze([
|
|
11061
|
+
{
|
|
11062
|
+
id: CompositePresetId.Card, name: 'Card', icon: 'ph-thin ph-cards',
|
|
11063
|
+
description: 'Image, title, text and buttons — every part its own element. Layout, parts and link in its panel.',
|
|
11064
|
+
build: create => buildCardRecipe(create),
|
|
11065
|
+
},
|
|
11066
|
+
{
|
|
11067
|
+
id: CompositePresetId.Badge, name: 'Badge', icon: 'ph-thin ph-seal',
|
|
11068
|
+
description: 'A small pill — an icon and a word in one of four tones.',
|
|
11069
|
+
build: create => buildBadge(create),
|
|
11070
|
+
},
|
|
11071
|
+
{
|
|
11072
|
+
id: CompositePresetId.Chip, name: 'Chip', icon: 'ph-thin ph-tag',
|
|
11073
|
+
description: 'An outlined pill — an icon and a word.',
|
|
11074
|
+
build: create => buildChip(create),
|
|
11075
|
+
},
|
|
11076
|
+
]);
|
|
11077
|
+
/**
|
|
11078
|
+
* The builder's node factory for composite-core recipes: a NEW node as the palette creates one
|
|
11079
|
+
* (`createConfig`, already wearing its type's default style), the recipe's props written into its traits,
|
|
11080
|
+
* then moved onto the asked element style (`switchElementStyle`, so nothing of the default style lingers).
|
|
11081
|
+
*/
|
|
11082
|
+
function builderCompositeFactory(createConfig, baselineStyles) {
|
|
11083
|
+
return spec => {
|
|
11084
|
+
const node = createConfig(spec.type);
|
|
11085
|
+
if (!node)
|
|
11086
|
+
throw new Error(`"${spec.type}" is not a registered component type`);
|
|
11087
|
+
for (const [name, value] of Object.entries(spec.props ?? {})) {
|
|
11088
|
+
const trait = node.traits?.find(t => t.name === name);
|
|
11089
|
+
if (trait)
|
|
11090
|
+
trait.default = value;
|
|
11091
|
+
}
|
|
11092
|
+
if (spec.elementStyle && spec.elementStyle !== node.elementStyle) {
|
|
11093
|
+
switchElementStyle(node, node.elementStyle, spec.elementStyle, baselineStyles(spec.type));
|
|
11094
|
+
}
|
|
11095
|
+
if (node.children)
|
|
11096
|
+
node.children = [];
|
|
11097
|
+
return node;
|
|
11098
|
+
};
|
|
11099
|
+
}
|
|
11100
|
+
/** A fresh content block for the palette (click or drag). */
|
|
11101
|
+
function buildCompositePreset(def, createConfig, baselineStyles) {
|
|
11102
|
+
return def.build(builderCompositeFactory(createConfig, baselineStyles));
|
|
11103
|
+
}
|
|
11104
|
+
|
|
10641
11105
|
function text$1(content, styles, createConfig) {
|
|
10642
11106
|
const node = textNode(content, createConfig);
|
|
10643
11107
|
if (node)
|
|
@@ -11039,6 +11503,8 @@ class DialogComponentConfig extends ComponentConfig {
|
|
|
11039
11503
|
closeOnBackdrop: 'checkbox',
|
|
11040
11504
|
showCloseButton: 'checkbox',
|
|
11041
11505
|
consentRole: 'checkbox',
|
|
11506
|
+
closeAbove: 'select',
|
|
11507
|
+
ariaLabel: 'text',
|
|
11042
11508
|
};
|
|
11043
11509
|
this.traits = [
|
|
11044
11510
|
// A5: "Centre modal · bottom bar · side sheet — placement is a style choice."
|
|
@@ -11048,6 +11514,8 @@ class DialogComponentConfig extends ComponentConfig {
|
|
|
11048
11514
|
// C3: the live runtime opens THIS dialog when consent is owed (needsPrompt), and a
|
|
11049
11515
|
// close without a decision records a "no". The canvas never auto-opens anything.
|
|
11050
11516
|
new TraitConfig('consentRole', 'Consent banner role', TraitInputType.Checkbox, undefined, false),
|
|
11517
|
+
...buildDialogCloseAboveTraits(),
|
|
11518
|
+
...buildAccessibleNameTraits(),
|
|
11051
11519
|
];
|
|
11052
11520
|
this.styleTraits = buildFullStyleTraits();
|
|
11053
11521
|
this.applyStyleDefaults({
|
|
@@ -11081,10 +11549,8 @@ class BuilderButtonComponentConfig extends ComponentConfig {
|
|
|
11081
11549
|
}
|
|
11082
11550
|
}
|
|
11083
11551
|
|
|
11084
|
-
const
|
|
11085
|
-
|
|
11086
|
-
{ label: 'Website Page', value: LinkType.Page },
|
|
11087
|
-
];
|
|
11552
|
+
const BUTTON_TARGETS = [{ label: '_self', value: '_self' }, { label: '_blank', value: '_blank' }];
|
|
11553
|
+
const LINKS_AS_ANCHOR = { trait: 'tag', values: 'a' };
|
|
11088
11554
|
class ButtonBlockComponentConfig extends ComponentConfig {
|
|
11089
11555
|
constructor() {
|
|
11090
11556
|
super('ButtonBlock', 'app-builder-button-block', 'ph-thin ph-cursor-click', 'Basic');
|
|
@@ -11098,6 +11564,7 @@ class ButtonBlockComponentConfig extends ComponentConfig {
|
|
|
11098
11564
|
pageRoute: 'text',
|
|
11099
11565
|
target: 'select',
|
|
11100
11566
|
type: 'select',
|
|
11567
|
+
ariaLabel: 'text',
|
|
11101
11568
|
};
|
|
11102
11569
|
// A link button points at a URL or at a website PAGE by route — the Link's two link types
|
|
11103
11570
|
// (W1.1): a page route is shown as the route here and resolved base-relative by the engine.
|
|
@@ -11105,11 +11572,9 @@ class ButtonBlockComponentConfig extends ComponentConfig {
|
|
|
11105
11572
|
// trait is hidden is hidden too, so a <button> shows neither.
|
|
11106
11573
|
this.traits = [
|
|
11107
11574
|
new TraitConfig('tag', 'Tag', TraitInputType.Select, ['button', 'a'], 'button'),
|
|
11108
|
-
|
|
11109
|
-
new TraitConfig('href', 'URL', TraitInputType.Text, undefined, '', false, { trait: 'linkType', values: LinkType.External }),
|
|
11110
|
-
new TraitConfig('pageRoute', 'Page Route', TraitInputType.Text, undefined, '', false, { trait: 'linkType', values: LinkType.Page }),
|
|
11111
|
-
new TraitConfig('target', 'Target', TraitInputType.Select, ['_self', '_blank'], '_self', false, { trait: 'tag', values: 'a' }),
|
|
11575
|
+
...linkTraits({ urlTrait: 'href', urlDefault: '', showWhen: LINKS_AS_ANCHOR, targets: BUTTON_TARGETS }),
|
|
11112
11576
|
new TraitConfig('type', 'Type', TraitInputType.Select, ['button', 'submit', 'reset'], 'button', false, { trait: 'tag', values: 'button' }),
|
|
11577
|
+
...buildAccessibleNameTraits(),
|
|
11113
11578
|
];
|
|
11114
11579
|
this.styleTraits = buildFullStyleTraits();
|
|
11115
11580
|
this.applyStyleDefaults({
|
|
@@ -11497,8 +11962,11 @@ class BuilderHeadingComponentConfig extends ComponentConfig {
|
|
|
11497
11962
|
this.traits = [
|
|
11498
11963
|
new TraitConfig('content', 'Content', TraitInputType.Text, undefined, 'Heading'),
|
|
11499
11964
|
new TraitConfig('level', 'Level', TraitInputType.Select, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], 'h2'),
|
|
11965
|
+
// A heading links only when asked: Card v2's "Link the whole card" makes the card's TITLE its
|
|
11966
|
+
// one link (the stretched link). Edited from the card's Link section, never a second control.
|
|
11967
|
+
...linkTraits({ allowNone: true, urlDefault: '' }),
|
|
11500
11968
|
];
|
|
11501
|
-
this.styleTraits = buildFullStyleTraits();
|
|
11969
|
+
this.styleTraits = withMaxLines(buildFullStyleTraits());
|
|
11502
11970
|
this.applyStyleDefaults({
|
|
11503
11971
|
fontSize: '32px',
|
|
11504
11972
|
fontWeight: '700',
|
|
@@ -11558,18 +12026,12 @@ class BuilderLinkComponentConfig extends ComponentConfig {
|
|
|
11558
12026
|
url: 'text',
|
|
11559
12027
|
pageRoute: 'text',
|
|
11560
12028
|
target: 'select',
|
|
12029
|
+
ariaLabel: 'text',
|
|
11561
12030
|
};
|
|
11562
12031
|
this.traits = [
|
|
11563
12032
|
new TraitConfig('label', 'Label', TraitInputType.Text, undefined, 'Click here'),
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
new TraitConfig('pageRoute', 'Page Route', TraitInputType.Text, undefined, '', false, { trait: 'linkType', values: 'page' }),
|
|
11567
|
-
new TraitConfig('target', 'Target', TraitInputType.Select, [
|
|
11568
|
-
{ label: 'Same tab', value: '_self' },
|
|
11569
|
-
{ label: 'New tab', value: '_blank' },
|
|
11570
|
-
{ label: 'Parent frame', value: '_parent' },
|
|
11571
|
-
{ label: 'Full window', value: '_top' },
|
|
11572
|
-
], '_self'),
|
|
12033
|
+
...linkTraits(),
|
|
12034
|
+
...buildAccessibleNameTraits(),
|
|
11573
12035
|
];
|
|
11574
12036
|
this.styleTraits = buildFullStyleTraits();
|
|
11575
12037
|
}
|
|
@@ -11584,18 +12046,12 @@ class BuilderLinkedContainerConfig extends ComponentConfig {
|
|
|
11584
12046
|
url: 'text',
|
|
11585
12047
|
pageRoute: 'text',
|
|
11586
12048
|
target: 'select',
|
|
12049
|
+
ariaLabel: 'text',
|
|
11587
12050
|
};
|
|
11588
12051
|
this.traits = [
|
|
11589
|
-
|
|
11590
|
-
new TraitConfig('url', 'URL', TraitInputType.Text, undefined, 'https://', false, { trait: 'linkType', values: 'external' }),
|
|
11591
|
-
new TraitConfig('pageRoute', 'Page Route', TraitInputType.Text, undefined, '', false, { trait: 'linkType', values: 'page' }),
|
|
11592
|
-
new TraitConfig('target', 'Target', TraitInputType.Select, [
|
|
11593
|
-
{ label: 'Same tab', value: '_self' },
|
|
11594
|
-
{ label: 'New tab', value: '_blank' },
|
|
11595
|
-
{ label: 'Parent frame', value: '_parent' },
|
|
11596
|
-
{ label: 'Full window', value: '_top' },
|
|
11597
|
-
], '_self'),
|
|
12052
|
+
...linkTraits(),
|
|
11598
12053
|
buildColorSchemeTrait(),
|
|
12054
|
+
...buildAccessibleNameTraits(),
|
|
11599
12055
|
];
|
|
11600
12056
|
this.styleTraits = buildFullStyleTraits();
|
|
11601
12057
|
this.applyStyleDefaults({
|
|
@@ -11633,7 +12089,7 @@ class TextBlockComponentConfig extends ComponentConfig {
|
|
|
11633
12089
|
new TraitConfig('content', 'Content', TraitInputType.Text, undefined, 'Enter text here'),
|
|
11634
12090
|
new TraitConfig('tag', 'Tag', TraitInputType.Select, ['p', 'span'], 'p'),
|
|
11635
12091
|
];
|
|
11636
|
-
this.styleTraits = buildFullStyleTraits();
|
|
12092
|
+
this.styleTraits = withMaxLines(buildFullStyleTraits());
|
|
11637
12093
|
this.applyStyleDefaults({
|
|
11638
12094
|
display: 'block',
|
|
11639
12095
|
height: 'auto',
|
|
@@ -11906,5 +12362,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
|
|
|
11906
12362
|
* Generated bundle index. Do not edit.
|
|
11907
12363
|
*/
|
|
11908
12364
|
|
|
11909
|
-
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, 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, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, 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, 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, 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, buildConsentPreset, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, canFluidise, canvasEmptyImageLabel, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropTargetLabel, escapesRoot, essentialTraitsFor, findNodeLocation, findParentOf, flattenSchemaFields, fluidiseProps, generateNodeId, insertRefusal, insertRefusalLabel, isHiddenAt, isSpecimen, markSpecimen, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, reorderProps, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, rootEscapeBand, rootZone, setStyleTrait, setTrait, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, textNode, visibilityProps, wrapNode };
|
|
12365
|
+
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, 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, 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, buildCompositePreset, buildConsentPreset, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, builderCompositeFactory, canFluidise, canvasEmptyImageLabel, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropTargetLabel, escapesRoot, essentialTraitsFor, findNodeLocation, findParentOf, flattenSchemaFields, fluidiseProps, generateNodeId, insertRefusal, insertRefusalLabel, isHiddenAt, isHiddenAtRest, isSpecimen, markSpecimen, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, nodeStyleMap, pathToNode, refreshSubtree, removalRefusal, reorderProps, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, rootEscapeBand, rootZone, setStyleTrait, setTrait, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, syncNodeInputs, textNode, upsertNodeStyles, visibilityProps, walkNodes, wrapNode };
|
|
11910
12366
|
//# sourceMappingURL=vectoriox-iox-builder.mjs.map
|