@vectoriox/iox-builder 11.14.0 → 11.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,24 +9,24 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
9
9
  import { AccordionModule } from 'primeng/accordion';
10
10
  import * as i19 from 'primeng/button';
11
11
  import { ButtonModule } from 'primeng/button';
12
- import * as i2$1 from 'primeng/popover';
12
+ import * as i2$2 from 'primeng/popover';
13
13
  import { PopoverModule } from 'primeng/popover';
14
14
  import { InputTextModule } from 'primeng/inputtext';
15
15
  import { SelectModule } from 'primeng/select';
16
16
  import { TooltipModule } from 'primeng/tooltip';
17
17
  import * as i20 from 'primeng/dialog';
18
18
  import { DialogModule } from 'primeng/dialog';
19
- import * as i1$1 from '@vectoriox/iox-ui';
20
- import { nodeCssId, needsGroupName, resolveBoundValue, STRUCTURAL_STATES as STRUCTURAL_STATES$1, VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, TEXT_DIRECTION_KEY, TEXT_ALIGN_KEY, TextAlign, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, LinkType, MAX_LINES_KEY, A11yTrait, BREAKPOINTS, Breakpoint, withSchemeInk, stripSuppressedProps, DeclarationContext, planPartRule, gateHoverRule, planResponsiveRules, resolveStyleProps, planStateRule, splitOuterProps, OUTER_STYLE_PROPS, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, isLastEmittedStateKey, wrapInBreakpointQuery, BreakpointQueryKind, NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, applyElementStyle, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, ContextStore, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, ComponentInstanceRegistryService, entranceStartsHidden, isActionEnabled, splitPressFeedback, isHoverPointer, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, motionAllowed, settleInteraction, settleModeFor, playInteraction, resolveInteractionEasing, entranceDelay, repeatedRowIndex, entrancePreStateStyle, buildInteractionKeyframes, cascadeChain, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, nodeTraitValue, syncNodeA11yAttributes, nodeA11yAttributes, safeSetInput, colorSchemeOf, withStateTransitions, composeVirtualTraits, stateNamesIn, partClassName, switchElementStyle, releaseElementStyle, countOverrides, setOverride, breakpointDef, breakpointForWidth, FormRenderState, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, hidingHeadersIn, HeaderRuntime, isScrolled, SCROLLED_ATTRIBUTE, syncSubmitButtons, IS_PREVIEW, buttonBlockHref, bootSlider, IoxPageModule, PALETTE_PRESETS, elementStyleById, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
19
+ import * as i2$1 from '@vectoriox/iox-ui';
20
+ import { nodeCssId, needsGroupName, resolveBoundValue, STRUCTURAL_STATES as STRUCTURAL_STATES$1, VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, TEXT_DIRECTION_KEY, TEXT_ALIGN_KEY, TextAlign, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, LinkType, MAX_LINES_KEY, A11yTrait, BREAKPOINTS, Breakpoint, withSchemeInk, stripSuppressedProps, DeclarationContext, planPartRule, gateHoverRule, planResponsiveRules, resolveStyleProps, planStateRule, splitOuterProps, OUTER_STYLE_PROPS, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, isLastEmittedStateKey, wrapInBreakpointQuery, BreakpointQueryKind, NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, applyElementStyle, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, ContextStore, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, ComponentInstanceRegistryService, entranceStartsHidden, isActionEnabled, splitPressFeedback, isHoverPointer, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, motionAllowed, settleInteraction, settleModeFor, playInteraction, resolveInteractionEasing, entranceDelay, repeatedRowIndex, entrancePreStateStyle, buildInteractionKeyframes, showToast, cascadeChain, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, nodeTraitValue, syncNodeA11yAttributes, nodeA11yAttributes, safeSetInput, colorSchemeOf, withStateTransitions, composeVirtualTraits, stateNamesIn, partClassName, switchElementStyle, releaseElementStyle, countOverrides, setOverride, breakpointDef, breakpointForWidth, FormRenderState, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, attachTooltips, showToastDesign, TOOLTIP_ATTRIBUTE, hidingHeadersIn, HeaderRuntime, isScrolled, SCROLLED_ATTRIBUTE, syncSubmitButtons, IS_PREVIEW, buttonBlockHref, layerFrame, rectInLayer, placeFloating, FloatingAlign, popoverSide, bootSlider, IoxPageModule, PALETTE_PRESETS, elementStyleById, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
21
21
  export { ComponentInstanceRegistryService, VIRTUAL_TRAIT_KEYS, composeVirtualTraits } from '@vectoriox/iox-ui';
22
22
  import { StructureEdit, isAccordionPart, isDisclosurePart, isSliderPart, isTabsPart, isSegmentedPart, structureRefusal, StructureRefusal, SegmentedRole, SEGMENTED_MAX_OPTIONS, AccordionRole, SEGMENTED_MIN_OPTIONS, isExclusiveItemPart, TabsRole, ITEM_OPEN_STATE, readStyle, isSegmentedRoot, segmentedDefaultOn, segmentedOptions, isTabsRoot, isTabsFromContent, tabsTriggers, isAccordionRoot, isAccordionFromContent, accordionItems, initialAccordionOpen, accordionBehaviour, exclusiveItemLabel, exclusiveRootOf, tabTriggerOf, tabLabel, removeTabPair, tabPanelOf, tabsListOf, tabsPanelsOf, nextTabKey, CompositeBehaviourTrait, ACCORDION_DEFAULT_ONS, TabsBehaviourTrait, TABS_ACTIVATE_ONS, TABS_ORIENTATIONS, onCompositeItemState, isCompositeItemOn, runCompositeItemAction, shouldScrollToItem, rowIndexOf as rowIndexOf$1, slideIndexForRow, DisclosureRole, isExclusiveTrigger, compositeScopeId, compositeRowKeys, isExclusiveRoot, ExclusiveRuntime, exclusiveDefaultOf, exclusiveLabelOf, exclusiveBehaviourOf, exclusiveKindOf, compositeKindOfRole, AccordionRuntime, isToggleKind, closestAccordionItem, isAnyCompositeRoot, SceneRole, isExclusiveList, buildCardRecipe, buildBadge, buildChip, buildAccordionRecipe, buildDisclosureRecipe, buildTabsRecipe, buildSegmentedRecipe, buildLightboxRecipe, NO_ELEMENT_STYLE, CompositeKind, SliderRole, accordionItemLabel, segmentedOptionLabel, isExclusivePanels, isLockedPart, accordionRootOf, accordionItemOf, addAccordionItem, addTabPair, segmentedAddRefusal, addSegmentedOption, isCompositeItem, accordionHeadingLevel, isCompositePanel, isCompositeTrigger, splitSliderChildren, hasSliderParts, SLIDE_CAPTION_TRAIT, SLIDER_SCROLL_PLAYS_TRAIT, SLIDER_PLACEMENT_TRAIT, SliderPlacement, SliderFlag } from '@vectoriox/iox-ui/composite-core';
23
- import { formContentInHost, isInteractiveHost, findFormContentInHosts, FORM_CHANNEL_ROLE, channelHref, channelOpensNewTab, FORM_ACTIONS_ROLE, SUBMIT_BUTTON_NODE_TYPE } from '@vectoriox/iox-ui/forms-core';
23
+ import { formContentInHost, isInteractiveHost, findFormContentInHosts, FORM_CHANNEL_ROLE, channelHref, channelOpensNewTab, FORM_ACTIONS_ROLE, SUBMIT_BUTTON_NODE_TYPE, FORM_AFTER_SEND_TRAIT, FormAfterSend } from '@vectoriox/iox-ui/forms-core';
24
24
  import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
25
25
  import interact from 'interactjs';
26
26
  import { HeaderTrait, HeaderBehaviour, HeaderEdge } from '@vectoriox/iox-ui/design-core';
27
27
  import { map, catchError } from 'rxjs/operators';
28
28
  import * as i1 from '@angular/common/http';
29
- import { itemActionOf, BEHAVIOUR_ACTION_TYPES, SERVER_ELEMENT_ACTION_TYPES, CONSENT_ACTION_TYPES, opensAtClickedItem, openedDialogId, DIALOG_CLOSING_ATTRIBUTE, cancelExit, runExitMotion } from '@vectoriox/iox-ui/actions-core';
29
+ import { POPOVER_ACTION_TYPE_SET, TOAST_ACTION_TYPES, itemActionOf, BEHAVIOUR_ACTION_TYPES, SERVER_ELEMENT_ACTION_TYPES, CONSENT_ACTION_TYPES, opensAtClickedItem, openedDialogId, DIALOG_CLOSING_ATTRIBUTE, cancelExit, runExitMotion, popoverOpenersSelector, popoverDomId } from '@vectoriox/iox-ui/actions-core';
30
30
  import { NavRole, navShapeBreakpoint, entranceInteraction, entranceAction, sectionEntrances, EntrancePart } from '@vectoriox/iox-ui/generation-core';
31
31
  import { ScrollDriver, syncSceneAttributes, buildSceneRecipe } from '@vectoriox/iox-ui/scene-core';
32
32
  import Lenis from 'lenis';
@@ -1311,6 +1311,8 @@ const INVERSE_ACTION = {
1311
1311
  clearContext: 'setContext',
1312
1312
  openDialog: 'closeDialog',
1313
1313
  closeDialog: 'openDialog',
1314
+ openPopover: 'closePopover',
1315
+ closePopover: 'openPopover',
1314
1316
  openItem: 'closeItem',
1315
1317
  closeItem: 'openItem',
1316
1318
  };
@@ -1342,6 +1344,10 @@ const ACTION_TYPE_OPTIONS = [
1342
1344
  { value: 'openDialog', label: 'Open dialog' },
1343
1345
  { value: 'closeDialog', label: 'Close dialog' },
1344
1346
  { value: 'toggleDialog', label: 'Toggle dialog' },
1347
+ { value: 'openPopover', label: 'Open popover' },
1348
+ { value: 'closePopover', label: 'Close popover' },
1349
+ { value: 'togglePopover', label: 'Toggle popover' },
1350
+ { value: 'showToast', label: 'Show a toast' },
1345
1351
  { value: 'setContext', label: 'Set context' },
1346
1352
  { value: 'clearContext', label: 'Clear context' },
1347
1353
  ];
@@ -2006,6 +2012,25 @@ function buildAccessibleNameTraits() {
2006
2012
  new TraitConfig(A11yTrait.AccessibleName, 'Accessible name', TraitInputType.Text, undefined, ''),
2007
2013
  ];
2008
2014
  }
2015
+ /** The sides a tooltip may take (premium M5) — Above unless the author picks another; flipped at the screen's edge. */
2016
+ const TOOLTIP_PLACEMENT_OPTIONS = [
2017
+ { label: 'Above', value: 'above' },
2018
+ { label: 'Below', value: 'below' },
2019
+ { label: 'Start', value: 'start' },
2020
+ { label: 'End', value: 'end' },
2021
+ ];
2022
+ /**
2023
+ * "Tooltip" (premium M5, N4): words for a control that has none of its own — an icon button, a gallery arrow. A TRAIT on
2024
+ * the control, not a node: a tooltip holds only words (APG: never interactive content), travels with the control
2025
+ * through copy, symbols and presets, and the shared `nodeA11yAttributes` renders it in every renderer. Its look is the
2026
+ * site's (Design ▸ Element styles ▸ Tooltip). The Bindings panel draws these in its own Tooltip section.
2027
+ */
2028
+ function buildTooltipTraits() {
2029
+ return [
2030
+ new TraitConfig(A11yTrait.Tooltip, 'Tooltip', TraitInputType.Text, undefined, ''),
2031
+ new TraitConfig(A11yTrait.TooltipPlacement, 'Tooltip side', TraitInputType.Select, TOOLTIP_PLACEMENT_OPTIONS, 'above'),
2032
+ ];
2033
+ }
2009
2034
  /** The breakpoints a Dialog may close above, widest first, plus "Never" (the default, empty). */
2010
2035
  const DIALOG_CLOSE_ABOVE_OPTIONS = [
2011
2036
  { label: 'Never', value: '' },
@@ -4520,6 +4545,18 @@ class InteractionEngineService {
4520
4545
  this.executeDialogAction(action, selfEl);
4521
4546
  continue;
4522
4547
  }
4548
+ // Popover actions (premium M5): the canvas RUNS them too, through the twin's registry — beside the element
4549
+ // whose click ran them (the opener the live runtime anchors to).
4550
+ if (POPOVER_ACTION_TYPE_SET.has(action.type)) {
4551
+ this.executePopoverAction(action, selfEl);
4552
+ continue;
4553
+ }
4554
+ // A toast (premium M5): RUN inside the page's frame — the layer of the token scope the element sits in (the
4555
+ // page, or the globals' overlay), so it wears the look and zooms with the canvas.
4556
+ if (TOAST_ACTION_TYPES.has(action.type)) {
4557
+ showCanvasToast(action.params?.['text'], action.params?.['edge'], selfEl ?? null);
4558
+ continue;
4559
+ }
4523
4560
  // Item actions (CC2.1 — composite T14): the canvas RUNS them on the target item's own runtime — the
4524
4561
  // one the live site runs — and brings an item that opened out of view into it.
4525
4562
  const itemAction = itemActionOf(action.type);
@@ -4659,6 +4696,22 @@ class InteractionEngineService {
4659
4696
  break;
4660
4697
  }
4661
4698
  }
4699
+ executePopoverAction(action, selfEl) {
4700
+ const popover = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
4701
+ if (!popover)
4702
+ return;
4703
+ switch (action.type) {
4704
+ case 'openPopover':
4705
+ popover.open?.(selfEl ?? null);
4706
+ break;
4707
+ case 'closePopover':
4708
+ popover.close?.();
4709
+ break;
4710
+ case 'togglePopover':
4711
+ popover.toggle?.(selfEl ?? null);
4712
+ break;
4713
+ }
4714
+ }
4662
4715
  /** The slider inside the opened dialog (its canvas twin renders on the next passes) → the row's slide. */
4663
4716
  slideDialogToRow(dialogId, row) {
4664
4717
  let tries = 0;
@@ -4828,6 +4881,17 @@ class InteractionEngineService {
4828
4881
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, decorators: [{
4829
4882
  type: Injectable
4830
4883
  }], ctorParameters: () => [{ type: OverlayService }] });
4884
+ /** The canvas's token scopes: the page frame and the globals' overlay (each declares the org's tokens). */
4885
+ const CANVAS_TOKEN_SCOPE = '.iox-token-scope';
4886
+ /** The floating layer's home on the canvas for an element: the token scope (the zoomed frame) it sits in. */
4887
+ function canvasFloatingContainer(from) {
4888
+ return from?.closest?.(CANVAS_TOKEN_SCOPE) ?? from?.ownerDocument?.querySelector(CANVAS_TOKEN_SCOPE) ?? null;
4889
+ }
4890
+ /** Raise a toast on the canvas (a showToast action the canvas runs): inside the frame, never over the builder. */
4891
+ function showCanvasToast(text, edge, from) {
4892
+ const doc = from?.ownerDocument ?? (typeof document !== 'undefined' ? document : null);
4893
+ showToast(doc, typeof text === 'string' ? text : '', { edge: typeof edge === 'string' ? edge : null, from, topLayer: false, container: canvasFloatingContainer(from) });
4894
+ }
4831
4895
 
4832
4896
  // Virtual trait composition now lives in @vectoriox/iox-ui so the SSR engine (which does NOT depend
4833
4897
  // on iox-builder) can use the SAME logic — otherwise the engine emitted raw virtual keys
@@ -4841,7 +4905,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
4841
4905
  * menu's drawer, a consent banner) by its type; an Accordion's PANEL by its role (CC2 — closed until its
4842
4906
  * item opens); a Disclosure's panel and its "Read less" word (CC2.1 — shown only while it is open).
4843
4907
  */
4844
- const HIDDEN_AT_REST_TYPES = ['Dialog'];
4908
+ const HIDDEN_AT_REST_TYPES = ['Dialog', 'Popover'];
4845
4909
  const HIDDEN_AT_REST_ROLES = [AccordionRole.Panel, DisclosureRole.Panel, DisclosureRole.Less];
4846
4910
  function isHiddenAtRest(node) {
4847
4911
  return (!!node?.type && HIDDEN_AT_REST_TYPES.includes(node.type)) || (!!node?.role && HIDDEN_AT_REST_ROLES.includes(node.role));
@@ -4949,7 +5013,7 @@ var CanvasShowState;
4949
5013
  })(CanvasShowState || (CanvasShowState = {}));
4950
5014
  const EMPTY_SNAPSHOT = () => ({
4951
5015
  revealed: new Set(), selectedTriggers: new Set(), pinned: new Set(), triggeredBySelection: new Set(), shown: new Map(), items: new Map(),
4952
- runtime: new Set(), menu: null,
5016
+ runtime: new Set(), menu: null, tooltips: new Set(), toast: null,
4953
5017
  });
4954
5018
  /**
4955
5019
  * The canvas's DESIGN state (complex-components R2; composite plan §5a's CompositeDesignStateService,
@@ -5069,6 +5133,32 @@ class CanvasDesignStateService {
5069
5133
  isRevealedBySelection(nodeId) {
5070
5134
  return !!nodeId && this._state$.value.revealed.has(nodeId);
5071
5135
  }
5136
+ /** Hold a control's tooltip open on the canvas (true), or let it go. */
5137
+ setTooltipShown(nodeId, on) {
5138
+ if (!nodeId || this._state$.value.tooltips.has(nodeId) === on)
5139
+ return;
5140
+ const tooltips = new Set(this._state$.value.tooltips);
5141
+ if (on)
5142
+ tooltips.add(nodeId);
5143
+ else
5144
+ tooltips.delete(nodeId);
5145
+ this._state$.next({ ...this._state$.value, tooltips });
5146
+ }
5147
+ /** True while the canvas holds the control's tooltip open. */
5148
+ tooltipShown(nodeId) {
5149
+ return !!nodeId && this._state$.value.tooltips.has(nodeId);
5150
+ }
5151
+ /** Hold a toast up on the canvas with these words, or take it down (null). */
5152
+ setToastShown(words) {
5153
+ const next = words === null ? null : String(words);
5154
+ if (this._state$.value.toast === next)
5155
+ return;
5156
+ this._state$.next({ ...this._state$.value, toast: next });
5157
+ }
5158
+ /** The words of the toast the canvas holds up, or null. */
5159
+ get toastShown() {
5160
+ return this._state$.value.toast;
5161
+ }
5072
5162
  /** A new page on the canvas: nothing is revealed or switched open. */
5073
5163
  reset() {
5074
5164
  this._state$.next(EMPTY_SNAPSHOT());
@@ -5458,7 +5548,7 @@ const DIRECTIVE_HOST_TYPES = new Set([
5458
5548
  * closed (the live site's closed `<dialog>` is display: none). A block host was a full-width flex item of its
5459
5549
  * parent — the CC1 drawer inside the header took a wrapped line plus a row gap, 24px the live header never had.
5460
5550
  */
5461
- const BOXLESS_HOST_TYPES = new Set(['Dialog']);
5551
+ const BOXLESS_HOST_TYPES = new Set(['Dialog', 'Popover']);
5462
5552
  /** The inline `display` the canvas gives a node's custom-element host, or null to leave the host alone. */
5463
5553
  function canvasHostDisplay(type) {
5464
5554
  if (BOXLESS_HOST_TYPES.has(type))
@@ -7905,6 +7995,8 @@ const ADD_ITEM_ROLES = new Set([
7905
7995
  const ADD_TAB_KEY = 'builder.chrome.addTab';
7906
7996
  /** A Segmented control says "Add option" (board 10). */
7907
7997
  const ADD_OPTION_KEY = 'builder.chrome.addOption';
7998
+ /** The Popover node type (premium M5) — its eye shares the dialog's. */
7999
+ const POPOVER_TYPE = 'Popover';
7908
8000
  class OverlayComponent {
7909
8001
  constructor(elRef, instanceRegistry, overlayService, viewportService, presetRegistry, symbolRegistry, interactionPresetRegistry, interactionEngine, clipboard, styleRegistry, panelEventService, cdr, dragEngine, labels, designState) {
7910
8002
  this.elRef = elRef;
@@ -8360,7 +8452,12 @@ class OverlayComponent {
8360
8452
  /** Boards A4/A5: a closed dialog has NO canvas presence — this toolbar eye (and the layer
8361
8453
  * tree row) is how an author opens and closes it for design. The Menu drawer has the Menu chip instead. */
8362
8454
  get isDialog() {
8363
- return this.selectEntry?.node?.type === 'Dialog' && this.selectEntry?.node?.id !== this.menuChip?.drawer.id;
8455
+ const type = this.selectEntry?.node?.type;
8456
+ return (type === 'Dialog' || this.isPopover) && this.selectEntry?.node?.id !== this.menuChip?.drawer.id;
8457
+ }
8458
+ /** Premium M5: a Popover is closed on the canvas until something opens it — the same eye opens it for design. */
8459
+ get isPopover() {
8460
+ return this.selectEntry?.node?.type === POPOVER_TYPE;
8364
8461
  }
8365
8462
  refreshMenuChip() {
8366
8463
  const menu = this.designState?.snapshot.menu ?? null;
@@ -8532,13 +8629,13 @@ class OverlayComponent {
8532
8629
  // is a zero-area line — prevents diagonal edge triangle artifacts.
8533
8630
  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)`;
8534
8631
  }
8535
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: OverlayComponent, deps: [{ token: i0.ElementRef }, { token: i1$1.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 }, { token: CanvasDesignStateService, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
8536
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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]=\"selectedRoleIcon\" aria-hidden=\"true\"></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\">{{ toolbarName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n @if (menuChip) {\n <!-- CC1.3: the phone menu's drawer, shown or hidden for design from anywhere in the navigation. -->\n <button class=\"toolbar-btn toolbar-btn--menu\" [class.toolbar-btn--on]=\"menuChipOpen\"\n [title]=\"menuChipTitle\" [attr.aria-label]=\"menuChipTitle\" [attr.aria-pressed]=\"menuChipOpen\"\n (mousedown)=\"onToggleMenuChip($event)\">\n <i [class]=\"menuChipOpen ? 'ph-thin ph-eye-slash' : 'ph-thin ph-eye'\" aria-hidden=\"true\"></i>\n <span class=\"toolbar-btn__label\">{{ menuChipLabel }}</span>\n </button>\n }\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 @if (canAddItem) {\n <button class=\"toolbar-btn toolbar-btn--add-slide\" [title]=\"addItemLabel\" (mousedown)=\"onAddItem($event)\">\n <i class=\"ph-thin ph-plus\" aria-hidden=\"true\"></i>\n <span class=\"toolbar-btn__label\">{{ addItemLabel }}</span>\n </button>\n }\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--add-slide,.select-toolbar .toolbar-btn--menu{width:auto;padding:0 7px;gap:4px;border-radius:3px}.select-toolbar .toolbar-btn--on{background:#ffffff3d}.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 }); }
8632
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: OverlayComponent, deps: [{ token: i0.ElementRef }, { token: i2$1.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 }, { token: CanvasDesignStateService, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
8633
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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]=\"selectedRoleIcon\" aria-hidden=\"true\"></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\">{{ toolbarName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n @if (menuChip) {\n <!-- CC1.3: the phone menu's drawer, shown or hidden for design from anywhere in the navigation. -->\n <button class=\"toolbar-btn toolbar-btn--menu\" [class.toolbar-btn--on]=\"menuChipOpen\"\n [title]=\"menuChipTitle\" [attr.aria-label]=\"menuChipTitle\" [attr.aria-pressed]=\"menuChipOpen\"\n (mousedown)=\"onToggleMenuChip($event)\">\n <i [class]=\"menuChipOpen ? 'ph-thin ph-eye-slash' : 'ph-thin ph-eye'\" aria-hidden=\"true\"></i>\n <span class=\"toolbar-btn__label\">{{ menuChipLabel }}</span>\n </button>\n }\n <button *ngIf=\"isDialog\" class=\"toolbar-btn\"\n [title]=\"isPopover ? (dialogDesignOpen ? 'Close the popover (design)' : 'Open the popover (design)') : (dialogDesignOpen ? 'Close the dialog (design)' : 'Open the dialog (design)')\"\n [attr.aria-label]=\"isPopover ? (dialogDesignOpen ? 'Close the popover for design' : 'Open the popover for design') : (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 @if (canAddItem) {\n <button class=\"toolbar-btn toolbar-btn--add-slide\" [title]=\"addItemLabel\" (mousedown)=\"onAddItem($event)\">\n <i class=\"ph-thin ph-plus\" aria-hidden=\"true\"></i>\n <span class=\"toolbar-btn__label\">{{ addItemLabel }}</span>\n </button>\n }\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--add-slide,.select-toolbar .toolbar-btn--menu{width:auto;padding:0 7px;gap:4px;border-radius:3px}.select-toolbar .toolbar-btn--on{background:#ffffff3d}.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 }); }
8537
8634
  }
8538
8635
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: OverlayComponent, decorators: [{
8539
8636
  type: Component,
8540
- 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]=\"selectedRoleIcon\" aria-hidden=\"true\"></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\">{{ toolbarName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n @if (menuChip) {\n <!-- CC1.3: the phone menu's drawer, shown or hidden for design from anywhere in the navigation. -->\n <button class=\"toolbar-btn toolbar-btn--menu\" [class.toolbar-btn--on]=\"menuChipOpen\"\n [title]=\"menuChipTitle\" [attr.aria-label]=\"menuChipTitle\" [attr.aria-pressed]=\"menuChipOpen\"\n (mousedown)=\"onToggleMenuChip($event)\">\n <i [class]=\"menuChipOpen ? 'ph-thin ph-eye-slash' : 'ph-thin ph-eye'\" aria-hidden=\"true\"></i>\n <span class=\"toolbar-btn__label\">{{ menuChipLabel }}</span>\n </button>\n }\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 @if (canAddItem) {\n <button class=\"toolbar-btn toolbar-btn--add-slide\" [title]=\"addItemLabel\" (mousedown)=\"onAddItem($event)\">\n <i class=\"ph-thin ph-plus\" aria-hidden=\"true\"></i>\n <span class=\"toolbar-btn__label\">{{ addItemLabel }}</span>\n </button>\n }\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--add-slide,.select-toolbar .toolbar-btn--menu{width:auto;padding:0 7px;gap:4px;border-radius:3px}.select-toolbar .toolbar-btn--on{background:#ffffff3d}.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"] }]
8541
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1$1.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: [{
8637
+ 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]=\"selectedRoleIcon\" aria-hidden=\"true\"></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\">{{ toolbarName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n @if (menuChip) {\n <!-- CC1.3: the phone menu's drawer, shown or hidden for design from anywhere in the navigation. -->\n <button class=\"toolbar-btn toolbar-btn--menu\" [class.toolbar-btn--on]=\"menuChipOpen\"\n [title]=\"menuChipTitle\" [attr.aria-label]=\"menuChipTitle\" [attr.aria-pressed]=\"menuChipOpen\"\n (mousedown)=\"onToggleMenuChip($event)\">\n <i [class]=\"menuChipOpen ? 'ph-thin ph-eye-slash' : 'ph-thin ph-eye'\" aria-hidden=\"true\"></i>\n <span class=\"toolbar-btn__label\">{{ menuChipLabel }}</span>\n </button>\n }\n <button *ngIf=\"isDialog\" class=\"toolbar-btn\"\n [title]=\"isPopover ? (dialogDesignOpen ? 'Close the popover (design)' : 'Open the popover (design)') : (dialogDesignOpen ? 'Close the dialog (design)' : 'Open the dialog (design)')\"\n [attr.aria-label]=\"isPopover ? (dialogDesignOpen ? 'Close the popover for design' : 'Open the popover for design') : (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 @if (canAddItem) {\n <button class=\"toolbar-btn toolbar-btn--add-slide\" [title]=\"addItemLabel\" (mousedown)=\"onAddItem($event)\">\n <i class=\"ph-thin ph-plus\" aria-hidden=\"true\"></i>\n <span class=\"toolbar-btn__label\">{{ addItemLabel }}</span>\n </button>\n }\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--add-slide,.select-toolbar .toolbar-btn--menu{width:auto;padding:0 7px;gap:4px;border-radius:3px}.select-toolbar .toolbar-btn--on{background:#ffffff3d}.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"] }]
8638
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i2$1.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: [{
8542
8639
  type: Optional
8543
8640
  }, {
8544
8641
  type: Inject,
@@ -8661,7 +8758,7 @@ class ToolbarComponent {
8661
8758
  this.zoomPopover.hide();
8662
8759
  }
8663
8760
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ToolbarComponent, deps: [{ token: IOX_BUILDER_LABELS }], target: i0.ɵɵFactoryTarget.Component }); }
8664
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: ToolbarComponent, isStandalone: false, selector: "app-toolbar", inputs: { activeMode: "activeMode", activeBreakpoint: "activeBreakpoint", breakpointCounts: "breakpointCounts", auditCount: "auditCount", auditOpen: "auditOpen", activeZoom: "activeZoom", activeScreenWidth: "activeScreenWidth", canUndo: "canUndo", canRedo: "canRedo", isSaving: "isSaving", pageStatus: "pageStatus" }, outputs: { modeChange: "modeChange", breakpointChange: "breakpointChange", auditToggle: "auditToggle", zoomChange: "zoomChange", screenWidthChange: "screenWidthChange", undo: "undo", redo: "redo", save: "save", preview: "preview", publishToggle: "publishToggle" }, viewQueries: [{ propertyName: "modePopover", first: true, predicate: ["modePopover"], descendants: true }, { propertyName: "zoomPopover", first: true, predicate: ["zoomPopover"], descendants: true }, { propertyName: "widthPopover", first: true, predicate: ["widthPopover"], descendants: true }], ngImport: i0, template: "<div class=\"builder-toolbar\">\n <!-- \u2500\u2500 Mode switcher (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful\"\n (click)=\"toggleModePopover($event)\"\n [title]=\"activeModeLabel\">\n <i [class]=\"activeModeIcon\"></i>\n </button>\n\n <p-popover #modePopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Select\"\n (click)=\"selectMode(modes.Select)\">\n <i class=\"ph-thin ph-cursor\"></i>\n <span>{{ t('builder.chrome.select') }}</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Pan\"\n (click)=\"selectMode(modes.Pan)\">\n <i class=\"ph-thin ph-hand\"></i>\n <span>{{ t('builder.chrome.pan') }}</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Style\"\n (click)=\"selectMode(modes.Style)\">\n <i class=\"ph-thin ph-paint-brush-broad\"></i>\n <span>{{ t('builder.chrome.style') }}</span>\n </button>\n </div>\n </p-popover>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Undo / Redo (action) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn\" [title]=\"t('builder.chrome.undo')\" [disabled]=\"!canUndo\" (click)=\"undo.emit()\">\n <i class=\"ph-thin ph-arrow-counter-clockwise\"></i>\n </button>\n <button class=\"tool-btn\" [title]=\"t('builder.chrome.redo')\" [disabled]=\"!canRedo\" (click)=\"redo.emit()\">\n <i class=\"ph-thin ph-arrow-clockwise\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Breakpoint bar \u2014 the EDITING SCOPE, not just a preview size \u2500\u2500 -->\n <div class=\"breakpoint-bar\" role=\"group\" [attr.aria-label]=\"t('builder.chrome.breakpoint')\">\n @for (bp of breakpointOptions; track bp.id) {\n <button type=\"button\"\n class=\"bp-btn\"\n [class.is-active]=\"activeBreakpoint === bp.id\"\n [attr.aria-pressed]=\"activeBreakpoint === bp.id\"\n [title]=\"bp.label + ' \u00B7 ' + bp.range\"\n [attr.aria-label]=\"bp.label\"\n (click)=\"selectBreakpoint(bp.id)\">\n <i [class]=\"bp.icon\" [class.bp-icon--landscape]=\"bp.landscape\" aria-hidden=\"true\"></i>\n @if (breakpointCounts[bp.id]) {\n <span class=\"bp-dot\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n </div>\n\n <!-- The form state switcher used to live here \u2014 the bug that broke the header\n (`.form-state-bar` had no CSS rule at all) and, separately, the wrong home per board 3\n (forms-and-inputs-plan.md Part 2). It now lives on the Form's own panel, driven by the\n shared FormStateService, and the toolbar is back to exactly what it was before forms\n existed. -->\n\n\n <!-- \u2500\u2500 Screen width \u2014 Base only (any width above Laptop) \u2500\u2500 -->\n @if (activeBreakpoint === breakpointIds.Base) {\n <button class=\"tool-btn stateful width-btn\"\n (click)=\"toggleWidthPopover($event)\"\n [title]=\"t('builder.chrome.screenWidth')\">\n <span class=\"width-label\">{{ activeScreenWidth }}</span>\n </button>\n }\n\n <p-popover #widthPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let w of screenWidthOptions\"\n [class.is-active]=\"activeScreenWidth === w.width\"\n (click)=\"selectScreenWidth(w.width)\">\n <span>{{ w.label }}</span>\n </button>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Zoom (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful zoom-btn\"\n (click)=\"toggleZoomPopover($event)\"\n [title]=\"t('builder.chrome.zoom')\">\n <span class=\"zoom-label\">{{ zoomLabel }}</span>\n </button>\n\n <p-popover #zoomPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"zoom-popover\">\n <div class=\"zoom-slider-row\">\n <input class=\"zoom-slider\"\n type=\"range\"\n min=\"20\" max=\"100\" step=\"1\"\n [value]=\"sliderZoom\"\n (input)=\"onSliderChange($event)\">\n <span class=\"zoom-slider-value\">{{ zoomLabel }}</span>\n </div>\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let z of zoomOptions\"\n [class.is-active]=\"z.value === activeZoom\"\n (click)=\"selectZoom(z.value)\">\n <span>{{ z.label }}</span>\n </button>\n </div>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Responsive audit (A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn audit-toggle\"\n [class.is-open]=\"auditOpen\"\n [class.has-findings]=\"auditCount > 0\"\n type=\"button\"\n [title]=\"t('builder.chrome.auditHint')\"\n [attr.aria-label]=\"t('builder.chrome.audit')\"\n [attr.aria-pressed]=\"auditOpen\"\n (click)=\"auditToggle.emit()\">\n <i class=\"ph-thin ph-stethoscope\" aria-hidden=\"true\"></i>\n @if (auditCount) {\n <span class=\"audit-count\">{{ auditCount }}</span>\n }\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Save \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn save-btn\"\n [class.is-saving]=\"isSaving\"\n [disabled]=\"isSaving\"\n [title]=\"t('builder.chrome.save')\"\n (click)=\"save.emit()\">\n <i class=\"ph-thin ph-floppy-disk\"></i>\n </button>\n\n <!-- \u2500\u2500 Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn preview-btn\"\n [title]=\"t('builder.chrome.preview')\"\n (click)=\"preview.emit()\">\n <i class=\"ph-thin ph-eye\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- Host-app toolbar actions (e.g. the Design trial button, flavors F2-D) -->\n <ng-content></ng-content>\n\n <!-- \u2500\u2500 Publish / Unpublish \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn publish-btn\"\n [class.is-published]=\"isPublished\"\n [title]=\"publishLabel\"\n (click)=\"publishToggle.emit()\">\n <i [class]=\"isPublished ? 'ph-thin ph-cloud-slash' : 'ph-thin ph-cloud-arrow-up'\"></i>\n <span class=\"publish-label\">{{ publishLabel }}</span>\n </button>\n</div>\n", styles: [".builder-toolbar{display:flex;align-items:center;gap:.125rem}.divider{width:1px;height:1.25rem;background:var(--p-surface-200);margin:0 .375rem;flex-shrink:0}.tool-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.tool-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color)}.tool-btn:disabled{opacity:.35;cursor:default}.tool-btn.stateful{position:relative}.tool-btn.stateful:after{content:\"\";position:absolute;inset-block-end:3px;width:4px;height:4px;border-radius:50%;background:#cb9090}.audit-toggle{position:relative}.audit-toggle.is-open{background:var(--p-surface-100);color:#a05c5c}.audit-toggle.has-findings{color:#b8702f}.audit-count{position:absolute;inset-block-start:1px;inset-inline-end:1px;min-inline-size:13px;padding:0 3px;border-radius:20px;background:#d9924e;color:#fff;font-size:8.5px;line-height:13px;font-weight:600}.breakpoint-bar{display:flex;align-items:center;gap:1px;padding:2px;border-radius:8px;background:var(--p-surface-100)}.bp-btn{position:relative;display:flex;align-items:center;justify-content:center;width:1.875rem;height:1.75rem;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--p-text-muted-color);font-size:.95rem;cursor:pointer;transition:background .15s ease-out,color .15s ease-out,border-color .15s ease-out}.bp-btn:hover{background:var(--p-surface-0);color:var(--p-text-color)}.bp-btn:focus-visible{outline:2px solid #cb9090;outline-offset:1px}.bp-btn.is-active{background:var(--p-surface-0);border-color:#cb9090;color:#a05c5c}.bp-icon--landscape{transform:rotate(90deg)}.bp-dot{position:absolute;inset-block-start:3px;inset-inline-end:3px;width:5px;height:5px;border-radius:50%;background:#d9924e}.zoom-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.5rem}.zoom-btn .zoom-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.width-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.8rem}.width-btn .width-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.save-btn{background:#cb9090;color:#fff}.save-btn:hover:not(:disabled){background:#b37373;color:#fff}.save-btn.is-saving{opacity:.7}.preview-btn{border:1px solid var(--p-surface-200)}.preview-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color);border-color:var(--p-surface-100)}.publish-btn{width:auto;border-radius:999px;padding:0 .65rem;gap:.35rem;font-size:.75rem;font-weight:500;background:var(--iox-success-fill, #2E7D45);color:#fff;border:none}.publish-btn .publish-label{font-size:.72rem;letter-spacing:.02em}.publish-btn:hover:not(:disabled){background:color-mix(in srgb,var(--iox-success-fill, #2E7D45) 86%,#000);color:#fff}.publish-btn.is-published{background:transparent;color:var(--p-text-muted-color);border:1px solid var(--p-surface-200)}.publish-btn.is-published:hover:not(:disabled){background:var(--iox-danger-bg, #FBEAE8);color:var(--iox-danger-text, #8E211A);border-color:var(--iox-danger-border, #F2CBC6)}::ng-deep .toolbar-popover .p-popover-content{padding:.25rem!important;background:var(--p-surface-0)!important;border:1px solid var(--p-surface-200)!important;border-radius:10px!important;box-shadow:0 4px 16px #0f172a1a!important}.popover-strip{display:flex;flex-direction:column;gap:2px;min-width:120px}.popover-option{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .65rem;border:none;background:transparent;color:var(--p-text-color);cursor:pointer;font-size:.8rem;border-radius:6px;transition:background .12s ease-out,color .12s ease-out;white-space:nowrap}.popover-option i{font-size:1rem;color:var(--p-text-muted-color)}.popover-option:hover{background:var(--p-surface-50)}.popover-option.is-active{background:#cb9090;color:#fff}.popover-option.is-active i{color:#fff}::ng-deep .toolbar-popover .zoom-popover{display:flex;flex-direction:column;gap:4px;padding:.15rem 0}::ng-deep .toolbar-popover .zoom-slider-row{display:flex;align-items:center;gap:.5rem;padding:.3rem .65rem .1rem}::ng-deep .toolbar-popover .zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:3px;border-radius:999px;background:var(--p-surface-200);outline:none;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#cb9090;cursor:pointer;transition:transform .12s ease-out}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}::ng-deep .toolbar-popover .zoom-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;border:none;background:#cb9090;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider-value{font-size:.68rem;font-weight:500;color:var(--p-text-muted-color);min-width:2.4rem;text-align:end;letter-spacing:.02em}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i2$1.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }] }); }
8761
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: ToolbarComponent, isStandalone: false, selector: "app-toolbar", inputs: { activeMode: "activeMode", activeBreakpoint: "activeBreakpoint", breakpointCounts: "breakpointCounts", auditCount: "auditCount", auditOpen: "auditOpen", activeZoom: "activeZoom", activeScreenWidth: "activeScreenWidth", canUndo: "canUndo", canRedo: "canRedo", isSaving: "isSaving", pageStatus: "pageStatus" }, outputs: { modeChange: "modeChange", breakpointChange: "breakpointChange", auditToggle: "auditToggle", zoomChange: "zoomChange", screenWidthChange: "screenWidthChange", undo: "undo", redo: "redo", save: "save", preview: "preview", publishToggle: "publishToggle" }, viewQueries: [{ propertyName: "modePopover", first: true, predicate: ["modePopover"], descendants: true }, { propertyName: "zoomPopover", first: true, predicate: ["zoomPopover"], descendants: true }, { propertyName: "widthPopover", first: true, predicate: ["widthPopover"], descendants: true }], ngImport: i0, template: "<div class=\"builder-toolbar\">\n <!-- \u2500\u2500 Mode switcher (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful\"\n (click)=\"toggleModePopover($event)\"\n [title]=\"activeModeLabel\">\n <i [class]=\"activeModeIcon\"></i>\n </button>\n\n <p-popover #modePopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Select\"\n (click)=\"selectMode(modes.Select)\">\n <i class=\"ph-thin ph-cursor\"></i>\n <span>{{ t('builder.chrome.select') }}</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Pan\"\n (click)=\"selectMode(modes.Pan)\">\n <i class=\"ph-thin ph-hand\"></i>\n <span>{{ t('builder.chrome.pan') }}</span>\n </button>\n <button class=\"popover-option\"\n [class.is-active]=\"activeMode === modes.Style\"\n (click)=\"selectMode(modes.Style)\">\n <i class=\"ph-thin ph-paint-brush-broad\"></i>\n <span>{{ t('builder.chrome.style') }}</span>\n </button>\n </div>\n </p-popover>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Undo / Redo (action) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn\" [title]=\"t('builder.chrome.undo')\" [disabled]=\"!canUndo\" (click)=\"undo.emit()\">\n <i class=\"ph-thin ph-arrow-counter-clockwise\"></i>\n </button>\n <button class=\"tool-btn\" [title]=\"t('builder.chrome.redo')\" [disabled]=\"!canRedo\" (click)=\"redo.emit()\">\n <i class=\"ph-thin ph-arrow-clockwise\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Breakpoint bar \u2014 the EDITING SCOPE, not just a preview size \u2500\u2500 -->\n <div class=\"breakpoint-bar\" role=\"group\" [attr.aria-label]=\"t('builder.chrome.breakpoint')\">\n @for (bp of breakpointOptions; track bp.id) {\n <button type=\"button\"\n class=\"bp-btn\"\n [class.is-active]=\"activeBreakpoint === bp.id\"\n [attr.aria-pressed]=\"activeBreakpoint === bp.id\"\n [title]=\"bp.label + ' \u00B7 ' + bp.range\"\n [attr.aria-label]=\"bp.label\"\n (click)=\"selectBreakpoint(bp.id)\">\n <i [class]=\"bp.icon\" [class.bp-icon--landscape]=\"bp.landscape\" aria-hidden=\"true\"></i>\n @if (breakpointCounts[bp.id]) {\n <span class=\"bp-dot\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n </div>\n\n <!-- The form state switcher used to live here \u2014 the bug that broke the header\n (`.form-state-bar` had no CSS rule at all) and, separately, the wrong home per board 3\n (forms-and-inputs-plan.md Part 2). It now lives on the Form's own panel, driven by the\n shared FormStateService, and the toolbar is back to exactly what it was before forms\n existed. -->\n\n\n <!-- \u2500\u2500 Screen width \u2014 Base only (any width above Laptop) \u2500\u2500 -->\n @if (activeBreakpoint === breakpointIds.Base) {\n <button class=\"tool-btn stateful width-btn\"\n (click)=\"toggleWidthPopover($event)\"\n [title]=\"t('builder.chrome.screenWidth')\">\n <span class=\"width-label\">{{ activeScreenWidth }}</span>\n </button>\n }\n\n <p-popover #widthPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let w of screenWidthOptions\"\n [class.is-active]=\"activeScreenWidth === w.width\"\n (click)=\"selectScreenWidth(w.width)\">\n <span>{{ w.label }}</span>\n </button>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Zoom (stateful) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn stateful zoom-btn\"\n (click)=\"toggleZoomPopover($event)\"\n [title]=\"t('builder.chrome.zoom')\">\n <span class=\"zoom-label\">{{ zoomLabel }}</span>\n </button>\n\n <p-popover #zoomPopover [appendTo]=\"'body'\" styleClass=\"toolbar-popover\">\n <div class=\"zoom-popover\">\n <div class=\"zoom-slider-row\">\n <input class=\"zoom-slider\"\n type=\"range\"\n min=\"20\" max=\"100\" step=\"1\"\n [value]=\"sliderZoom\"\n (input)=\"onSliderChange($event)\">\n <span class=\"zoom-slider-value\">{{ zoomLabel }}</span>\n </div>\n <div class=\"popover-strip\">\n <button class=\"popover-option\"\n *ngFor=\"let z of zoomOptions\"\n [class.is-active]=\"z.value === activeZoom\"\n (click)=\"selectZoom(z.value)\">\n <span>{{ z.label }}</span>\n </button>\n </div>\n </div>\n </p-popover>\n\n <!-- \u2500\u2500 Responsive audit (A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn audit-toggle\"\n [class.is-open]=\"auditOpen\"\n [class.has-findings]=\"auditCount > 0\"\n type=\"button\"\n [title]=\"t('builder.chrome.auditHint')\"\n [attr.aria-label]=\"t('builder.chrome.audit')\"\n [attr.aria-pressed]=\"auditOpen\"\n (click)=\"auditToggle.emit()\">\n <i class=\"ph-thin ph-stethoscope\" aria-hidden=\"true\"></i>\n @if (auditCount) {\n <span class=\"audit-count\">{{ auditCount }}</span>\n }\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- \u2500\u2500 Save \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn save-btn\"\n [class.is-saving]=\"isSaving\"\n [disabled]=\"isSaving\"\n [title]=\"t('builder.chrome.save')\"\n (click)=\"save.emit()\">\n <i class=\"ph-thin ph-floppy-disk\"></i>\n </button>\n\n <!-- \u2500\u2500 Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn preview-btn\"\n [title]=\"t('builder.chrome.preview')\"\n (click)=\"preview.emit()\">\n <i class=\"ph-thin ph-eye\"></i>\n </button>\n\n <span class=\"divider\"></span>\n\n <!-- Host-app toolbar actions (e.g. the Design trial button, flavors F2-D) -->\n <ng-content></ng-content>\n\n <!-- \u2500\u2500 Publish / Unpublish \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <button class=\"tool-btn publish-btn\"\n [class.is-published]=\"isPublished\"\n [title]=\"publishLabel\"\n (click)=\"publishToggle.emit()\">\n <i [class]=\"isPublished ? 'ph-thin ph-cloud-slash' : 'ph-thin ph-cloud-arrow-up'\"></i>\n <span class=\"publish-label\">{{ publishLabel }}</span>\n </button>\n</div>\n", styles: [".builder-toolbar{display:flex;align-items:center;gap:.125rem}.divider{width:1px;height:1.25rem;background:var(--p-surface-200);margin:0 .375rem;flex-shrink:0}.tool-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.tool-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color)}.tool-btn:disabled{opacity:.35;cursor:default}.tool-btn.stateful{position:relative}.tool-btn.stateful:after{content:\"\";position:absolute;inset-block-end:3px;width:4px;height:4px;border-radius:50%;background:#cb9090}.audit-toggle{position:relative}.audit-toggle.is-open{background:var(--p-surface-100);color:#a05c5c}.audit-toggle.has-findings{color:#b8702f}.audit-count{position:absolute;inset-block-start:1px;inset-inline-end:1px;min-inline-size:13px;padding:0 3px;border-radius:20px;background:#d9924e;color:#fff;font-size:8.5px;line-height:13px;font-weight:600}.breakpoint-bar{display:flex;align-items:center;gap:1px;padding:2px;border-radius:8px;background:var(--p-surface-100)}.bp-btn{position:relative;display:flex;align-items:center;justify-content:center;width:1.875rem;height:1.75rem;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--p-text-muted-color);font-size:.95rem;cursor:pointer;transition:background .15s ease-out,color .15s ease-out,border-color .15s ease-out}.bp-btn:hover{background:var(--p-surface-0);color:var(--p-text-color)}.bp-btn:focus-visible{outline:2px solid #cb9090;outline-offset:1px}.bp-btn.is-active{background:var(--p-surface-0);border-color:#cb9090;color:#a05c5c}.bp-icon--landscape{transform:rotate(90deg)}.bp-dot{position:absolute;inset-block-start:3px;inset-inline-end:3px;width:5px;height:5px;border-radius:50%;background:#d9924e}.zoom-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.5rem}.zoom-btn .zoom-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.width-btn{width:auto;border-radius:999px;padding:0 .5rem;min-width:2.8rem}.width-btn .width-label{font-size:.7rem;font-weight:500;letter-spacing:.02em}.save-btn{background:#cb9090;color:#fff}.save-btn:hover:not(:disabled){background:#b37373;color:#fff}.save-btn.is-saving{opacity:.7}.preview-btn{border:1px solid var(--p-surface-200)}.preview-btn:hover:not(:disabled){background:var(--p-surface-50);color:var(--p-text-color);border-color:var(--p-surface-100)}.publish-btn{width:auto;border-radius:999px;padding:0 .65rem;gap:.35rem;font-size:.75rem;font-weight:500;background:var(--iox-success-fill, #2E7D45);color:#fff;border:none}.publish-btn .publish-label{font-size:.72rem;letter-spacing:.02em}.publish-btn:hover:not(:disabled){background:color-mix(in srgb,var(--iox-success-fill, #2E7D45) 86%,#000);color:#fff}.publish-btn.is-published{background:transparent;color:var(--p-text-muted-color);border:1px solid var(--p-surface-200)}.publish-btn.is-published:hover:not(:disabled){background:var(--iox-danger-bg, #FBEAE8);color:var(--iox-danger-text, #8E211A);border-color:var(--iox-danger-border, #F2CBC6)}::ng-deep .toolbar-popover .p-popover-content{padding:.25rem!important;background:var(--p-surface-0)!important;border:1px solid var(--p-surface-200)!important;border-radius:10px!important;box-shadow:0 4px 16px #0f172a1a!important}.popover-strip{display:flex;flex-direction:column;gap:2px;min-width:120px}.popover-option{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4rem .65rem;border:none;background:transparent;color:var(--p-text-color);cursor:pointer;font-size:.8rem;border-radius:6px;transition:background .12s ease-out,color .12s ease-out;white-space:nowrap}.popover-option i{font-size:1rem;color:var(--p-text-muted-color)}.popover-option:hover{background:var(--p-surface-50)}.popover-option.is-active{background:#cb9090;color:#fff}.popover-option.is-active i{color:#fff}::ng-deep .toolbar-popover .zoom-popover{display:flex;flex-direction:column;gap:4px;padding:.15rem 0}::ng-deep .toolbar-popover .zoom-slider-row{display:flex;align-items:center;gap:.5rem;padding:.3rem .65rem .1rem}::ng-deep .toolbar-popover .zoom-slider{flex:1;-webkit-appearance:none;appearance:none;height:3px;border-radius:999px;background:var(--p-surface-200);outline:none;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#cb9090;cursor:pointer;transition:transform .12s ease-out}::ng-deep .toolbar-popover .zoom-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}::ng-deep .toolbar-popover .zoom-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;border:none;background:#cb9090;cursor:pointer}::ng-deep .toolbar-popover .zoom-slider-value{font-size:.68rem;font-weight:500;color:var(--p-text-muted-color);min-width:2.4rem;text-align:end;letter-spacing:.02em}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i2$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }] }); }
8665
8762
  }
8666
8763
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ToolbarComponent, decorators: [{
8667
8764
  type: Component,
@@ -9669,6 +9766,11 @@ class BuilderComponent {
9669
9766
  this.contextStore = inject(ContextStore);
9670
9767
  this.labels = inject(IOX_BUILDER_LABELS);
9671
9768
  this.sceneCanvas = inject(SceneCanvasService, { optional: true });
9769
+ /** Premium M5: the canvas's tooltips (the live site's runtime, placed in the zoomed frame) and what the design
9770
+ * state holds up for styling — a control's tooltip, a toast. */
9771
+ this.canvasTooltips = null;
9772
+ this.pinnedTooltips = new Map();
9773
+ this.shownToast = null;
9672
9774
  /**
9673
9775
  * Re-apply context-bound inputs on the CANVAS when a slot changes — the builder twin of the
9674
9776
  * engine's effect. The canvas is a live runtime, so hovering a repeater row while editing must
@@ -9876,6 +9978,7 @@ class BuilderComponent {
9876
9978
  if (this.viewportService.scaleIsDefault)
9877
9979
  this.autoFitViewport();
9878
9980
  this.overlayService.setContainer(this.previewCanvas?.nativeElement || null);
9981
+ this.attachCanvasFloating();
9879
9982
  // Builder dispatches synthetic 'scroll' events on .preview-lenis-content from
9880
9983
  // the Lenis callback — point the overlay scroll listener at that element.
9881
9984
  // (Not previewScrollRef which is the Lenis eventsTarget — dispatching there
@@ -9987,7 +10090,63 @@ class BuilderComponent {
9987
10090
  this.applyContextBindings(node.children, data, slotKeys);
9988
10091
  }
9989
10092
  }
10093
+ /**
10094
+ * Premium M5 — the floating family on the canvas. Tooltips run live (the canvas is a live runtime): hover with a
10095
+ * mouse, keyboard focus, placed in the token scope's layer inside the zoomed frame, never over the builder. The design
10096
+ * state holds a control's tooltip open (Bindings ▸ Tooltip ▸ Showing on canvas) and a toast up (After sending, a
10097
+ * showToast row) so the author styles them on the canvas — never saved, never an undo step.
10098
+ */
10099
+ attachCanvasFloating() {
10100
+ const root = this.previewCanvas?.nativeElement;
10101
+ if (!root || this.canvasTooltips)
10102
+ return;
10103
+ this.canvasTooltips = attachTooltips(root, { topLayer: false, container: canvasFloatingContainer });
10104
+ this.floatingSub = this.canvasDesignState.state$.subscribe(state => this.applyFloatingDesign(state.tooltips, state.toast));
10105
+ }
10106
+ applyFloatingDesign(tooltips, toast) {
10107
+ const root = this.previewCanvas?.nativeElement;
10108
+ const runtime = this.canvasTooltips;
10109
+ if (!root || !runtime)
10110
+ return;
10111
+ for (const [id, el] of [...this.pinnedTooltips]) {
10112
+ if (tooltips.has(id) && el.isConnected)
10113
+ continue;
10114
+ runtime.pin(el, false);
10115
+ this.pinnedTooltips.delete(id);
10116
+ }
10117
+ for (const id of tooltips) {
10118
+ if (this.pinnedTooltips.has(id))
10119
+ continue;
10120
+ const el = this.tooltipTriggerOf(root, id);
10121
+ if (!el)
10122
+ continue;
10123
+ runtime.pin(el, true);
10124
+ this.pinnedTooltips.set(id, el);
10125
+ }
10126
+ if (toast !== this.shownToast) {
10127
+ const page = root.querySelector('iox-page-component.iox-token-scope') ?? root;
10128
+ if (this.shownToast !== null && toast === null)
10129
+ showToastDesign(root.ownerDocument, '', false, { topLayer: false, container: page });
10130
+ if (toast !== null)
10131
+ showToastDesign(root.ownerDocument, toast, true, { topLayer: false, container: page });
10132
+ this.shownToast = toast;
10133
+ }
10134
+ }
10135
+ /** The element carrying a node's tooltip on the canvas (its host or the element inside it the a11y sync wrote on). */
10136
+ tooltipTriggerOf(root, id) {
10137
+ for (const el of Array.from(root.querySelectorAll(`.iox-node-${CSS.escape(id)}, .iox-outer-${CSS.escape(id)}`))) {
10138
+ if (el.hasAttribute(TOOLTIP_ATTRIBUTE))
10139
+ return el;
10140
+ const inner = el.querySelector(`[${TOOLTIP_ATTRIBUTE}]`);
10141
+ if (inner)
10142
+ return inner;
10143
+ }
10144
+ return null;
10145
+ }
9990
10146
  ngOnDestroy() {
10147
+ this.floatingSub?.unsubscribe();
10148
+ this.canvasTooltips?.destroy();
10149
+ this.canvasTooltips = null;
9991
10150
  this.sub?.unsubscribe();
9992
10151
  this.registrySub?.unsubscribe();
9993
10152
  this.viewportSub?.unsubscribe();
@@ -11056,7 +11215,7 @@ class BuilderComponent {
11056
11215
  }
11057
11216
  }
11058
11217
  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 }); }
11059
- 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 }, { propertyName: "globalsBeforeRef", first: true, predicate: ["globalsBefore"], 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 @if (activeSidebar === 'pages') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'components') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tokens') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tree') {\n <app-layer-tree\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n }\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 is the frame a pinned global sits in (globalOverlayHeight: the canvas\n slot, or the page on a short page), bound in px so position:fixed + inset:0\n fills it (CSS calc(100%) would give the wrong containing-block height). -->\n @if (globalElements.length) {\n <div\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"globalOverlayHeight\"\n [style.transform]=\"viewportTransform\">\n <!-- BEFORE globals stay here, in the frame (a Dialog inside a header must fill the frame,\n not scroll away with the page). Their wrapper's height is what the in-flow ones take,\n and the page reserves it at its top (canvasHeaderReserve, iox-ui#19). -->\n @if (globalElementsBefore.length) {\n <div #globalsBefore class=\"iox-canvas-globals-before\">\n @for (item of globalElementsBefore; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n <!-- The PINNED after globals (a fixed bottom bar, a Dialog). An in-flow AFTER global (a\n footer) is drawn in the page, after its content (iox-ui#12). -->\n @for (item of globalElementsPinnedAfter; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\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 [style.padding-block-start.px]=\"canvasHeaderReserve || null\"\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 @for (item of layout; track item) {\n <ng-container\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n\n <!-- In-flow AFTER globals (a footer) follow the page's content, inside the page's\n ground, as on the live site (iox-ui#12). Not part of the layout: the drag\n engine skips this wrapper (GLOBALS_AFTER_CLASS). -->\n @if (globalElementsInPage.length) {\n <div [class]=\"globalsAfterClass\">\n @for (item of globalElementsInPage; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\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 <!-- \u2463 THE SCENE TIMELINE (premium sites SC2, board G) \u2014 its own row under the canvas, projected from the host\n like every panel, and only there while the page holds a Scene. A sibling of .preview for the reason above. -->\n <ng-content select=\"[builderTimeline]\"></ng-content>\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 <!-- A tab or a panel deleted alone (CC4a \u2014 composite T18): the pair goes together, so the delete is offered for\n both. Every delete path asks the one guard; this is its answer. -->\n <p-dialog [visible]=\"!!pendingPairRemoval\" (visibleChange)=\"$event || cancelPairRemoval()\" [modal]=\"true\" [closable]=\"true\"\n [draggable]=\"false\" [resizable]=\"false\" [header]=\"pairRemovalText('builder.chrome.deletePairTitle')\" styleClass=\"iox-pair-removal-dialog\"\n appendTo=\"body\" [style]=\"{ width: '400px' }\">\n <p class=\"pair-removal-body\">{{ pairRemovalText('builder.chrome.deletePairBody') }}</p>\n <ng-template pTemplate=\"footer\">\n <p-button size=\"small\" severity=\"secondary\" [label]=\"pairRemovalText('builder.chrome.cancel')\" (onClick)=\"cancelPairRemoval()\"></p-button>\n <p-button size=\"small\" severity=\"danger\" [label]=\"pairRemovalText('builder.chrome.deletePair')\" (onClick)=\"confirmPairRemoval()\"></p-button>\n </ng-template>\n </p-dialog>\n\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;direction:ltr;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;direction:ltr}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .global-elements-overlay .iox-canvas-globals-before{display:flow-root;pointer-events:none}.builder .global-elements-overlay .iox-canvas-globals-before ::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.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: "directive", type: i18.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i19.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: i20.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "component", type: i1$1.IoxPageComponent, selector: "iox-page-component", inputs: ["class", "scroll"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId", "ioxPhrasing"], 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"] }] }); }
11218
+ 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 }, { propertyName: "globalsBeforeRef", first: true, predicate: ["globalsBefore"], 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 @if (activeSidebar === 'pages') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'components') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tokens') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tree') {\n <app-layer-tree\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n }\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 is the frame a pinned global sits in (globalOverlayHeight: the canvas\n slot, or the page on a short page), bound in px so position:fixed + inset:0\n fills it (CSS calc(100%) would give the wrong containing-block height). -->\n @if (globalElements.length) {\n <div\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"globalOverlayHeight\"\n [style.transform]=\"viewportTransform\">\n <!-- BEFORE globals stay here, in the frame (a Dialog inside a header must fill the frame,\n not scroll away with the page). Their wrapper's height is what the in-flow ones take,\n and the page reserves it at its top (canvasHeaderReserve, iox-ui#19). -->\n @if (globalElementsBefore.length) {\n <div #globalsBefore class=\"iox-canvas-globals-before\">\n @for (item of globalElementsBefore; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n <!-- The PINNED after globals (a fixed bottom bar, a Dialog). An in-flow AFTER global (a\n footer) is drawn in the page, after its content (iox-ui#12). -->\n @for (item of globalElementsPinnedAfter; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\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 [style.padding-block-start.px]=\"canvasHeaderReserve || null\"\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 @for (item of layout; track item) {\n <ng-container\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n\n <!-- In-flow AFTER globals (a footer) follow the page's content, inside the page's\n ground, as on the live site (iox-ui#12). Not part of the layout: the drag\n engine skips this wrapper (GLOBALS_AFTER_CLASS). -->\n @if (globalElementsInPage.length) {\n <div [class]=\"globalsAfterClass\">\n @for (item of globalElementsInPage; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\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 <!-- \u2463 THE SCENE TIMELINE (premium sites SC2, board G) \u2014 its own row under the canvas, projected from the host\n like every panel, and only there while the page holds a Scene. A sibling of .preview for the reason above. -->\n <ng-content select=\"[builderTimeline]\"></ng-content>\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 <!-- A tab or a panel deleted alone (CC4a \u2014 composite T18): the pair goes together, so the delete is offered for\n both. Every delete path asks the one guard; this is its answer. -->\n <p-dialog [visible]=\"!!pendingPairRemoval\" (visibleChange)=\"$event || cancelPairRemoval()\" [modal]=\"true\" [closable]=\"true\"\n [draggable]=\"false\" [resizable]=\"false\" [header]=\"pairRemovalText('builder.chrome.deletePairTitle')\" styleClass=\"iox-pair-removal-dialog\"\n appendTo=\"body\" [style]=\"{ width: '400px' }\">\n <p class=\"pair-removal-body\">{{ pairRemovalText('builder.chrome.deletePairBody') }}</p>\n <ng-template pTemplate=\"footer\">\n <p-button size=\"small\" severity=\"secondary\" [label]=\"pairRemovalText('builder.chrome.cancel')\" (onClick)=\"cancelPairRemoval()\"></p-button>\n <p-button size=\"small\" severity=\"danger\" [label]=\"pairRemovalText('builder.chrome.deletePair')\" (onClick)=\"confirmPairRemoval()\"></p-button>\n </ng-template>\n </p-dialog>\n\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;direction:ltr;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;direction:ltr}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .global-elements-overlay .iox-canvas-globals-before{display:flow-root;pointer-events:none}.builder .global-elements-overlay .iox-canvas-globals-before ::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.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: "directive", type: i18.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i19.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: i20.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "component", type: i2$1.IoxPageComponent, selector: "iox-page-component", inputs: ["class", "scroll"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId", "ioxPhrasing"], 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"] }] }); }
11060
11219
  }
11061
11220
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, decorators: [{
11062
11221
  type: Component,
@@ -11265,6 +11424,8 @@ class BuilderContainerComponent {
11265
11424
  */
11266
11425
  this.formClass = '';
11267
11426
  this._formState = 'idle';
11427
+ /** A form's After sending (premium M5): `toast` keeps its fields — the Form trait, read on a form host only. */
11428
+ this.afterSend = '';
11268
11429
  this.childClick = new EventEmitter();
11269
11430
  this.childMouseEnter = new EventEmitter();
11270
11431
  this.childMouseLeave = new EventEmitter();
@@ -11291,7 +11452,7 @@ class BuilderContainerComponent {
11291
11452
  this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
11292
11453
  }
11293
11454
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderContainerComponent, deps: [{ token: i0.ElementRef }, { token: DragEngineService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
11294
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderContainerComponent, isStandalone: false, selector: "div[iox-container],section[iox-container],article[iox-container],header[iox-container],footer[iox-container],main[iox-container],nav[iox-container],ul[iox-container],ol[iox-container],li[iox-container],form[iox-container]", inputs: { children: "children", style: "style", nodeId: "nodeId", dropListId: "dropListId", htmlTag: "htmlTag", start: "start", reversed: "reversed", formClass: "formClass", formState: "formState" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, host: { properties: { "class": "'container-root iox-node-' + nodeId + (listOrientation === 'horizontal' ? ' is-horizontal' : '') + (formClass ? ' ' + formClass : '')", "attr.data-iox-state": "formClass === \"iox-form\" ? formState : null", "attr.start": "htmlTag === 'ol' && start !== 1 ? start : null", "attr.reversed": "htmlTag === 'ol' && reversed ? '' : null", "attr.data-iox-item-key": "node?.itemKey ?? null" } }, ngImport: i0, template: `
11455
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderContainerComponent, isStandalone: false, selector: "div[iox-container],section[iox-container],article[iox-container],header[iox-container],footer[iox-container],main[iox-container],nav[iox-container],ul[iox-container],ol[iox-container],li[iox-container],form[iox-container]", inputs: { children: "children", style: "style", nodeId: "nodeId", dropListId: "dropListId", htmlTag: "htmlTag", start: "start", reversed: "reversed", formClass: "formClass", formState: "formState", afterSend: "afterSend" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, host: { properties: { "class": "'container-root iox-node-' + nodeId + (listOrientation === 'horizontal' ? ' is-horizontal' : '') + (formClass ? ' ' + formClass : '')", "attr.data-iox-state": "formClass === \"iox-form\" ? formState : null", "attr.data-iox-after-send": "formClass === \"iox-form\" && afterSend === \"toast\" ? \"toast\" : null", "attr.start": "htmlTag === 'ol' && start !== 1 ? start : null", "attr.reversed": "htmlTag === 'ol' && reversed ? '' : null", "attr.data-iox-item-key": "node?.itemKey ?? null" } }, ngImport: i0, template: `
11295
11456
  <!-- Empty-container slot (B2). pointer-events:none, so a click falls through and SELECTS
11296
11457
  this container — and the components panel then inserts INSIDE it. -->
11297
11458
  <div *ngIf="!children.length" class="container-placeholder" data-iox-chrome>
@@ -11376,6 +11537,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
11376
11537
  // Form nodes on the canvas carry the same class + state attribute the engine's twin does, so
11377
11538
  // the SHARED `iox-forms.css` decides visibility identically in both renderers.
11378
11539
  '[attr.data-iox-state]': 'formClass === "iox-form" ? formState : null',
11540
+ // Premium M5: a form that keeps its fields after sending — the shared CSS keeps them on screen in every state.
11541
+ '[attr.data-iox-after-send]': 'formClass === "iox-form" && afterSend === "toast" ? "toast" : null',
11379
11542
  '[attr.start]': "htmlTag === 'ol' && start !== 1 ? start : null",
11380
11543
  '[attr.reversed]': "htmlTag === 'ol' && reversed ? '' : null",
11381
11544
  // An item's identity (an accordion item, or a disclosure) — the runtime pairs its header and panel ids from it.
@@ -11399,6 +11562,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
11399
11562
  type: Input
11400
11563
  }], formState: [{
11401
11564
  type: Input
11565
+ }], afterSend: [{
11566
+ type: Input
11402
11567
  }], childClick: [{
11403
11568
  type: Output
11404
11569
  }], childMouseEnter: [{
@@ -11887,7 +12052,7 @@ class BuilderDialogComponent {
11887
12052
  onDrop(event) {
11888
12053
  this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
11889
12054
  }
11890
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, deps: [{ token: DragEngineService }, { token: i1$1.ComponentInstanceRegistryService }, { token: OverlayService }, { token: i0.ChangeDetectorRef }, { token: CanvasDesignStateService, optional: true }, { token: PanelEventService, optional: true }, { token: i0.NgZone, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
12055
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, deps: [{ token: DragEngineService }, { token: i2$1.ComponentInstanceRegistryService }, { token: OverlayService }, { token: i0.ChangeDetectorRef }, { token: CanvasDesignStateService, optional: true }, { token: PanelEventService, optional: true }, { token: i0.NgZone, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
11891
12056
  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" }, viewQueries: [{ propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true }], ngImport: i0, template: `
11892
12057
  <div *ngIf="designOpen" #stage class="dialog-stage" [attr.data-placement]="placementValue"
11893
12058
  (click)="onStageClick($event)">
@@ -11957,7 +12122,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
11957
12122
  </div>
11958
12123
  </div>
11959
12124
  `, standalone: false, styles: [":host{display:contents}.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}:host(.iox-es) .dialog-stage{animation:iox-dialog-scrim-in var(--iox-dur-base) var(--iox-ease-enter) both}:host(.iox-es) .dialog-stage[data-iox-closing]{animation:iox-dialog-scrim-out var(--iox-dur-base) var(--iox-ease-exit) both}:host(.iox-es) .dialog-surface{--dialog-exit-x: 0px;--dialog-exit-y: 12px;--dialog-exit-opacity: 0;animation:iox-dialog-in var(--iox-dur-base) var(--iox-ease-enter) both}:host(.iox-es) .dialog-stage[data-iox-closing] .dialog-surface{animation:iox-dialog-out var(--iox-dur-base) var(--iox-ease-exit) both}:host(.iox-es) .dialog-stage[data-placement=top] .dialog-surface{--dialog-exit-y: -12px}:host(.iox-es) .dialog-stage:is([data-placement=end],[data-placement=start]) .dialog-surface{--dialog-exit-y: 0px;--dialog-exit-opacity: 1}:host(.iox-es) .dialog-stage[data-placement=end] .dialog-surface{--dialog-exit-x: calc(100% * var(--dialog-dir, 1))}:host(.iox-es) .dialog-stage[data-placement=start] .dialog-surface{--dialog-exit-x: calc(-100% * var(--dialog-dir, 1))}:host-context([dir=\"rtl\"]){--dialog-dir: -1}@keyframes iox-dialog-scrim-in{0%{background-color:transparent}}@keyframes iox-dialog-scrim-out{to{background-color:transparent}}@keyframes iox-dialog-in{0%{translate:var(--dialog-exit-x) var(--dialog-exit-y);opacity:var(--dialog-exit-opacity)}}@keyframes iox-dialog-out{to{translate:var(--dialog-exit-x) var(--dialog-exit-y);opacity:var(--dialog-exit-opacity)}}: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"] }]
11960
- }], ctorParameters: () => [{ type: DragEngineService }, { type: i1$1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: i0.ChangeDetectorRef }, { type: CanvasDesignStateService, decorators: [{
12125
+ }], ctorParameters: () => [{ type: DragEngineService }, { type: i2$1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: i0.ChangeDetectorRef }, { type: CanvasDesignStateService, decorators: [{
11961
12126
  type: Optional
11962
12127
  }] }, { type: PanelEventService, decorators: [{
11963
12128
  type: Optional
@@ -11986,6 +12151,281 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
11986
12151
  args: ['stage']
11987
12152
  }] } });
11988
12153
 
12154
+ /** A frame this narrow is a phone: the popover is a bottom sheet there (iox-ui FLOATING_SHEET_QUERY's width). */
12155
+ const POPOVER_SHEET_MAX_WIDTH = 767;
12156
+ /**
12157
+ * The Popover's CANVAS twin (premium components M5, N4). Two render strategies, one model, like the Dialog's: the live
12158
+ * site uses a top-layer `popover="manual"` panel (iox-ui `popover.runtime`); here a fixed STAGE fills the transformed page
12159
+ * frame (a transform contains fixed descendants, at every zoom) and holds a PIN placed by the SAME `placeFloating`, in
12160
+ * the frame's own px, beside the element that opened it — the surface inside the pin carries both node classes (the
12161
+ * compiled node rule forces `position: static`, so the pin is what moves). The stage takes no pointer: the page around a
12162
+ * non-modal popover stays live. A frame of a phone's width shows the sheet.
12163
+ *
12164
+ * CLOSED IT RENDERS NOTHING (the Dialog's board A4): its handles are the layer-tree row, the selection toolbar's eye and
12165
+ * Bindings ▸ Popover ▸ Showing on canvas. Selecting it (or anything in it) opens it; its opener's action opens it on the
12166
+ * canvas too (the canvas is a live runtime) and a press outside, or Esc, closes that.
12167
+ */
12168
+ class BuilderPopoverComponent {
12169
+ set placement(value) {
12170
+ this._placement = value;
12171
+ if (this.designOpen)
12172
+ this.schedulePlace();
12173
+ }
12174
+ get placement() { return this._placement; }
12175
+ constructor(dragEngine, instanceRegistry, host, cdr, designState, zone) {
12176
+ this.dragEngine = dragEngine;
12177
+ this.instanceRegistry = instanceRegistry;
12178
+ this.host = host;
12179
+ this.cdr = cdr;
12180
+ this.designState = designState;
12181
+ this.zone = zone;
12182
+ this.children = [];
12183
+ this.nodeId = '';
12184
+ this.dropListId = '';
12185
+ this._placement = 'below';
12186
+ this.childClick = new EventEmitter();
12187
+ this.childMouseEnter = new EventEmitter();
12188
+ this.childMouseLeave = new EventEmitter();
12189
+ /** Canvas-only visibility. Never serialised; the live site always starts closed. */
12190
+ this.designOpen = false;
12191
+ this.top = 0;
12192
+ this.left = 0;
12193
+ this.origin = '0px 0px';
12194
+ this.side = 'below';
12195
+ this.sheet = false;
12196
+ this.closing = false;
12197
+ /** Opened by an action on the canvas: stays until a press outside, Esc, the eye or a close action. */
12198
+ this.runtimeOpen = false;
12199
+ /** Closed while the selection or the switcher still shows it: hidden until either changes. */
12200
+ this.dismissed = false;
12201
+ /** The element whose action opened it (the canvas's live runtime hands it over). */
12202
+ this.opener = null;
12203
+ this.frame = 0;
12204
+ this.cleanups = [];
12205
+ }
12206
+ /** Open on the canvas right now (an exit that is playing counts as closed). */
12207
+ get isOpen() {
12208
+ return this.designOpen && !this.closing;
12209
+ }
12210
+ ngOnInit() {
12211
+ this.stateSub = this.designState?.state$.subscribe(() => { this.dismissed = false; this.recompute(); });
12212
+ this.instanceRegistry.register(this.nodeId, {
12213
+ open: (opener) => this.inZone(() => this.openRuntime(opener)),
12214
+ close: () => this.inZone(() => this.closeRuntime()),
12215
+ toggle: (opener) => this.inZone(() => (this.isOpen ? this.closeRuntime() : this.openRuntime(opener))),
12216
+ isOpen: () => this.isOpen,
12217
+ });
12218
+ }
12219
+ ngOnDestroy() {
12220
+ this.stateSub?.unsubscribe();
12221
+ this.unwire();
12222
+ this.instanceRegistry.unregister(this.nodeId);
12223
+ }
12224
+ onDrop(event) {
12225
+ this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
12226
+ }
12227
+ /** The canvas runs interactions OUTSIDE Angular's zone; what they open still has to reach the panels. */
12228
+ inZone(fn) {
12229
+ if (this.zone)
12230
+ this.zone.run(fn);
12231
+ else
12232
+ fn();
12233
+ }
12234
+ openRuntime(opener) {
12235
+ if (opener)
12236
+ this.opener = opener;
12237
+ this.runtimeOpen = true;
12238
+ this.dismissed = false;
12239
+ this.recompute();
12240
+ this.designState?.markOpened(this.nodeId);
12241
+ }
12242
+ closeRuntime() {
12243
+ this.runtimeOpen = false;
12244
+ this.designState?.dismiss(this.nodeId);
12245
+ this.dismissed = true;
12246
+ this.recompute();
12247
+ }
12248
+ /** Shown when an action opened it, or the canvas design state shows it (selection inside / switched open). */
12249
+ recompute() {
12250
+ const shown = !this.dismissed && (this.runtimeOpen || !!this.designState?.isShown(this.nodeId));
12251
+ if (shown) {
12252
+ if (this.closing) {
12253
+ this.closing = false;
12254
+ cancelExit(this.stageRef?.nativeElement);
12255
+ }
12256
+ if (this.designOpen)
12257
+ return;
12258
+ this.designOpen = true;
12259
+ this.cdr.detectChanges();
12260
+ this.place();
12261
+ this.wire();
12262
+ return;
12263
+ }
12264
+ if (this.designOpen && !this.closing)
12265
+ this.playExit();
12266
+ }
12267
+ playExit() {
12268
+ const stage = this.stageRef?.nativeElement;
12269
+ this.closing = true;
12270
+ this.unwire();
12271
+ if (!stage) {
12272
+ this.finishExit();
12273
+ return;
12274
+ }
12275
+ void runExitMotion(stage, { animate: motionAllowed() }).then(done => { if (done && this.closing)
12276
+ this.finishExit(); });
12277
+ }
12278
+ finishExit() {
12279
+ this.closing = false;
12280
+ this.designOpen = false;
12281
+ this.cdr.markForCheck();
12282
+ this.cdr.detectChanges();
12283
+ }
12284
+ /** The opener it is anchored to: the one whose action opened it, else the first element on the canvas announcing it. */
12285
+ anchor() {
12286
+ if (this.opener?.isConnected)
12287
+ return this.opener;
12288
+ return this.host.nativeElement.ownerDocument.querySelector(popoverOpenersSelector(popoverDomId(this.nodeId)));
12289
+ }
12290
+ /** Beside its opener in the frame's own px — the ONE placeFloating; a phone-wide frame shows the sheet. */
12291
+ place() {
12292
+ const stage = this.stageRef?.nativeElement;
12293
+ const pin = this.pinRef?.nativeElement;
12294
+ if (!stage || !pin)
12295
+ return;
12296
+ const frame = layerFrame(stage);
12297
+ this.sheet = frame.viewport.width <= POPOVER_SHEET_MAX_WIDTH;
12298
+ const opener = this.anchor();
12299
+ if (!this.sheet) {
12300
+ const rtl = getComputedStyle(opener ?? this.host.nativeElement.parentElement ?? stage).direction === 'rtl';
12301
+ const anchor = opener
12302
+ ? rectInLayer(opener, frame)
12303
+ : { top: 0, left: frame.viewport.width / 2, width: 0, height: 0 };
12304
+ const spot = placeFloating(anchor, { width: pin.offsetWidth, height: pin.offsetHeight }, frame.viewport, { side: popoverSide(this.placement), align: opener ? FloatingAlign.Start : FloatingAlign.Center, rtl });
12305
+ this.top = spot.top;
12306
+ this.left = spot.left;
12307
+ this.origin = spot.origin;
12308
+ this.side = spot.side;
12309
+ }
12310
+ this.cdr.detectChanges();
12311
+ }
12312
+ schedulePlace() {
12313
+ if (this.frame)
12314
+ return;
12315
+ this.frame = requestAnimationFrame(() => { this.frame = 0; this.place(); });
12316
+ }
12317
+ /** While open: a press outside it (and outside its opener) or Esc closes what an action opened; the page scrolling
12318
+ * or the frame resizing places it again. */
12319
+ wire() {
12320
+ if (this.cleanups.length)
12321
+ return;
12322
+ const doc = this.host.nativeElement.ownerDocument;
12323
+ const on = (target, type, fn, opts) => {
12324
+ target.addEventListener(type, fn, opts);
12325
+ this.cleanups.push(() => target.removeEventListener(type, fn, opts));
12326
+ };
12327
+ const outside = (n) => {
12328
+ const surface = this.pinRef?.nativeElement;
12329
+ if (!n || surface?.contains(n))
12330
+ return false;
12331
+ return !n.closest?.(popoverOpenersSelector(popoverDomId(this.nodeId)));
12332
+ };
12333
+ on(doc, 'pointerdown', e => { if (this.runtimeOpen && outside(e.target))
12334
+ this.inZone(() => this.closeRuntime()); }, true);
12335
+ on(doc, 'keydown', e => { if (e.key === 'Escape' && this.runtimeOpen)
12336
+ this.inZone(() => this.closeRuntime()); });
12337
+ on(doc, 'scroll', () => this.schedulePlace(), { capture: true, passive: true });
12338
+ on(doc.defaultView ?? window, 'resize', () => this.schedulePlace(), { passive: true });
12339
+ }
12340
+ unwire() {
12341
+ for (const c of this.cleanups)
12342
+ c();
12343
+ this.cleanups.length = 0;
12344
+ if (this.frame) {
12345
+ cancelAnimationFrame(this.frame);
12346
+ this.frame = 0;
12347
+ }
12348
+ }
12349
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderPopoverComponent, deps: [{ token: DragEngineService }, { token: i2$1.ComponentInstanceRegistryService }, { token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: CanvasDesignStateService, optional: true }, { token: i0.NgZone, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
12350
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: BuilderPopoverComponent, isStandalone: false, selector: "app-builder-popover", inputs: { children: "children", nodeId: "nodeId", dropListId: "dropListId", placement: "placement" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, viewQueries: [{ propertyName: "stageRef", first: true, predicate: ["stage"], descendants: true }, { propertyName: "pinRef", first: true, predicate: ["pin"], descendants: true }], ngImport: i0, template: `
12351
+ <div *ngIf="designOpen" #stage class="popover-stage" [class.popover-stage--sheet]="sheet">
12352
+ <div #pin class="popover-pin" [attr.data-iox-side]="side"
12353
+ [style.top.px]="sheet ? null : top" [style.left.px]="sheet ? null : left" [style.transform-origin]="origin">
12354
+ <div [ngClass]="['iox-node-' + nodeId, 'iox-outer-' + nodeId]"
12355
+ class="popover-surface"
12356
+ ioxDropzone
12357
+ [ioxDropzoneId]="dropListId"
12358
+ [ioxDropzoneData]="children"
12359
+ (ioxDrop)="onDrop($event)">
12360
+ <div *ngIf="!children.length" class="popover-placeholder" data-iox-chrome>
12361
+ <i class="ph-thin ph-chat-teardrop-text"></i>
12362
+ <span>Drop components here</span>
12363
+ </div>
12364
+ <ng-container *ngFor="let child of children"
12365
+ [ioxRender]="child"
12366
+ [ioxDragSourceId]="dropListId"
12367
+ (onClick)="childClick.emit($event)"
12368
+ (onMouseEnter)="childMouseEnter.emit($event)"
12369
+ (onMouseLeave)="childMouseLeave.emit()">
12370
+ </ng-container>
12371
+ </div>
12372
+ </div>
12373
+ </div>
12374
+ `, isInline: true, styles: [":host{display:contents}.popover-stage{position:fixed;inset:0;z-index:55;pointer-events:none}.popover-pin{position:absolute;top:0;left:0;max-width:calc(100% - 24px);pointer-events:auto}.popover-stage--sheet .popover-pin{inset:auto 0 0;max-width:none}.popover-stage--sheet .popover-surface{width:100%;max-width:100%;border-end-start-radius:0;border-end-end-radius:0}:where(.popover-surface){box-sizing:border-box;max-width:100%;overflow:auto;contain:layout}:host(.iox-es) .popover-pin{animation:iox-popover-in var(--iox-popover-dur-in) var(--iox-popover-ease) both}:host(.iox-es) .popover-stage[data-iox-closing] .popover-pin{animation:iox-popover-out var(--iox-popover-dur-out) var(--iox-popover-ease) both}:host(.iox-es) .popover-stage--sheet .popover-pin{animation:iox-sheet-in var(--iox-sheet-dur-in) var(--iox-sheet-ease) both}:host(.iox-es) .popover-stage--sheet[data-iox-closing] .popover-pin{animation:iox-sheet-out var(--iox-sheet-dur-out) var(--iox-sheet-ease) both}@keyframes iox-popover-in{0%{opacity:0;scale:var(--iox-popover-scale)}}@keyframes iox-popover-out{to{opacity:0;scale:var(--iox-popover-scale)}}@keyframes iox-sheet-in{0%{translate:0 100%}}@keyframes iox-sheet-out{to{translate:0 100%}}@media(prefers-reduced-motion:reduce){@keyframes iox-popover-in{0%{opacity:0}}@keyframes iox-popover-out{to{opacity:0}}@keyframes iox-sheet-in{0%{opacity:0}}@keyframes iox-sheet-out{to{opacity:0}}}.popover-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:72px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.popover-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", "ioxPhrasing"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
12375
+ }
12376
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderPopoverComponent, decorators: [{
12377
+ type: Component,
12378
+ args: [{ selector: 'app-builder-popover', template: `
12379
+ <div *ngIf="designOpen" #stage class="popover-stage" [class.popover-stage--sheet]="sheet">
12380
+ <div #pin class="popover-pin" [attr.data-iox-side]="side"
12381
+ [style.top.px]="sheet ? null : top" [style.left.px]="sheet ? null : left" [style.transform-origin]="origin">
12382
+ <div [ngClass]="['iox-node-' + nodeId, 'iox-outer-' + nodeId]"
12383
+ class="popover-surface"
12384
+ ioxDropzone
12385
+ [ioxDropzoneId]="dropListId"
12386
+ [ioxDropzoneData]="children"
12387
+ (ioxDrop)="onDrop($event)">
12388
+ <div *ngIf="!children.length" class="popover-placeholder" data-iox-chrome>
12389
+ <i class="ph-thin ph-chat-teardrop-text"></i>
12390
+ <span>Drop components here</span>
12391
+ </div>
12392
+ <ng-container *ngFor="let child of children"
12393
+ [ioxRender]="child"
12394
+ [ioxDragSourceId]="dropListId"
12395
+ (onClick)="childClick.emit($event)"
12396
+ (onMouseEnter)="childMouseEnter.emit($event)"
12397
+ (onMouseLeave)="childMouseLeave.emit()">
12398
+ </ng-container>
12399
+ </div>
12400
+ </div>
12401
+ </div>
12402
+ `, standalone: false, styles: [":host{display:contents}.popover-stage{position:fixed;inset:0;z-index:55;pointer-events:none}.popover-pin{position:absolute;top:0;left:0;max-width:calc(100% - 24px);pointer-events:auto}.popover-stage--sheet .popover-pin{inset:auto 0 0;max-width:none}.popover-stage--sheet .popover-surface{width:100%;max-width:100%;border-end-start-radius:0;border-end-end-radius:0}:where(.popover-surface){box-sizing:border-box;max-width:100%;overflow:auto;contain:layout}:host(.iox-es) .popover-pin{animation:iox-popover-in var(--iox-popover-dur-in) var(--iox-popover-ease) both}:host(.iox-es) .popover-stage[data-iox-closing] .popover-pin{animation:iox-popover-out var(--iox-popover-dur-out) var(--iox-popover-ease) both}:host(.iox-es) .popover-stage--sheet .popover-pin{animation:iox-sheet-in var(--iox-sheet-dur-in) var(--iox-sheet-ease) both}:host(.iox-es) .popover-stage--sheet[data-iox-closing] .popover-pin{animation:iox-sheet-out var(--iox-sheet-dur-out) var(--iox-sheet-ease) both}@keyframes iox-popover-in{0%{opacity:0;scale:var(--iox-popover-scale)}}@keyframes iox-popover-out{to{opacity:0;scale:var(--iox-popover-scale)}}@keyframes iox-sheet-in{0%{translate:0 100%}}@keyframes iox-sheet-out{to{translate:0 100%}}@media(prefers-reduced-motion:reduce){@keyframes iox-popover-in{0%{opacity:0}}@keyframes iox-popover-out{to{opacity:0}}@keyframes iox-sheet-in{0%{opacity:0}}@keyframes iox-sheet-out{to{opacity:0}}}.popover-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:72px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.popover-placeholder i{font-size:18px}\n"] }]
12403
+ }], ctorParameters: () => [{ type: DragEngineService }, { type: i2$1.ComponentInstanceRegistryService }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: CanvasDesignStateService, decorators: [{
12404
+ type: Optional
12405
+ }] }, { type: i0.NgZone, decorators: [{
12406
+ type: Optional
12407
+ }] }], propDecorators: { children: [{
12408
+ type: Input
12409
+ }], nodeId: [{
12410
+ type: Input
12411
+ }], dropListId: [{
12412
+ type: Input
12413
+ }], placement: [{
12414
+ type: Input
12415
+ }], childClick: [{
12416
+ type: Output
12417
+ }], childMouseEnter: [{
12418
+ type: Output
12419
+ }], childMouseLeave: [{
12420
+ type: Output
12421
+ }], stageRef: [{
12422
+ type: ViewChild,
12423
+ args: ['stage']
12424
+ }], pinRef: [{
12425
+ type: ViewChild,
12426
+ args: ['pin']
12427
+ }] } });
12428
+
11989
12429
  /** Swiper's event → the interaction trigger it replays (componentEvents). */
11990
12430
  const SWIPER_TRIGGER = {
11991
12431
  slideChange: 'swiperSlideChange', reachBeginning: 'swiperReachBeginning', reachEnd: 'swiperReachEnd',
@@ -13033,6 +13473,7 @@ const DECLARATIONS = [
13033
13473
  BuilderRepeaterComponent,
13034
13474
  BuilderButtonBlockComponent,
13035
13475
  BuilderDialogComponent,
13476
+ BuilderPopoverComponent,
13036
13477
  SliderContainerComponent,
13037
13478
  SliderSlideComponent,
13038
13479
  PanelComponent,
@@ -13055,6 +13496,7 @@ class IoxBuilderModule {
13055
13496
  BuilderRepeaterComponent,
13056
13497
  BuilderButtonBlockComponent,
13057
13498
  BuilderDialogComponent,
13499
+ BuilderPopoverComponent,
13058
13500
  SliderContainerComponent,
13059
13501
  SliderSlideComponent,
13060
13502
  PanelComponent,
@@ -13097,6 +13539,7 @@ class IoxBuilderModule {
13097
13539
  BuilderRepeaterComponent,
13098
13540
  BuilderButtonBlockComponent,
13099
13541
  BuilderDialogComponent,
13542
+ BuilderPopoverComponent,
13100
13543
  SliderContainerComponent,
13101
13544
  SliderSlideComponent,
13102
13545
  PanelComponent,
@@ -13210,6 +13653,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
13210
13653
  BuilderRepeaterComponent,
13211
13654
  BuilderButtonBlockComponent,
13212
13655
  BuilderDialogComponent,
13656
+ BuilderPopoverComponent,
13213
13657
  SliderContainerComponent,
13214
13658
  SliderSlideComponent,
13215
13659
  PanelComponent,
@@ -13931,6 +14375,7 @@ function layoutSpecimen(id, createConfig, copy) {
13931
14375
  case 'card':
13932
14376
  case 'panel':
13933
14377
  case 'dialog':
14378
+ case 'popover':
13934
14379
  return boxed(id, createConfig, [text(copy.label, createConfig, 'small', { margin: '0' })], { padding: '10px 12px', display: 'inline-block', minWidth: '120px', width: 'auto', maxWidth: '100%' });
13935
14380
  case 'nav':
13936
14381
  return boxed(id, createConfig, [
@@ -14147,6 +14592,46 @@ class DialogComponentConfig extends ComponentConfig {
14147
14592
  }
14148
14593
  }
14149
14594
 
14595
+ /** The sides a popover may take beside its opener — Below unless the author picks another; flipped at the screen's edge. */
14596
+ const POPOVER_PLACEMENT_OPTIONS = [
14597
+ { label: 'Below', value: 'below' },
14598
+ { label: 'Above', value: 'above' },
14599
+ { label: 'Start', value: 'start' },
14600
+ { label: 'End', value: 'end' },
14601
+ ];
14602
+ /**
14603
+ * Popover (premium components M5, N4) — an ordinary container whose children are normal nodes, opened BESIDE the element
14604
+ * that opens it (its `openPopover` / `togglePopover` action) and closed by `closePopover`, Esc or a press outside. NOT
14605
+ * modal: the page stays live around it. On the live site it is a top-layer `popover="manual"` panel placed by the ONE
14606
+ * `placeFloating` (iox-ui `popover.runtime`), a bottom sheet on a phone; on the CANVAS it is a pin in a fixed stage that
14607
+ * the transformed frame contains, placed by the same function in the frame's own px. Ships CLOSED on the live site;
14608
+ * on the canvas the selection, the eye and Bindings ▸ Popover ▸ Showing on canvas open it.
14609
+ */
14610
+ class PopoverComponentConfig extends ComponentConfig {
14611
+ constructor() {
14612
+ super('Popover', 'app-builder-popover', 'ph-thin ph-chat-teardrop-text', 'Overlay');
14613
+ this.children = [];
14614
+ this.inputs = {
14615
+ placement: 'select',
14616
+ colorScheme: 'select',
14617
+ ariaLabel: 'text',
14618
+ };
14619
+ this.traits = [
14620
+ new TraitConfig('placement', 'Side', TraitInputType.Select, POPOVER_PLACEMENT_OPTIONS, 'below'),
14621
+ ...buildAccessibleNameTraits(),
14622
+ buildColorSchemeTrait(),
14623
+ ];
14624
+ this.styleTraits = buildFullStyleTraits();
14625
+ this.applyStyleDefaults({
14626
+ width: '320px',
14627
+ backgroundColor: '#ffffff',
14628
+ borderRadius: '12px',
14629
+ padding: '16px',
14630
+ boxShadow: '0 12px 32px rgba(0, 0, 0, 0.18)',
14631
+ });
14632
+ }
14633
+ }
14634
+
14150
14635
  class BuilderButtonComponentConfig extends ComponentConfig {
14151
14636
  constructor() {
14152
14637
  super('Button', 'app-builder-button', 'ph-thin ph-cursor-click', 'Basic');
@@ -14194,6 +14679,8 @@ class ButtonBlockComponentConfig extends ComponentConfig {
14194
14679
  ...linkTraits({ urlTrait: 'href', urlDefault: '', showWhen: LINKS_AS_ANCHOR, targets: BUTTON_TARGETS }),
14195
14680
  new TraitConfig('type', 'Type', TraitInputType.Select, ['button', 'submit', 'reset'], 'button', false, { trait: 'tag', values: 'button' }),
14196
14681
  ...buildAccessibleNameTraits(),
14682
+ // Premium M5: words for a control that has none of its own (Bindings ▸ Tooltip).
14683
+ ...buildTooltipTraits(),
14197
14684
  ];
14198
14685
  this.styleTraits = buildFullStyleTraits();
14199
14686
  this.applyStyleDefaults({
@@ -14464,7 +14951,13 @@ class FormComponentConfig extends ComponentConfig {
14464
14951
  { label: 'On submit only', value: 'submit' },
14465
14952
  { label: 'As you type', value: 'live' },
14466
14953
  ], 'smart'),
14467
- new TraitConfig('resetAfterSubmit', 'Clear the form after submit', TraitInputType.Checkbox, undefined, false),
14954
+ // After sending (premium M5, N3): the success in place (M3), or keep the fields — cleared for the next answer —
14955
+ // and say the Sent words in a toast. Edited in Bindings ▸ After sending. (It replaced `resetAfterSubmit`, a
14956
+ // switch no renderer ever read: every form clears its answers once they are sent.)
14957
+ new TraitConfig(FORM_AFTER_SEND_TRAIT, 'After sending', TraitInputType.Select, [
14958
+ { label: 'Show the success message', value: FormAfterSend.Message },
14959
+ { label: 'Keep the fields and show a toast', value: FormAfterSend.Toast },
14960
+ ], FormAfterSend.Message),
14468
14961
  // Board 11 — "when it fails". Both default ON: they are what a well-behaved form does,
14469
14962
  // and an author turns one off deliberately. Edited in the Form Studio's States tab,
14470
14963
  // which is why they carry no SectionLabel of their own here.
@@ -14744,6 +15237,8 @@ class BuilderLinkComponentConfig extends ComponentConfig {
14744
15237
  new TraitConfig('label', 'Label', TraitInputType.Text, undefined, 'Click here'),
14745
15238
  ...linkTraits(),
14746
15239
  ...buildAccessibleNameTraits(),
15240
+ // Premium M5: words for a control that has none of its own (Bindings ▸ Tooltip).
15241
+ ...buildTooltipTraits(),
14747
15242
  ];
14748
15243
  this.styleTraits = buildFullStyleTraits();
14749
15244
  }
@@ -14764,6 +15259,8 @@ class BuilderLinkedContainerConfig extends ComponentConfig {
14764
15259
  ...linkTraits(),
14765
15260
  buildColorSchemeTrait(),
14766
15261
  ...buildAccessibleNameTraits(),
15262
+ // Premium M5: words for a control that has none of its own (Bindings ▸ Tooltip).
15263
+ ...buildTooltipTraits(),
14767
15264
  ];
14768
15265
  this.styleTraits = buildFullStyleTraits();
14769
15266
  this.applyStyleDefaults({
@@ -15092,5 +15589,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
15092
15589
  * Generated bundle index. Do not edit.
15093
15590
  */
15094
15591
 
15095
- export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BOUND_ICON, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_FONT_LINK_ATTR, BUILDER_TOKEN_SCOPE_CLASS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDialogComponent, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, BusinessSpecimenView, ButtonBlockComponentConfig, CANVAS_EMPTY_IMAGE_LABEL_EN, CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_ROOT_RESET_CSS, COMPOSITE_PRESETS, CanvasDesignStateService, CanvasShowState, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, CompositeCanvasService, CompositePaletteGroup, CompositePresetId, DEFAULT_ESSENTIAL_TRAITS, DESCRIPTOR_MAX_LENGTH, DRAG_HANDLE_ATTR, DROP_REFUSED_CLASS, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS, EDITOR_CHROME_ATTRIBUTE, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_STATE_BLOCK_TYPES, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FieldTrait, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, GLOBALS_AFTER_CLASS, GalleryConfig, GlobalCanvasLayer, GroupStyleConfig, HIDDEN_AT_REST_LABEL_KEY, HIDDEN_AT_REST_ROLES, HIDDEN_AT_REST_TYPES, INSERT_REFUSAL_LABEL_KEYS, INTERACTION_STATES, INVERSE_ACTION, IOX_BUILDER_CANVAS, IOX_BUILDER_LABELS, IOX_CONTENT_TYPE_LABELS, IOX_DATA_SOURCE_ENDPOINTS, InsertRefusal, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LAYOUT_PRESETS, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PAGE_TRANSITION_EASING_OPTIONS, PALETTE_PRESET_DEFS, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PhoneMenuOpenerAction, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, REPEATER_SOURCE_PROP, REPEATER_TYPE, REVEALED_WHILE_SELECTED_ROLES, ROOT_ESCAPE_PX, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, RootZone, SCREEN_WIDTH_OPTIONS, SENT_LABEL_TRAIT, SILENT_REFUSALS, STRUCTURAL_STATES, SUPPORTED_STATES, SceneCanvasService, SectionComponent, SectionComponentConfig, SelectionReveal, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StructureFix, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SubmitButtonComponentConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TOGGLE_PART_ROLES, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VALIDATION_STATES, VALIDATION_STATE_CLASS, VideoConfig, ViewportService, ZOOM_OPTIONS, addFormChannel, arrayItemSchemaFields, arraySchemaFields, auditNodes, bindingLabel, bindingLabels, buildBusinessSpecimen, buildCompositePreset, buildConsentPreset, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormActionsRow, buildFormChannel, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, builderCompositeFactory, canFluidise, canvasEmptyImageLabel, canvasHostDisplay, collapseColumnsProps, columnCountOf, compositeStructureFindings, defaultBuilderLabels, defaultPageSettings, deleteTabPair, displayTriggerOf, dropChildren, dropTargetLabel, duplicateTabPair, escapesRoot, essentialTraitsFor, findNodeByIdInEither, findNodeLocation, findParentOf, flattenLayerTree, flattenSchemaFields, fluidiseProps, formChannelOf, generateNodeId, globalCanvasLayer, hiddenAtBase, hiddenUntilItemOn, inPhoneMenuContext, insertRefusal, insertRefusalLabel, interactionTargets, isHiddenAt, isHiddenAtRest, isPhoneMenuOpener, isPinnedGlobal, isSpecimen, itemsOnAtRest, markSpecimen, matchesControlSearch, nearestFormNode, neverShown, nodeAncestorPath, nodeAncestorPathInEither, nodeDescriptor, nodeStyleMap, overlayGlobals, pathToNode, phoneMenuOf, phoneMenuShowsAt, refreshSubtree, regenerateNodeIds, removalRefusal, reorderProps, repeaterSourceAlias, repeaterSubLabel, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, retypeNode, rootEscapeBand, rootZone, selectionRevealOf, setChannelAddress, setOverlayPreview, setStyleTrait, setTrait, shortDescriptor, shownBeforeControl, sourceContentType, sourceTypeLabel, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, syncNodeInputs, tabPairLabel, tabsRootForPart, textNode, upsertNodeStyles, visibilityProps, walkNodes, wrapNode };
15592
+ export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BOUND_ICON, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_FONT_LINK_ATTR, BUILDER_TOKEN_SCOPE_CLASS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDialogComponent, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderPopoverComponent, BuilderRepeaterComponent, BuilderSpacerComponentConfig, BusinessSpecimenView, ButtonBlockComponentConfig, CANVAS_EMPTY_IMAGE_LABEL_EN, CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_ROOT_RESET_CSS, CANVAS_TOKEN_SCOPE, COMPOSITE_PRESETS, CanvasDesignStateService, CanvasShowState, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, CompositeCanvasService, CompositePaletteGroup, CompositePresetId, DEFAULT_ESSENTIAL_TRAITS, DESCRIPTOR_MAX_LENGTH, DRAG_HANDLE_ATTR, DROP_REFUSED_CLASS, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS, EDITOR_CHROME_ATTRIBUTE, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_STATE_BLOCK_TYPES, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FieldTrait, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, GLOBALS_AFTER_CLASS, GalleryConfig, GlobalCanvasLayer, GroupStyleConfig, HIDDEN_AT_REST_LABEL_KEY, HIDDEN_AT_REST_ROLES, HIDDEN_AT_REST_TYPES, INSERT_REFUSAL_LABEL_KEYS, INTERACTION_STATES, INVERSE_ACTION, IOX_BUILDER_CANVAS, IOX_BUILDER_LABELS, IOX_CONTENT_TYPE_LABELS, IOX_DATA_SOURCE_ENDPOINTS, InsertRefusal, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LAYOUT_PRESETS, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PAGE_TRANSITION_EASING_OPTIONS, PALETTE_PRESET_DEFS, POPOVER_PLACEMENT_OPTIONS, POPOVER_SHEET_MAX_WIDTH, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PhoneMenuOpenerAction, PopoverComponentConfig, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, REPEATER_SOURCE_PROP, REPEATER_TYPE, REVEALED_WHILE_SELECTED_ROLES, ROOT_ESCAPE_PX, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, RootZone, SCREEN_WIDTH_OPTIONS, SENT_LABEL_TRAIT, SILENT_REFUSALS, STRUCTURAL_STATES, SUPPORTED_STATES, SceneCanvasService, SectionComponent, SectionComponentConfig, SelectionReveal, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StructureFix, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SubmitButtonComponentConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TOGGLE_PART_ROLES, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VALIDATION_STATES, VALIDATION_STATE_CLASS, VideoConfig, ViewportService, ZOOM_OPTIONS, addFormChannel, arrayItemSchemaFields, arraySchemaFields, auditNodes, bindingLabel, bindingLabels, buildBusinessSpecimen, buildCompositePreset, buildConsentPreset, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormActionsRow, buildFormChannel, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, builderCompositeFactory, canFluidise, canvasEmptyImageLabel, canvasFloatingContainer, canvasHostDisplay, collapseColumnsProps, columnCountOf, compositeStructureFindings, defaultBuilderLabels, defaultPageSettings, deleteTabPair, displayTriggerOf, dropChildren, dropTargetLabel, duplicateTabPair, escapesRoot, essentialTraitsFor, findNodeByIdInEither, findNodeLocation, findParentOf, flattenLayerTree, flattenSchemaFields, fluidiseProps, formChannelOf, generateNodeId, globalCanvasLayer, hiddenAtBase, hiddenUntilItemOn, inPhoneMenuContext, insertRefusal, insertRefusalLabel, interactionTargets, isHiddenAt, isHiddenAtRest, isPhoneMenuOpener, isPinnedGlobal, isSpecimen, itemsOnAtRest, markSpecimen, matchesControlSearch, nearestFormNode, neverShown, nodeAncestorPath, nodeAncestorPathInEither, nodeDescriptor, nodeStyleMap, overlayGlobals, pathToNode, phoneMenuOf, phoneMenuShowsAt, refreshSubtree, regenerateNodeIds, removalRefusal, reorderProps, repeaterSourceAlias, repeaterSubLabel, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, retypeNode, rootEscapeBand, rootZone, selectionRevealOf, setChannelAddress, setOverlayPreview, setStyleTrait, setTrait, shortDescriptor, showCanvasToast, shownBeforeControl, sourceContentType, sourceTypeLabel, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, syncNodeInputs, tabPairLabel, tabsRootForPart, textNode, upsertNodeStyles, visibilityProps, walkNodes, wrapNode };
15096
15593
  //# sourceMappingURL=vectoriox-iox-builder.mjs.map