@vectoriox/iox-builder 11.3.0 → 11.5.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.
@@ -17,7 +17,7 @@ import { TooltipModule } from 'primeng/tooltip';
17
17
  import * as i20 from 'primeng/dialog';
18
18
  import { DialogModule } from 'primeng/dialog';
19
19
  import * as i1$1 from '@vectoriox/iox-ui';
20
- import { nodeCssId, needsGroupName, resolveBoundValue, 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, buildPartSelector, planResponsiveRules, resolveStyleProps, targetedStateSelector, stateSelectorSuffix, 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, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, motionAllowed, finalKeyframe, keyframeStyleEntries, buildInteractionKeyframes, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, nodeTraitValue, syncNodeA11yAttributes, nodeA11yAttributes, safeSetInput, colorSchemeOf, composeVirtualTraits, withDefaultHoverTransition, stateNamesIn, partClassName, switchElementStyle, releaseElementStyle, countOverrides, setOverride, breakpointDef, breakpointForWidth, FormRenderState, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, IS_PREVIEW, buttonBlockHref, bootSlider, IoxPageModule, PALETTE_PRESETS, elementStyleById, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
20
+ import { nodeCssId, needsGroupName, resolveBoundValue, 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, buildPartSelector, planResponsiveRules, resolveStyleProps, targetedStateSelector, stateSelectorSuffix, 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, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, motionAllowed, finalKeyframe, entranceDelay, repeatedRowIndex, keyframeStyleEntries, buildInteractionKeyframes, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, nodeTraitValue, syncNodeA11yAttributes, nodeA11yAttributes, safeSetInput, colorSchemeOf, composeVirtualTraits, withDefaultHoverTransition, stateNamesIn, partClassName, switchElementStyle, releaseElementStyle, countOverrides, setOverride, breakpointDef, breakpointForWidth, FormRenderState, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, IS_PREVIEW, buttonBlockHref, 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, 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 } from '@vectoriox/iox-ui/composite-core';
23
23
  import { formContentInHost, isInteractiveHost, findFormContentInHosts } from '@vectoriox/iox-ui/forms-core';
@@ -25,7 +25,7 @@ import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
25
25
  import interact from 'interactjs';
26
26
  import { map, catchError } from 'rxjs/operators';
27
27
  import * as i1 from '@angular/common/http';
28
- import { itemActionOf, BEHAVIOUR_ACTION_TYPES, SERVER_ELEMENT_ACTION_TYPES, CONSENT_ACTION_TYPES, opensAtClickedItem } from '@vectoriox/iox-ui/actions-core';
28
+ 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
29
  import Lenis from 'lenis';
30
30
  import Moveable from 'moveable';
31
31
  import Selecto from 'selecto';
@@ -931,6 +931,9 @@ var PanelEventTypes;
931
931
  * through also runs. This event is that path for code outside `BuilderComponent` itself,
932
932
  * mirroring what `onTreeNodeSelect`/`onCrumbSelect` already do internally. */
933
933
  PanelEventTypes["SELECT_NODE"] = "selectnode";
934
+ /** A canvas Dialog closed while the selection sat in it (data = { dialogId }): the builder moves the selection
935
+ * to the dialog's opener (the menu button), else clears it — never left on an element leaving the DOM. */
936
+ PanelEventTypes["DIALOG_CLOSED_OVER_SELECTION"] = "dialogclosedoverselection";
934
937
  /** A panel rewrote a node's SUBTREE in place — its children (a Card's Parts), its layout styles (Card ▸
935
938
  * Layout), a part's element style or scheme. Carries `{ node }`. The builder re-renders the subtree,
936
939
  * re-registers every node's rules and design classes, refreshes a Repeater holding it, and records ONE
@@ -4132,7 +4135,8 @@ class InteractionEngineService {
4132
4135
  // now runs live in the builder (see attachInteraction), so pre-state is applied
4133
4136
  // for all of them EXCEPT scrollProgress — that trigger is unimplemented (no-op),
4134
4137
  // so pre-stating it would leave the element stuck at opacity:0 forever.
4135
- const skipPreState = inBuilder && ix.trigger === 'scrollProgress';
4138
+ // Under reduced motion a self-playing entrance starts shown and never moves (shared `entranceStartsHidden`).
4139
+ const skipPreState = (inBuilder && ix.trigger === 'scrollProgress') || !entranceStartsHidden(ix.trigger);
4136
4140
  if (!skipPreState) {
4137
4141
  for (const action of ix.actions) {
4138
4142
  // A disabled action must not pre-state: it would hide the element with nothing
@@ -4678,10 +4682,11 @@ class InteractionEngineService {
4678
4682
  this.settleAfter(element, type);
4679
4683
  return;
4680
4684
  }
4681
- // 'both' = backwards (holds first keyframe during delay) + forwards (holds last keyframe after end).
4685
+ // 'both' = backwards (holds first keyframe during delay) + forwards (holds last keyframe after end). A look
4686
+ // entrance on a Repeater's template staggers its rows: each copy adds its row's share (shared `entranceDelay`).
4682
4687
  const anim = element.animate(keyframes, {
4683
4688
  duration: action.duration,
4684
- delay: action.delay,
4689
+ delay: entranceDelay(action, repeatedRowIndex(element)),
4685
4690
  easing: action.easing,
4686
4691
  fill: 'both',
4687
4692
  });
@@ -4889,18 +4894,21 @@ class CanvasDesignStateService {
4889
4894
  return;
4890
4895
  this._state$.next({ ...this._state$.value, revealed, pinned, selectedTriggers, triggeredBySelection });
4891
4896
  }
4892
- /** Closed on the canvas (its X, its backdrop, the eye): no longer pinned or switched open. */
4897
+ /** Closed on the canvas (its X, its backdrop, the eye): no longer pinned, switched open or revealed by the
4898
+ * selection — selecting into it again reveals it again. */
4893
4899
  dismiss(nodeId) {
4894
4900
  if (!nodeId)
4895
4901
  return;
4896
- const { pinned, shown } = this._state$.value;
4897
- if (!pinned.has(nodeId) && !shown.has(nodeId))
4902
+ const { pinned, shown, revealed } = this._state$.value;
4903
+ if (!pinned.has(nodeId) && !shown.has(nodeId) && !revealed.has(nodeId))
4898
4904
  return;
4899
4905
  const nextPinned = new Set(pinned);
4900
4906
  nextPinned.delete(nodeId);
4901
4907
  const nextShown = new Map(shown);
4902
4908
  nextShown.delete(nodeId);
4903
- this._state$.next({ ...this._state$.value, pinned: nextPinned, shown: nextShown });
4909
+ const nextRevealed = new Set(revealed);
4910
+ nextRevealed.delete(nodeId);
4911
+ this._state$.next({ ...this._state$.value, pinned: nextPinned, shown: nextShown, revealed: nextRevealed });
4904
4912
  }
4905
4913
  /** The switcher's choice for one part (null = back to the saved default). */
4906
4914
  setShown(nodeId, state) {
@@ -6647,6 +6655,45 @@ function preventNativeCanvasDrag(event) {
6647
6655
  event.preventDefault();
6648
6656
  }
6649
6657
 
6658
+ /** The panels that describe the SELECTED ELEMENT: selecting another element keeps whichever of these is open. */
6659
+ const ELEMENT_PANELS = new Set([PanelTypes.BINDINGS, PanelTypes.STYLES, PanelTypes.INTERACTIONS]);
6660
+ /**
6661
+ * The panel a selection shows. An element panel that is open stays (the author wiring Interactions across
6662
+ * several elements is not thrown back to Styles on every click); with the Page panel open, or none, the
6663
+ * element's Styles open.
6664
+ */
6665
+ function panelForSelection(activePanel, isPanelOpen) {
6666
+ return isPanelOpen && activePanel && ELEMENT_PANELS.has(activePanel) ? activePanel : PanelTypes.STYLES;
6667
+ }
6668
+
6669
+ /**
6670
+ * The node whose click opens `dialogId` (the menu button for a drawer, More for a bar's drawer) — where the
6671
+ * canvas selection goes when the dialog it sat in closes. The first in tree order across the page and its
6672
+ * globals, or null when nothing opens it.
6673
+ */
6674
+ function findDialogOpener(trees, dialogId) {
6675
+ const visit = (nodes) => {
6676
+ for (const node of nodes ?? []) {
6677
+ if (openedDialogId(node) === dialogId)
6678
+ return node;
6679
+ const hit = visit(node.children);
6680
+ if (hit)
6681
+ return hit;
6682
+ }
6683
+ return null;
6684
+ };
6685
+ for (const tree of trees) {
6686
+ const hit = visit(tree);
6687
+ if (hit)
6688
+ return hit;
6689
+ }
6690
+ return null;
6691
+ }
6692
+ /** True when the element sits in a dialog that is playing its exit (actions-core `runExitMotion` marks it). */
6693
+ function isInsideExit(el) {
6694
+ return !!el?.closest?.(`[${DIALOG_CLOSING_ATTRIBUTE}]`);
6695
+ }
6696
+
6650
6697
  /** States whose rule needs the LIVE DOM to scope (a parent's / a trigger's id) — the RenderDirective owns them. */
6651
6698
  const DOM_SCOPED_STATES = new Set([...RELATIONAL_STATES, ...TARGETED_STATES]);
6652
6699
  /** A node's base style map, from its style traits (the live values the Style panel edits). */
@@ -7483,6 +7530,19 @@ function compositePartSub(node, ancestors = []) {
7483
7530
  return null;
7484
7531
  }
7485
7532
 
7533
+ /** Hides a piece of selection / hover chrome without dropping the entry it describes. */
7534
+ const HIDDEN_CHROME = Object.freeze({ display: 'none' });
7535
+ /**
7536
+ * True when the element is in the document and draws a box the chrome can point at. A node whose element left
7537
+ * the DOM (a part of a canvas dialog that just closed) or draws nothing (`display: contents`, a closed dialog's
7538
+ * host) measures 0×0 at the viewport's corner — chrome drawn from that rect lands at the top-left of the screen.
7539
+ */
7540
+ function hasRenderedBox(el, rect) {
7541
+ if (!el?.isConnected || !rect)
7542
+ return false;
7543
+ return rect.width > 0 || rect.height > 0;
7544
+ }
7545
+
7486
7546
  /**
7487
7547
  * PresetRegistryService — in-memory store for org-level style presets.
7488
7548
  *
@@ -8060,6 +8120,12 @@ class OverlayComponent {
8060
8120
  }
8061
8121
  const styledEl = this.overlayService.getStyledElement(this.hoverEntry.element);
8062
8122
  const rect = styledEl.getBoundingClientRect();
8123
+ if (!hasRenderedBox(styledEl, rect)) {
8124
+ this.hiOutlineStyle = HIDDEN_CHROME;
8125
+ this.hiMarginStyle = HIDDEN_CHROME;
8126
+ this.hiPaddingStyle = HIDDEN_CHROME;
8127
+ return;
8128
+ }
8063
8129
  // Recalculate fresh — stored boxModel can be stale if CSS rules changed.
8064
8130
  const { margin, border, padding } = this.overlayService.buildBoxModel(this.hoverEntry.element);
8065
8131
  // All overlay boxes use position:fixed with raw viewport coords from
@@ -8102,6 +8168,12 @@ class OverlayComponent {
8102
8168
  }
8103
8169
  const styledEl = this.overlayService.getStyledElement(this.selectEntry.element);
8104
8170
  const rect = styledEl.getBoundingClientRect();
8171
+ // An element that left the DOM (a part of a dialog that just closed) or draws no box measures 0,0 — the
8172
+ // outline and its toolbar would sit at the viewport's corner. Nothing to point at: no chrome.
8173
+ if (!hasRenderedBox(styledEl, rect)) {
8174
+ this.selectOutlineStyle = HIDDEN_CHROME;
8175
+ return;
8176
+ }
8105
8177
  // position:fixed with raw viewport coords — same reasoning as updateHover.
8106
8178
  this.selectOutlineStyle = this.createBoxStyle(rect.top, rect.left, rect.width, rect.height);
8107
8179
  // The breadcrumb band begins at the canvas area's top (our own host). A toolbar rendered
@@ -9827,12 +9899,20 @@ class BuilderComponent {
9827
9899
  this.activePanel = panel;
9828
9900
  this.panelEventService.emit(this.isPanelOpen ? PanelEventTypes.PANEL_SELECTED : PanelEventTypes.PANEL_OPEN, panel);
9829
9901
  }
9902
+ /** A new element was selected: keep the element panel that is open, else open Styles (`panelForSelection`). */
9903
+ selectPanelForElement() {
9904
+ this.selectPanel(panelForSelection(this.activePanel, this.isPanelOpen));
9905
+ }
9830
9906
  handleClick(event) {
9831
9907
  if (this.activeMode === BuilderMode.Pan)
9832
9908
  return;
9909
+ // The canvas is live: a click on a dialog's Close ran its closeDialog first (capture). Selecting into the
9910
+ // closing dialog would reveal it again (R2) and leave the selection on an element about to leave the DOM.
9911
+ if (isInsideExit(event.element))
9912
+ return;
9833
9913
  this.selectedItem = event.node;
9834
9914
  this.overlayService.setSelect(event.element, event.node.type, this.activeMode, event.node, event.componentRef, event.fromRow ?? null);
9835
- this.selectPanel(PanelTypes.STYLES);
9915
+ this.selectPanelForElement();
9836
9916
  this._emitElementSelect(event.node, event.componentRef);
9837
9917
  }
9838
9918
  /**
@@ -9859,7 +9939,7 @@ class BuilderComponent {
9859
9939
  return;
9860
9940
  this.selectedItem = node;
9861
9941
  this.overlayService.setSelect(ref.element, node.type, this.activeMode, node, ref.componentRef);
9862
- this.selectPanel(PanelTypes.STYLES);
9942
+ this.selectPanelForElement();
9863
9943
  this._emitElementSelect(node, ref.componentRef);
9864
9944
  });
9865
9945
  }
@@ -9892,6 +9972,26 @@ class BuilderComponent {
9892
9972
  this.overlayService.clearSelect();
9893
9973
  this.refreshBreakpointCounts();
9894
9974
  }
9975
+ /**
9976
+ * The canvas Dialog the selection sat in closed (its Close, the backdrop, the eye): the selection moves to its
9977
+ * opener — the menu button that opened the drawer — or, when nothing opens it, off the canvas. Never left on an
9978
+ * element that is leaving the DOM (its chrome drew at the viewport's corner), and never on the dialog itself,
9979
+ * which selecting would open again.
9980
+ */
9981
+ selectAfterDialogClosed(dialogId) {
9982
+ const opener = dialogId ? findDialogOpener([this.layout, this.globalElements], dialogId) : null;
9983
+ const ref = opener ? this.overlayService.getNodeRef(opener) : null;
9984
+ if (opener && ref?.element?.isConnected) {
9985
+ this.selectedItem = opener;
9986
+ this.overlayService.setSelect(ref.element, opener.type, this.activeMode, opener, ref.componentRef);
9987
+ this._emitElementSelect(opener, ref.componentRef);
9988
+ return;
9989
+ }
9990
+ this.selectedItem = null;
9991
+ this.selectedPath = [];
9992
+ this.overlayService.clearSelect();
9993
+ this.panelEventService.emit(PanelEventTypes.ELEMENT_DESELECT, null);
9994
+ }
9895
9995
  /** Breadcrumb crumb clicked — select that ancestor. Mirrors a layer-tree click. */
9896
9996
  onCrumbSelect(node) {
9897
9997
  this._withNodeRef(node, ref => {
@@ -9930,7 +10030,7 @@ class BuilderComponent {
9930
10030
  this.overlayService.setSelect(ref.element, node.type, this.activeMode, node, ref.componentRef);
9931
10031
  this._emitElementSelect(node, ref.componentRef);
9932
10032
  });
9933
- this.selectPanel(PanelTypes.STYLES);
10033
+ this.selectPanelForElement();
9934
10034
  }
9935
10035
  onTreeNodeAction(event) {
9936
10036
  const { action, node } = event;
@@ -10099,7 +10199,7 @@ class BuilderComponent {
10099
10199
  }
10100
10200
  case ToolbarAction.EditPreset: {
10101
10201
  this.panelEventService.emit(PanelEventTypes.PRESET_EDIT, node.stylePresetId);
10102
- this.panelEventService.emit(this.isPanelOpen ? PanelEventTypes.PANEL_SELECTED : PanelEventTypes.PANEL_OPEN, PanelTypes.STYLES);
10202
+ this.selectPanel(PanelTypes.STYLES);
10103
10203
  break;
10104
10204
  }
10105
10205
  case ToolbarAction.EditInteractionPreset: {
@@ -10107,7 +10207,7 @@ class BuilderComponent {
10107
10207
  if (node.interactionPresetId) {
10108
10208
  this.panelEventService.emit(PanelEventTypes.INTERACTION_PRESET_EDIT, node.interactionPresetId);
10109
10209
  }
10110
- this.panelEventService.emit(this.isPanelOpen ? PanelEventTypes.PANEL_SELECTED : PanelEventTypes.PANEL_OPEN, PanelTypes.INTERACTIONS);
10210
+ this.selectPanel(PanelTypes.INTERACTIONS);
10111
10211
  // Expand the specific interaction the badge represents so the panel opens "on the right
10112
10212
  // interaction active". Emitted AFTER PANEL_OPEN so the panel is showing this node.
10113
10213
  if (event.interaction?.id) {
@@ -10514,6 +10614,10 @@ class BuilderComponent {
10514
10614
  });
10515
10615
  return;
10516
10616
  }
10617
+ if (panelEvent.type === PanelEventTypes.DIALOG_CLOSED_OVER_SELECTION) {
10618
+ this.selectAfterDialogClosed(String(panelEvent.data?.dialogId ?? ''));
10619
+ return;
10620
+ }
10517
10621
  // Every style or structural edit — including a breakpoint override — emits LAYOUT_CHANGED.
10518
10622
  if (panelEvent.type === PanelEventTypes.LAYOUT_CHANGED) {
10519
10623
  this.refreshBreakpointCounts();
@@ -10525,8 +10629,12 @@ class BuilderComponent {
10525
10629
  this.isPanelOpen = false;
10526
10630
  return;
10527
10631
  }
10528
- if (panelEvent.type === PanelEventTypes.PANEL_OPEN) {
10632
+ // Whoever opened a panel (a tab, a deep link, a toolbar badge), the builder knows which one is showing —
10633
+ // the next selection keeps it when it is an element panel.
10634
+ if (panelEvent.type === PanelEventTypes.PANEL_OPEN || panelEvent.type === PanelEventTypes.PANEL_SELECTED) {
10529
10635
  this.isPanelOpen = true;
10636
+ if (typeof panelEvent.data === 'string')
10637
+ this.activePanel = panelEvent.data;
10530
10638
  }
10531
10639
  }
10532
10640
  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 }); }
@@ -11181,12 +11289,13 @@ class BuilderDialogComponent {
11181
11289
  const allowed = ['center', 'top', 'bottom', 'start', 'end'];
11182
11290
  return allowed.indexOf(String(this.placement)) >= 0 ? String(this.placement) : 'center';
11183
11291
  }
11184
- constructor(dragEngine, instanceRegistry, overlayService, cdr, designState) {
11292
+ constructor(dragEngine, instanceRegistry, overlayService, cdr, designState, panelEvents) {
11185
11293
  this.dragEngine = dragEngine;
11186
11294
  this.instanceRegistry = instanceRegistry;
11187
11295
  this.overlayService = overlayService;
11188
11296
  this.cdr = cdr;
11189
11297
  this.designState = designState;
11298
+ this.panelEvents = panelEvents;
11190
11299
  this.children = [];
11191
11300
  this.nodeId = '';
11192
11301
  this.dropListId = '';
@@ -11198,27 +11307,74 @@ class BuilderDialogComponent {
11198
11307
  this.childMouseLeave = new EventEmitter();
11199
11308
  /** Canvas-only visibility. Never serialised; the live site always starts closed. */
11200
11309
  this.designOpen = false;
11310
+ /** The exit is playing: still rendered, already closed for every action and the eye. */
11311
+ this.closing = false;
11201
11312
  /** Opened by an action on the canvas (the canvas is a live runtime): stays until something closes it. */
11202
11313
  this.runtimeOpen = false;
11203
11314
  /** Closed by its X / backdrop while the selection or the switcher still shows it: hidden until either changes. */
11204
11315
  this.dismissed = false;
11205
11316
  }
11317
+ /** Open on the canvas right now (an exit that is playing counts as closed). */
11318
+ get isOpen() {
11319
+ return this.designOpen && !this.closing;
11320
+ }
11206
11321
  /** Shown when an action opened it, or the canvas design state shows it (selection inside / switched open). */
11207
11322
  recompute() {
11208
11323
  const shown = !this.dismissed && (this.runtimeOpen || !!this.designState?.isShown(this.nodeId));
11209
- if (shown === this.designOpen)
11324
+ if (shown) {
11325
+ if (this.closing) {
11326
+ this.closing = false;
11327
+ cancelExit(this.stageRef?.nativeElement);
11328
+ }
11329
+ if (this.designOpen)
11330
+ return;
11331
+ this.designOpen = true;
11332
+ this.cdr.markForCheck();
11210
11333
  return;
11211
- this.designOpen = shown;
11334
+ }
11335
+ if (this.designOpen && !this.closing)
11336
+ this.playExit();
11337
+ }
11338
+ /** Close with the look's exit motion; the stage leaves the DOM when it has played (now, under reduced motion). */
11339
+ playExit() {
11340
+ const stage = this.stageRef?.nativeElement;
11341
+ this.closing = true;
11342
+ if (!stage) {
11343
+ this.finishExit();
11344
+ return;
11345
+ }
11346
+ void runExitMotion(stage, { animate: motionAllowed() }).then(done => { if (done && this.closing)
11347
+ this.finishExit(); });
11348
+ }
11349
+ finishExit() {
11350
+ this.closing = false;
11351
+ this.designOpen = false;
11212
11352
  this.cdr.markForCheck();
11213
11353
  }
11214
11354
  openRuntime() { this.runtimeOpen = true; this.dismissed = false; this.recompute(); }
11215
11355
  closeRuntime() {
11356
+ // A selection INSIDE the dialog (its Close, picked to style it) would be left on an element about to leave
11357
+ // the DOM — the selection chrome drawn at the viewport's corner (Doron's live test). It moves to the
11358
+ // dialog's opener (the menu button), else off the canvas; checked BEFORE the reveal is cleared.
11359
+ const handOff = this.selectionInside();
11216
11360
  this.runtimeOpen = false;
11217
- // Unpin it first (a free-standing dialog the selection opened, A4) — that emits and clears
11218
- // `dismissed`; then hide it even while the selection still sits inside a revealed part (R2).
11361
+ // Unpin it and clear its reveal (a free-standing dialog the selection opened, A4; a part the selection sat
11362
+ // in, R2) — that emits and clears `dismissed`; then hide it even while the selection is still inside.
11219
11363
  this.designState?.dismiss(this.nodeId);
11220
11364
  this.dismissed = true;
11221
11365
  this.recompute();
11366
+ if (handOff)
11367
+ this.panelEvents?.emit(PanelEventTypes.DIALOG_CLOSED_OVER_SELECTION, { dialogId: this.nodeId });
11368
+ }
11369
+ /** True when the canvas selection is this dialog or a node inside it. */
11370
+ selectionInside() {
11371
+ const sel = this.overlayService.getCurrentSelection?.();
11372
+ if (!sel)
11373
+ return false;
11374
+ if (sel.node?.id === this.nodeId)
11375
+ return true;
11376
+ const stage = this.stageRef?.nativeElement;
11377
+ return !!stage && !!sel.element && stage.contains(sel.element);
11222
11378
  }
11223
11379
  ngOnInit() {
11224
11380
  // Board A4: "Closed later? Click it [in the tree] and it reopens." Selection IS the
@@ -11256,8 +11412,8 @@ class BuilderDialogComponent {
11256
11412
  this.instanceRegistry.register(this.nodeId, {
11257
11413
  open: () => this.openRuntime(),
11258
11414
  close: () => this.closeRuntime(),
11259
- toggle: () => (this.designOpen ? this.closeRuntime() : this.openRuntime()),
11260
- isOpen: () => this.designOpen,
11415
+ toggle: () => (this.isOpen ? this.closeRuntime() : this.openRuntime()),
11416
+ isOpen: () => this.isOpen,
11261
11417
  });
11262
11418
  }
11263
11419
  ngOnDestroy() {
@@ -11288,9 +11444,9 @@ class BuilderDialogComponent {
11288
11444
  onDrop(event) {
11289
11445
  this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
11290
11446
  }
11291
- 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 }], target: i0.ɵɵFactoryTarget.Component }); }
11292
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: BuilderDialogComponent, isStandalone: false, selector: "app-builder-dialog", inputs: { children: "children", nodeId: "nodeId", dropListId: "dropListId", closeOnBackdrop: "closeOnBackdrop", showCloseButton: "showCloseButton", placement: "placement" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, ngImport: i0, template: `
11293
- <div *ngIf="designOpen" class="dialog-stage" [attr.data-placement]="placementValue"
11447
+ 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 }], target: i0.ɵɵFactoryTarget.Component }); }
11448
+ 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: `
11449
+ <div *ngIf="designOpen" #stage class="dialog-stage" [attr.data-placement]="placementValue"
11294
11450
  (click)="onStageClick($event)">
11295
11451
  <div [ngClass]="['iox-node-' + nodeId, 'iox-outer-' + nodeId]"
11296
11452
  class="dialog-surface"
@@ -11321,12 +11477,12 @@ class BuilderDialogComponent {
11321
11477
  </ng-container>
11322
11478
  </div>
11323
11479
  </div>
11324
- `, isInline: true, 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}:where(.dialog-surface){box-sizing:border-box;max-width:100%;max-height:100%;overflow:auto;contain:layout}.dialog-close{position:absolute;inset-inline-end:10px;top:10px;width:26px;height:26px;border:none;border-radius:999px;background:#0f172a0f;color:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}:host(.iox-es) .dialog-close{border-radius:var(--iox-radius-pill);background:var(--iox-surface-2);color:var(--iox-text)}.dialog-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:90px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.dialog-placeholder i{font-size:18px}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId", "ioxPhrasing"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
11480
+ `, isInline: true, 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"], 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"] }] }); }
11325
11481
  }
11326
11482
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, decorators: [{
11327
11483
  type: Component,
11328
11484
  args: [{ selector: 'app-builder-dialog', template: `
11329
- <div *ngIf="designOpen" class="dialog-stage" [attr.data-placement]="placementValue"
11485
+ <div *ngIf="designOpen" #stage class="dialog-stage" [attr.data-placement]="placementValue"
11330
11486
  (click)="onStageClick($event)">
11331
11487
  <div [ngClass]="['iox-node-' + nodeId, 'iox-outer-' + nodeId]"
11332
11488
  class="dialog-surface"
@@ -11357,9 +11513,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
11357
11513
  </ng-container>
11358
11514
  </div>
11359
11515
  </div>
11360
- `, 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}: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"] }]
11516
+ `, 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"] }]
11361
11517
  }], ctorParameters: () => [{ type: DragEngineService }, { type: i1$1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: i0.ChangeDetectorRef }, { type: CanvasDesignStateService, decorators: [{
11362
11518
  type: Optional
11519
+ }] }, { type: PanelEventService, decorators: [{
11520
+ type: Optional
11363
11521
  }] }], propDecorators: { children: [{
11364
11522
  type: Input
11365
11523
  }], nodeId: [{
@@ -11378,6 +11536,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
11378
11536
  type: Output
11379
11537
  }], childMouseLeave: [{
11380
11538
  type: Output
11539
+ }], stageRef: [{
11540
+ type: ViewChild,
11541
+ args: ['stage']
11381
11542
  }] } });
11382
11543
 
11383
11544
  /** Swiper's event → the interaction trigger it replays (componentEvents). */