@vectoriox/iox-builder 11.15.1 → 11.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/vectoriox-iox-builder.mjs +1242 -1111
- package/fesm2022/vectoriox-iox-builder.mjs.map +1 -1
- package/index.d.ts +65 -12
- package/package.json +2 -2
|
@@ -17,9 +17,9 @@ import { TooltipModule } from 'primeng/tooltip';
|
|
|
17
17
|
import * as i20 from 'primeng/dialog';
|
|
18
18
|
import { DialogModule } from 'primeng/dialog';
|
|
19
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,
|
|
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, PROGRESS_ATTRIBUTE, interactionMarkerAttributes, cascadeChain, attachScrollProgress, ContextStore, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, ComponentInstanceRegistryService, syncInteractionMarkers, entranceStartsHidden, isActionEnabled, restoreCountUp, SCROLL_PROGRESS_TRIGGER, armInteractionCounts, splitPressFeedback, isHoverPointer, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, COUNT_UP_ACTION, motionAllowed, settleInteraction, settleModeFor, playInteraction, resolveInteractionEasing, entranceDelay, repeatedRowIndex, entrancePreStateStyle, buildInteractionKeyframes, showToast, 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
|
-
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,
|
|
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, MarqueeTrait, MarqueeSpeed, MarqueeDirection, DisclosureRole, isExclusiveTrigger, onCompositeItemState, isCompositeItemOn, runCompositeItemAction, shouldScrollToItem, rowIndexOf as rowIndexOf$1, slideIndexForRow, isMarqueeRoot, compositeScopeId, compositeRowKeys, isExclusiveRoot, ExclusiveRuntime, exclusiveDefaultOf, exclusiveLabelOf, exclusiveBehaviourOf, exclusiveKindOf, compositeKindOfRole, AccordionRuntime, isToggleKind, MarqueeRuntime, closestAccordionItem, isAnyCompositeRoot, SceneRole, isExclusiveList, buildCardRecipe, buildBadge, buildChip, buildAccordionRecipe, buildDisclosureRecipe, buildTabsRecipe, buildSegmentedRecipe, buildLightboxRecipe, buildMarqueeRecipe, buildCounterRecipe, buildReadingBarRecipe, 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
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';
|
|
@@ -1338,6 +1338,9 @@ const ACTION_TYPE_OPTIONS = [
|
|
|
1338
1338
|
{ value: 'clipReveal', label: 'Reveal' },
|
|
1339
1339
|
{ value: 'zoomOut', label: 'Zoom settle' },
|
|
1340
1340
|
{ value: 'revealText', label: 'Reveal text' },
|
|
1341
|
+
{ value: 'countUp', label: 'Count up' },
|
|
1342
|
+
{ value: 'parallax', label: 'Depth (parallax)' },
|
|
1343
|
+
{ value: 'progressBar', label: 'Fill as read' },
|
|
1341
1344
|
{ value: 'show', label: 'Show' },
|
|
1342
1345
|
{ value: 'hide', label: 'Hide' },
|
|
1343
1346
|
{ value: 'toggleVisibility', label: 'Toggle visibility' },
|
|
@@ -1948,6 +1951,18 @@ function buildCompositeBehaviourTraits() {
|
|
|
1948
1951
|
new TraitConfig(TabsBehaviourTrait.Label, 'Accessible name', TraitInputType.Text),
|
|
1949
1952
|
];
|
|
1950
1953
|
}
|
|
1954
|
+
/**
|
|
1955
|
+
* A Marquee band's own settings (premium components M6, N6) — on the band's root Container. No default: a plain Container
|
|
1956
|
+
* saves none (the recipe writes them); edited in Bindings ▸ Marquee, never as generic rows.
|
|
1957
|
+
*/
|
|
1958
|
+
function buildMarqueeTraits() {
|
|
1959
|
+
return [
|
|
1960
|
+
new TraitConfig(MarqueeTrait.Speed, 'Speed', TraitInputType.Select, Object.values(MarqueeSpeed).map(v => ({ label: v, value: v }))),
|
|
1961
|
+
new TraitConfig(MarqueeTrait.Direction, 'Direction', TraitInputType.Select, Object.values(MarqueeDirection).map(v => ({ label: v, value: v }))),
|
|
1962
|
+
new TraitConfig(MarqueeTrait.Fade, 'Fade the edges', TraitInputType.Checkbox),
|
|
1963
|
+
new TraitConfig(MarqueeTrait.PauseOnHover, 'Pause on hover', TraitInputType.Checkbox),
|
|
1964
|
+
];
|
|
1965
|
+
}
|
|
1951
1966
|
/**
|
|
1952
1967
|
* How a header behaves as the page scrolls (premium components T2, M4) — on the Navigation bar's root. No default: a
|
|
1953
1968
|
* plain Container saves none; edited in the Navigation bar's Header section, never as generic rows.
|
|
@@ -4111,1092 +4126,1277 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
4111
4126
|
// Re-exported from the shared iox-ui package so iox-builder consumers
|
|
4112
4127
|
// don't need to import from two different packages.
|
|
4113
4128
|
|
|
4114
|
-
/** What the canvas never writes inline (a transform inside the scaled page breaks Lenis). */
|
|
4115
|
-
const CANVAS_SKIPPED_PROPS = new Set(['transform']);
|
|
4116
4129
|
/**
|
|
4117
|
-
*
|
|
4118
|
-
*
|
|
4130
|
+
* Parts that are HIDDEN AT REST (complex-components R2, board 3): on the live site they are not on
|
|
4131
|
+
* screen until something opens them, so on the canvas they show only while selected into or switched
|
|
4132
|
+
* open ("Showing on canvas"). The layer tree marks them with the eye-slash glyph. A Dialog (the mobile
|
|
4133
|
+
* menu's drawer, a consent banner) by its type; an Accordion's PANEL by its role (CC2 — closed until its
|
|
4134
|
+
* item opens); a Disclosure's panel and its "Read less" word (CC2.1 — shown only while it is open).
|
|
4135
|
+
*/
|
|
4136
|
+
const HIDDEN_AT_REST_TYPES = ['Dialog', 'Popover'];
|
|
4137
|
+
const HIDDEN_AT_REST_ROLES = [AccordionRole.Panel, DisclosureRole.Panel, DisclosureRole.Less];
|
|
4138
|
+
/** A READING BAR (premium M6, N7): empty until the scroll fills it, so on the canvas the selection holds it full. */
|
|
4139
|
+
function isReadingBar(node) {
|
|
4140
|
+
return PROGRESS_ATTRIBUTE in interactionMarkerAttributes(node);
|
|
4141
|
+
}
|
|
4142
|
+
function isHiddenAtRest(node) {
|
|
4143
|
+
return (!!node?.type && HIDDEN_AT_REST_TYPES.includes(node.type)) || (!!node?.role && HIDDEN_AT_REST_ROLES.includes(node.role)) || isReadingBar(node);
|
|
4144
|
+
}
|
|
4145
|
+
/**
|
|
4146
|
+
* How the selection shows a node hidden at rest — the two approved rules, by what the node IS:
|
|
4147
|
+
* - a PART of a composite (it carries a `role`: the mobile menu's drawer) follows R2 (board 3): the
|
|
4148
|
+
* selection is the reveal, and moving it out of the part hides it again;
|
|
4149
|
+
* - a free-standing overlay (a plain Dialog) follows board A4: selecting it opens it, and it stays open
|
|
4150
|
+
* while the author works elsewhere, until its X, its backdrop or the eye closes it.
|
|
4151
|
+
*/
|
|
4152
|
+
var SelectionReveal;
|
|
4153
|
+
(function (SelectionReveal) {
|
|
4154
|
+
SelectionReveal["WhileSelected"] = "while-selected";
|
|
4155
|
+
SelectionReveal["UntilClosed"] = "until-closed";
|
|
4156
|
+
})(SelectionReveal || (SelectionReveal = {}));
|
|
4157
|
+
/**
|
|
4158
|
+
* Parts hidden at rest that live in a composite's TOGGLE, not in its panel (CC2.1 follow-up, approved 2026-10-21): a
|
|
4159
|
+
* click on one on the canvas toggles the composite live — selecting it never forces the reveal, so "Read less"
|
|
4160
|
+
* closes the disclosure it was clicked in. Only a part inside a PANEL is revealed by the selection.
|
|
4161
|
+
*/
|
|
4162
|
+
const TOGGLE_PART_ROLES = [DisclosureRole.Less];
|
|
4163
|
+
/**
|
|
4164
|
+
* Parts the selection REVEALS though they are not hidden at rest (CC4a): a tab PANEL — one is always on, so the layer
|
|
4165
|
+
* tree draws it no eye-slash (board 3), but selecting anything inside an inactive one shows its tab while selected.
|
|
4166
|
+
*/
|
|
4167
|
+
const REVEALED_WHILE_SELECTED_ROLES = [TabsRole.Panel];
|
|
4168
|
+
function selectionRevealOf(node) {
|
|
4169
|
+
if ((!!node?.role && REVEALED_WHILE_SELECTED_ROLES.includes(node.role)) || isReadingBar(node))
|
|
4170
|
+
return SelectionReveal.WhileSelected;
|
|
4171
|
+
if (!isHiddenAtRest(node) || (!!node?.role && TOGGLE_PART_ROLES.includes(node.role)))
|
|
4172
|
+
return null;
|
|
4173
|
+
return node?.role ? SelectionReveal.WhileSelected : SelectionReveal.UntilClosed;
|
|
4174
|
+
}
|
|
4175
|
+
/** Root → `target`, inclusive, across `roots` (page layout and globals), or [] when it is not there. */
|
|
4176
|
+
function pathToNode(roots, target) {
|
|
4177
|
+
for (const root of roots) {
|
|
4178
|
+
if (root === target)
|
|
4179
|
+
return [root];
|
|
4180
|
+
const below = pathToNode(root.children ?? [], target);
|
|
4181
|
+
if (below.length)
|
|
4182
|
+
return [root, ...below];
|
|
4183
|
+
}
|
|
4184
|
+
return [];
|
|
4185
|
+
}
|
|
4186
|
+
|
|
4187
|
+
const NAV_CONTEXT_ROLES = new Set([NavRole.Root, NavRole.Drawer, NavRole.Bar]);
|
|
4188
|
+
function findRole(nodes, role) {
|
|
4189
|
+
for (const n of nodes) {
|
|
4190
|
+
if (n.role === role)
|
|
4191
|
+
return n;
|
|
4192
|
+
const below = findRole(n.children ?? [], role);
|
|
4193
|
+
if (below)
|
|
4194
|
+
return below;
|
|
4195
|
+
}
|
|
4196
|
+
return null;
|
|
4197
|
+
}
|
|
4198
|
+
/** True when the selection (root → selected) is the Navigation bar, its drawer, its bottom bar, or anything inside them. */
|
|
4199
|
+
function inPhoneMenuContext(path) {
|
|
4200
|
+
return path.some(n => !!n.role && NAV_CONTEXT_ROLES.has(n.role));
|
|
4201
|
+
}
|
|
4202
|
+
/**
|
|
4203
|
+
* The phone menu a selection belongs to, or null when the selection is outside a navigation or the site has no drawer
|
|
4204
|
+
* (no phone shape yet, or the Bottom bar shape). The drawer is looked up in the selection's own Navigation bar first,
|
|
4205
|
+
* then in the globals (the Both shape makes it a global of its own).
|
|
4206
|
+
*/
|
|
4207
|
+
function phoneMenuOf(path, globals = []) {
|
|
4208
|
+
if (!inPhoneMenuContext(path))
|
|
4209
|
+
return null;
|
|
4210
|
+
const root = path.find(n => n.role === NavRole.Root) ?? null;
|
|
4211
|
+
const scopes = root ? [root, ...globals] : [...globals];
|
|
4212
|
+
const drawer = path.find(n => n.role === NavRole.Drawer) ?? findRole(scopes, NavRole.Drawer);
|
|
4213
|
+
if (!drawer)
|
|
4214
|
+
return null;
|
|
4215
|
+
const menuButton = findRole(scopes, NavRole.MenuButton);
|
|
4216
|
+
const at = navShapeBreakpoint(scopes);
|
|
4217
|
+
return { root, drawer, menuButton, at };
|
|
4218
|
+
}
|
|
4219
|
+
/** True when the canvas, at `breakpoint`, shows the phone menu that takes over at `at` (that width or narrower). */
|
|
4220
|
+
function phoneMenuShowsAt(at, breakpoint) {
|
|
4221
|
+
return cascadeChain(breakpoint).includes(at);
|
|
4222
|
+
}
|
|
4223
|
+
const OPENER_TRIGGER = 'click';
|
|
4224
|
+
/** The dialog actions a phone menu's opener runs (generation-core stamps Toggle; CC1 menus still hold Open). */
|
|
4225
|
+
var PhoneMenuOpenerAction;
|
|
4226
|
+
(function (PhoneMenuOpenerAction) {
|
|
4227
|
+
PhoneMenuOpenerAction["Open"] = "openDialog";
|
|
4228
|
+
PhoneMenuOpenerAction["Toggle"] = "toggleDialog";
|
|
4229
|
+
})(PhoneMenuOpenerAction || (PhoneMenuOpenerAction = {}));
|
|
4230
|
+
const OPENER_ACTIONS = new Set(Object.values(PhoneMenuOpenerAction));
|
|
4231
|
+
/** True when `interaction` is the phone menu's own opener: the menu button's (or More's) click opening the drawer. */
|
|
4232
|
+
function isPhoneMenuOpener(node, interaction, drawerId) {
|
|
4233
|
+
if (!drawerId || (node?.role !== NavRole.MenuButton && node?.role !== NavRole.More))
|
|
4234
|
+
return false;
|
|
4235
|
+
const actions = interaction?.actions ?? [];
|
|
4236
|
+
return interaction?.trigger === OPENER_TRIGGER && actions.length === 1
|
|
4237
|
+
&& OPENER_ACTIONS.has(actions[0].type ?? '') && actions[0].target === drawerId;
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
/** What a hidden-at-rest part shows on the canvas when the author picks it in "Showing on canvas". */
|
|
4241
|
+
var CanvasShowState;
|
|
4242
|
+
(function (CanvasShowState) {
|
|
4243
|
+
CanvasShowState["Closed"] = "closed";
|
|
4244
|
+
CanvasShowState["Open"] = "open";
|
|
4245
|
+
})(CanvasShowState || (CanvasShowState = {}));
|
|
4246
|
+
const EMPTY_SNAPSHOT = () => ({
|
|
4247
|
+
revealed: new Set(), selectedTriggers: new Set(), pinned: new Set(), triggeredBySelection: new Set(), shown: new Map(), items: new Map(),
|
|
4248
|
+
runtime: new Set(), menu: null, tooltips: new Set(), toast: null,
|
|
4249
|
+
});
|
|
4250
|
+
/**
|
|
4251
|
+
* The canvas's DESIGN state (complex-components R2; composite plan §5a's CompositeDesignStateService,
|
|
4252
|
+
* started here for the navigation and reused by Accordion in CC2): which parts that are hidden at rest
|
|
4253
|
+
* — a closed drawer today, a collapsed panel or an inactive tab next — the canvas shows right now.
|
|
4119
4254
|
*
|
|
4120
|
-
*
|
|
4121
|
-
*
|
|
4255
|
+
* Two sources, the Dialog's "selection is the reveal" and the Form's switcher, generalised:
|
|
4256
|
+
* - **the selection** — selecting anything inside a hidden part (tree, breadcrumb, an Items row,
|
|
4257
|
+
* SELECT_NODE) shows it; deselecting hides it again (`setSelectionPath`);
|
|
4258
|
+
* - **"Showing on canvas"** — the Bindings panel's switcher, for the one case a selection cannot express:
|
|
4259
|
+
* the page with the menu open while a part OUTSIDE it is selected (`setShown`).
|
|
4260
|
+
*
|
|
4261
|
+
* Canvas-only: never saved, never a history step. Also carries the canvas page's path, so a link to the
|
|
4262
|
+
* page being edited renders as the current page (N2) — the same decision the live site makes.
|
|
4122
4263
|
*/
|
|
4123
|
-
class
|
|
4124
|
-
constructor(
|
|
4125
|
-
this.
|
|
4126
|
-
this.
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
this.
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
* element's own attach() fires.
|
|
4142
|
-
*/
|
|
4143
|
-
this.pendingPreStates = new Map();
|
|
4144
|
-
/** Action types that animate an element from hidden → visible / visible → hidden — the SHARED sets
|
|
4145
|
-
* (@vectoriox/iox-ui), the same ones the live site's runtime reads. */
|
|
4146
|
-
this.ENTRANCE_TYPES = ENTRANCE_ACTION_TYPES;
|
|
4147
|
-
this.EXIT_TYPES = EXIT_ACTION_TYPES;
|
|
4148
|
-
this.SWIPER_ACTIONS = new Set([
|
|
4149
|
-
'swiperNext', 'swiperPrev', 'swiperGoToSlide',
|
|
4150
|
-
'swiperAutoplayStart', 'swiperAutoplayStop', 'swiperAutoplayPause',
|
|
4151
|
-
]);
|
|
4152
|
-
this.instanceRegistry = inject(ComponentInstanceRegistryService);
|
|
4153
|
-
this.ngZone = inject(NgZone);
|
|
4264
|
+
class CanvasDesignStateService {
|
|
4265
|
+
constructor() {
|
|
4266
|
+
this._state$ = new BehaviorSubject(EMPTY_SNAPSHOT());
|
|
4267
|
+
this.state$ = this._state$.asObservable();
|
|
4268
|
+
this._pagePath$ = new BehaviorSubject(null);
|
|
4269
|
+
/** The app path of the page on the canvas (`/work`), or null when unknown. */
|
|
4270
|
+
this.pagePath$ = this._pagePath$.asObservable();
|
|
4271
|
+
}
|
|
4272
|
+
get snapshot() {
|
|
4273
|
+
return this._state$.value;
|
|
4274
|
+
}
|
|
4275
|
+
get pagePath() {
|
|
4276
|
+
return this._pagePath$.value;
|
|
4277
|
+
}
|
|
4278
|
+
setPagePath(path) {
|
|
4279
|
+
const next = path ?? null;
|
|
4280
|
+
if (next !== this._pagePath$.value)
|
|
4281
|
+
this._pagePath$.next(next);
|
|
4154
4282
|
}
|
|
4155
4283
|
/**
|
|
4156
|
-
*
|
|
4157
|
-
*
|
|
4158
|
-
*
|
|
4159
|
-
* The caller is responsible for calling markForCheck() / refreshSelect() inside
|
|
4160
|
-
* the zone after this returns.
|
|
4284
|
+
* The selection moved. The hidden-at-rest PARTS on its path are revealed and every other part is not
|
|
4285
|
+
* (R2); a free-standing overlay on the path is pinned open until it is closed (A4). `globals` lets it find the
|
|
4286
|
+
* phone menu the selection belongs to when its drawer lives outside the path (the Both shape).
|
|
4161
4287
|
*/
|
|
4162
|
-
|
|
4163
|
-
|
|
4288
|
+
setSelectionPath(path, globals = []) {
|
|
4289
|
+
const ids = (reveal) => path.filter(n => selectionRevealOf(n) === reveal).map(n => n.id).filter((id) => !!id);
|
|
4290
|
+
const revealed = new Set(ids(SelectionReveal.WhileSelected));
|
|
4291
|
+
const selectedTriggers = new Set(path.filter(n => isExclusiveTrigger(n)).map(n => n.id).filter((id) => !!id));
|
|
4292
|
+
const triggeredBySelection = new Set(path.map(displayTriggerOf).filter((id) => !!id));
|
|
4293
|
+
const { revealed: was, pinned: held, selectedTriggers: wasTriggers, triggeredBySelection: wasTriggered } = this._state$.value;
|
|
4294
|
+
const pinned = new Set([...held, ...ids(SelectionReveal.UntilClosed)]);
|
|
4295
|
+
const menu = phoneMenuOf(path, globals);
|
|
4296
|
+
if (sameSet(revealed, was) && sameSet(pinned, held) && sameSet(selectedTriggers, wasTriggers) && sameSet(triggeredBySelection, wasTriggered)
|
|
4297
|
+
&& sameMenu(menu, this._state$.value.menu))
|
|
4298
|
+
return;
|
|
4299
|
+
this._state$.next({ ...this._state$.value, revealed, pinned, selectedTriggers, triggeredBySelection, menu });
|
|
4164
4300
|
}
|
|
4165
|
-
/**
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
* which animates every repeated row). See RenderDirective. */
|
|
4170
|
-
attachTo(node, element) { this.runOutside(() => this._attach(node, element)); }
|
|
4171
|
-
_attach(node, elementOverride) {
|
|
4172
|
-
const element = elementOverride ?? this.overlayService.getNodeRef(node)?.element;
|
|
4173
|
-
if (!element)
|
|
4301
|
+
/** Closed on the canvas (its X, its backdrop, the eye): no longer pinned, switched open or revealed by the
|
|
4302
|
+
* selection — selecting into it again reveals it again. */
|
|
4303
|
+
dismiss(nodeId) {
|
|
4304
|
+
if (!nodeId)
|
|
4174
4305
|
return;
|
|
4175
|
-
const
|
|
4176
|
-
|
|
4177
|
-
// Pending entries are only added when appropriate (see loop below), so always consume.
|
|
4178
|
-
if (node.id) {
|
|
4179
|
-
const pending = this.pendingPreStates.get(node.id);
|
|
4180
|
-
if (pending) {
|
|
4181
|
-
for (const action of pending) {
|
|
4182
|
-
this.applyPreState(element, action);
|
|
4183
|
-
}
|
|
4184
|
-
this.pendingPreStates.delete(node.id);
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
if (!node.interactions?.length)
|
|
4306
|
+
const { pinned, shown, revealed, runtime } = this._state$.value;
|
|
4307
|
+
if (!pinned.has(nodeId) && !shown.has(nodeId) && !revealed.has(nodeId) && !runtime.has(nodeId))
|
|
4188
4308
|
return;
|
|
4189
|
-
const
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
if (!skipPreState) {
|
|
4199
|
-
for (const action of ix.actions) {
|
|
4200
|
-
// A disabled action must not pre-state: it would hide the element with nothing
|
|
4201
|
-
// left to reveal it.
|
|
4202
|
-
if (!isActionEnabled(action))
|
|
4203
|
-
continue;
|
|
4204
|
-
if (this.ENTRANCE_TYPES.has(action.type)) {
|
|
4205
|
-
const target = this.resolveTarget(node, action, element);
|
|
4206
|
-
if (target) {
|
|
4207
|
-
this.applyPreState(target, action);
|
|
4208
|
-
}
|
|
4209
|
-
else if (action.target && action.target !== 'self') {
|
|
4210
|
-
const list = this.pendingPreStates.get(action.target) ?? [];
|
|
4211
|
-
list.push(action);
|
|
4212
|
-
this.pendingPreStates.set(action.target, list);
|
|
4213
|
-
}
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
}
|
|
4217
|
-
const cleanup = this.attachInteraction(node, ix, element);
|
|
4218
|
-
cleanups.push({ node, interaction: ix, cleanup });
|
|
4219
|
-
}
|
|
4220
|
-
this.attached.set(node, cleanups);
|
|
4309
|
+
const nextPinned = new Set(pinned);
|
|
4310
|
+
nextPinned.delete(nodeId);
|
|
4311
|
+
const nextShown = new Map(shown);
|
|
4312
|
+
nextShown.delete(nodeId);
|
|
4313
|
+
const nextRevealed = new Set(revealed);
|
|
4314
|
+
nextRevealed.delete(nodeId);
|
|
4315
|
+
const nextRuntime = new Set(runtime);
|
|
4316
|
+
nextRuntime.delete(nodeId);
|
|
4317
|
+
this._state$.next({ ...this._state$.value, pinned: nextPinned, shown: nextShown, revealed: nextRevealed, runtime: nextRuntime });
|
|
4221
4318
|
}
|
|
4222
|
-
/**
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
const entries = this.attached.get(node);
|
|
4226
|
-
if (!entries)
|
|
4319
|
+
/** An action opened the overlay on the canvas (a click on the menu button): shown until something closes it. */
|
|
4320
|
+
markOpened(nodeId) {
|
|
4321
|
+
if (!nodeId || this._state$.value.runtime.has(nodeId))
|
|
4227
4322
|
return;
|
|
4228
|
-
|
|
4229
|
-
entry.cleanup();
|
|
4230
|
-
}
|
|
4231
|
-
this.attached.delete(node);
|
|
4232
|
-
// Reset toggle state so re-attach starts from the primary actions.
|
|
4233
|
-
for (const entry of entries) {
|
|
4234
|
-
if (entry.interaction.reverseActions?.length) {
|
|
4235
|
-
this.toggleStates.delete(entry.interaction.id);
|
|
4236
|
-
}
|
|
4237
|
-
}
|
|
4238
|
-
// Cancel Web Animations and strip any inline pre-state styles that
|
|
4239
|
-
// were applied by applyPreState() so the element returns to its natural CSS state.
|
|
4240
|
-
const ref = this.overlayService.getNodeRef(node);
|
|
4241
|
-
if (ref) {
|
|
4242
|
-
ref.element.getAnimations().forEach(a => a.cancel());
|
|
4243
|
-
this.clearInlineAnimationStyles(ref.element);
|
|
4244
|
-
}
|
|
4245
|
-
// Also clean up pre-state on any target elements this node was animating.
|
|
4246
|
-
if (node.interactions) {
|
|
4247
|
-
for (const ix of node.interactions) {
|
|
4248
|
-
for (const action of ix.actions) {
|
|
4249
|
-
// Remove any pending pre-states for unregistered targets.
|
|
4250
|
-
if (action.target && action.target !== 'self') {
|
|
4251
|
-
const pending = this.pendingPreStates.get(action.target);
|
|
4252
|
-
if (pending) {
|
|
4253
|
-
const filtered = pending.filter(a => a !== action);
|
|
4254
|
-
if (filtered.length) {
|
|
4255
|
-
this.pendingPreStates.set(action.target, filtered);
|
|
4256
|
-
}
|
|
4257
|
-
else {
|
|
4258
|
-
this.pendingPreStates.delete(action.target);
|
|
4259
|
-
}
|
|
4260
|
-
}
|
|
4261
|
-
}
|
|
4262
|
-
// Clean up inline styles on already-registered target elements.
|
|
4263
|
-
const target = this.resolveTarget(node, action);
|
|
4264
|
-
if (target && this.preStatedElements.has(target)) {
|
|
4265
|
-
target.getAnimations().forEach(a => a.cancel());
|
|
4266
|
-
this.clearInlineAnimationStyles(target);
|
|
4267
|
-
this.preStatedElements.delete(target);
|
|
4268
|
-
}
|
|
4269
|
-
}
|
|
4270
|
-
}
|
|
4271
|
-
}
|
|
4323
|
+
this._state$.next({ ...this._state$.value, runtime: new Set([...this._state$.value.runtime, nodeId]) });
|
|
4272
4324
|
}
|
|
4273
|
-
/**
|
|
4274
|
-
|
|
4275
|
-
|
|
4325
|
+
/** The switcher's choice for one part (null = back to the saved default). */
|
|
4326
|
+
setShown(nodeId, state) {
|
|
4327
|
+
const shown = new Map(this._state$.value.shown);
|
|
4328
|
+
if (state && state !== CanvasShowState.Closed)
|
|
4329
|
+
shown.set(nodeId, state);
|
|
4330
|
+
else
|
|
4331
|
+
shown.delete(nodeId);
|
|
4332
|
+
this._state$.next({ ...this._state$.value, shown });
|
|
4276
4333
|
}
|
|
4277
|
-
/**
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
* Uses fill:'none' so the element returns to its natural state after the preview.
|
|
4281
|
-
*/
|
|
4282
|
-
replay(node) { this.runOutside(() => this._replay(node)); }
|
|
4283
|
-
_replay(node) {
|
|
4284
|
-
if (!node.interactions?.length)
|
|
4334
|
+
/** Tabs' "Showing on canvas": the item a root shows (null = whatever the canvas shows live). */
|
|
4335
|
+
setShownItem(rootId, key) {
|
|
4336
|
+
if ((this._state$.value.items.get(rootId) ?? null) === key)
|
|
4285
4337
|
return;
|
|
4286
|
-
const
|
|
4287
|
-
if (
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
}
|
|
4292
|
-
// In builder mode pre-state is never applied on attach, so apply it now
|
|
4293
|
-
// to give an accurate hidden→visible preview. It is cleaned up via
|
|
4294
|
-
// executeAction's anim.finished handler once the animation completes.
|
|
4295
|
-
for (const ix of node.interactions) {
|
|
4296
|
-
for (const action of ix.actions) {
|
|
4297
|
-
if (this.ENTRANCE_TYPES.has(action.type)) {
|
|
4298
|
-
const target = this.resolveTarget(node, action);
|
|
4299
|
-
if (target)
|
|
4300
|
-
this.applyPreState(target, action);
|
|
4301
|
-
}
|
|
4302
|
-
}
|
|
4303
|
-
}
|
|
4304
|
-
for (const ix of node.interactions) {
|
|
4305
|
-
this.runActions(node, ix.actions);
|
|
4306
|
-
}
|
|
4338
|
+
const items = new Map(this._state$.value.items);
|
|
4339
|
+
if (key)
|
|
4340
|
+
items.set(rootId, key);
|
|
4341
|
+
else
|
|
4342
|
+
items.delete(rootId);
|
|
4343
|
+
this._state$.next({ ...this._state$.value, items });
|
|
4307
4344
|
}
|
|
4308
|
-
/**
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4345
|
+
/** The item a root's "Showing on canvas" shows, or null. */
|
|
4346
|
+
shownItem(rootId) {
|
|
4347
|
+
return (rootId && this._state$.value.items.get(rootId)) || null;
|
|
4348
|
+
}
|
|
4349
|
+
/** The switcher's current choice for one part. */
|
|
4350
|
+
shownState(nodeId) {
|
|
4351
|
+
return (nodeId && this._state$.value.shown.get(nodeId)) || CanvasShowState.Closed;
|
|
4352
|
+
}
|
|
4353
|
+
/** True when the canvas shows the part: selected into, switched open, or opened by an action. */
|
|
4354
|
+
isShown(nodeId) {
|
|
4355
|
+
if (!nodeId)
|
|
4356
|
+
return false;
|
|
4357
|
+
const { revealed, pinned, shown, runtime } = this._state$.value;
|
|
4358
|
+
return revealed.has(nodeId) || pinned.has(nodeId) || runtime.has(nodeId) || shown.get(nodeId) === CanvasShowState.Open;
|
|
4359
|
+
}
|
|
4360
|
+
/** True when the canvas shows an item because the selection is styled "when it is on" (the switcher's hint). */
|
|
4361
|
+
isTriggeredBySelection(triggerId) {
|
|
4362
|
+
return !!triggerId && this._state$.value.triggeredBySelection.has(triggerId);
|
|
4363
|
+
}
|
|
4364
|
+
/** True when the part is shown only because the selection is inside it (the switcher's hint line). */
|
|
4365
|
+
isRevealedBySelection(nodeId) {
|
|
4366
|
+
return !!nodeId && this._state$.value.revealed.has(nodeId);
|
|
4367
|
+
}
|
|
4368
|
+
/** Hold a control's tooltip open on the canvas (true), or let it go. */
|
|
4369
|
+
setTooltipShown(nodeId, on) {
|
|
4370
|
+
if (!nodeId || this._state$.value.tooltips.has(nodeId) === on)
|
|
4323
4371
|
return;
|
|
4324
|
-
|
|
4372
|
+
const tooltips = new Set(this._state$.value.tooltips);
|
|
4373
|
+
if (on)
|
|
4374
|
+
tooltips.add(nodeId);
|
|
4375
|
+
else
|
|
4376
|
+
tooltips.delete(nodeId);
|
|
4377
|
+
this._state$.next({ ...this._state$.value, tooltips });
|
|
4325
4378
|
}
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
// animation (pageLoad on attach, viewportEnter on scroll-into-view, click/hover).
|
|
4330
|
-
// The former NG0103 crash was caused by (a) Swiper's ResizeObserver created inside
|
|
4331
|
-
// the Angular zone — fixed in SliderContainerComponent via runOutsideAngular — and
|
|
4332
|
-
// (b) the overlay's Resize/MutationObservers synchronously re-dirtying the view
|
|
4333
|
-
// during synchronize() — fixed by coalescing overlay updates through a RAF.
|
|
4334
|
-
// scrollProgress is still unimplemented and returns a no-op below.
|
|
4335
|
-
switch (ix.trigger) {
|
|
4336
|
-
case 'pageLoad':
|
|
4337
|
-
return this.attachPageLoad(node, ix, element);
|
|
4338
|
-
case 'pageEnter':
|
|
4339
|
-
// In the builder canvas there is no navigation, so preview a page-enter the same
|
|
4340
|
-
// way as page-load (pre-stated hidden → animate in). In production the SSR engine
|
|
4341
|
-
// fires it only after the page transition completes (ClientInteractionService).
|
|
4342
|
-
return this.attachPageLoad(node, ix, element);
|
|
4343
|
-
case 'pageLeave':
|
|
4344
|
-
// Exit choreography that plays on navigate-away. There is no navigation in the
|
|
4345
|
-
// builder, so it does not auto-fire (element stays visible) — the overlay play
|
|
4346
|
-
// button previews it via replay(). In production the orchestrator awaits it.
|
|
4347
|
-
return () => { };
|
|
4348
|
-
case 'viewportEnter':
|
|
4349
|
-
return this.attachViewportEnter(node, ix, element);
|
|
4350
|
-
case 'click':
|
|
4351
|
-
return this.attachClick(node, ix, element);
|
|
4352
|
-
case 'hover':
|
|
4353
|
-
return this.attachHover(node, ix, element);
|
|
4354
|
-
case 'scrollProgress':
|
|
4355
|
-
// Scroll-progress is complex — deferred.
|
|
4356
|
-
return () => { };
|
|
4357
|
-
case 'itemOn':
|
|
4358
|
-
case 'itemOff':
|
|
4359
|
-
return this.attachItemState(node, ix, element, ix.trigger === 'itemOn');
|
|
4360
|
-
default:
|
|
4361
|
-
return () => { };
|
|
4362
|
-
}
|
|
4379
|
+
/** True while the canvas holds the control's tooltip open. */
|
|
4380
|
+
tooltipShown(nodeId) {
|
|
4381
|
+
return !!nodeId && this._state$.value.tooltips.has(nodeId);
|
|
4363
4382
|
}
|
|
4364
|
-
/**
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
const run = () => this.runActions(node, ix.actions, element);
|
|
4371
|
-
const off = onCompositeItemState(element, on, run);
|
|
4372
|
-
const timer = on && isCompositeItemOn(element) ? setTimeout(run, 50) : null;
|
|
4373
|
-
return () => { off(); if (timer)
|
|
4374
|
-
clearTimeout(timer); };
|
|
4383
|
+
/** Hold a toast up on the canvas with these words, or take it down (null). */
|
|
4384
|
+
setToastShown(words) {
|
|
4385
|
+
const next = words === null ? null : String(words);
|
|
4386
|
+
if (this._state$.value.toast === next)
|
|
4387
|
+
return;
|
|
4388
|
+
this._state$.next({ ...this._state$.value, toast: next });
|
|
4375
4389
|
}
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
if (this.hasPreStatedAncestor(element)) {
|
|
4380
|
-
this.deferUntilVisible(element, () => this.runActions(node, ix.actions, element));
|
|
4381
|
-
}
|
|
4382
|
-
else {
|
|
4383
|
-
this.runActions(node, ix.actions, element);
|
|
4384
|
-
}
|
|
4385
|
-
}, 50);
|
|
4386
|
-
return () => clearTimeout(timer);
|
|
4390
|
+
/** The words of the toast the canvas holds up, or null. */
|
|
4391
|
+
get toastShown() {
|
|
4392
|
+
return this._state$.value.toast;
|
|
4387
4393
|
}
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
const key = ix.id;
|
|
4392
|
-
if (ix.actions.some(a => a.once) && firedSet.has(key))
|
|
4393
|
-
return;
|
|
4394
|
-
if (this.hasPreStatedAncestor(element)) {
|
|
4395
|
-
this.deferUntilVisible(element, () => {
|
|
4396
|
-
if (ix.actions.some(a => a.once) && firedSet.has(key))
|
|
4397
|
-
return;
|
|
4398
|
-
firedSet.add(key);
|
|
4399
|
-
this.runActions(node, ix.actions, element);
|
|
4400
|
-
});
|
|
4401
|
-
return;
|
|
4402
|
-
}
|
|
4403
|
-
firedSet.add(key);
|
|
4404
|
-
this.runActions(node, ix.actions, element);
|
|
4405
|
-
};
|
|
4406
|
-
// IntersectionObserver with no root uses the browser viewport.
|
|
4407
|
-
// It respects overflow:hidden clipping from .preview-scroll so it fires
|
|
4408
|
-
// correctly as Lenis scrolls (Lenis changes scrollTop on .preview-scroll).
|
|
4409
|
-
const observer = new IntersectionObserver((entries) => {
|
|
4410
|
-
for (const entry of entries) {
|
|
4411
|
-
if (entry.isIntersecting) {
|
|
4412
|
-
cleanup();
|
|
4413
|
-
fire();
|
|
4414
|
-
}
|
|
4415
|
-
}
|
|
4416
|
-
}, { threshold: 0.1 });
|
|
4417
|
-
observer.observe(element);
|
|
4418
|
-
// Builder: also listen to Lenis scroll events on .preview-lenis-content
|
|
4419
|
-
// as a fallback in case IntersectionObserver misses elements behind a
|
|
4420
|
-
// CSS transform (scale) on iox-page-component.
|
|
4421
|
-
const lenisEl = this.overlayService.getScrollContainer();
|
|
4422
|
-
let scrollFired = false;
|
|
4423
|
-
const onScroll = () => {
|
|
4424
|
-
if (scrollFired)
|
|
4425
|
-
return;
|
|
4426
|
-
const rootEl = this.overlayService.getIntersectionRoot();
|
|
4427
|
-
if (!rootEl)
|
|
4428
|
-
return;
|
|
4429
|
-
const rootRect = rootEl.getBoundingClientRect();
|
|
4430
|
-
const elRect = element.getBoundingClientRect();
|
|
4431
|
-
const overlapH = Math.min(elRect.bottom, rootRect.bottom) - Math.max(elRect.top, rootRect.top);
|
|
4432
|
-
const elH = elRect.bottom - elRect.top;
|
|
4433
|
-
if (elH > 0 && overlapH / elH >= 0.1) {
|
|
4434
|
-
scrollFired = true;
|
|
4435
|
-
cleanup();
|
|
4436
|
-
fire();
|
|
4437
|
-
}
|
|
4438
|
-
};
|
|
4439
|
-
if (lenisEl)
|
|
4440
|
-
lenisEl.addEventListener('scroll', onScroll, { passive: true });
|
|
4441
|
-
const cleanup = () => {
|
|
4442
|
-
observer.disconnect();
|
|
4443
|
-
scrollFired = true;
|
|
4444
|
-
if (lenisEl)
|
|
4445
|
-
lenisEl.removeEventListener('scroll', onScroll);
|
|
4446
|
-
};
|
|
4447
|
-
return cleanup;
|
|
4394
|
+
/** A new page on the canvas: nothing is revealed or switched open. */
|
|
4395
|
+
reset() {
|
|
4396
|
+
this._state$.next(EMPTY_SNAPSHOT());
|
|
4448
4397
|
}
|
|
4449
|
-
|
|
4450
|
-
|
|
4398
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4399
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService }); }
|
|
4400
|
+
}
|
|
4401
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService, decorators: [{
|
|
4402
|
+
type: Injectable
|
|
4403
|
+
}] });
|
|
4404
|
+
function sameMenu(a, b) {
|
|
4405
|
+
if (!a || !b)
|
|
4406
|
+
return a === b;
|
|
4407
|
+
return a.drawer === b.drawer && a.menuButton === b.menuButton && a.root === b.root && a.at === b.at;
|
|
4408
|
+
}
|
|
4409
|
+
function sameSet(a, b) {
|
|
4410
|
+
return a.size === b.size && [...a].every(id => b.has(id));
|
|
4411
|
+
}
|
|
4412
|
+
/**
|
|
4413
|
+
* Show or hide an overlay part (the Menu drawer) for DESIGN — ONE move for every control that previews it (the
|
|
4414
|
+
* Bindings ▸ Phone menu row, the canvas's Menu chip). Opening switches it open; closing closes its canvas twin the way
|
|
4415
|
+
* its own Close does (the exit plays, the reveal and the switch are cleared, a selection inside it moves to its
|
|
4416
|
+
* opener), so a drawer shown because the selection sits in it closes too.
|
|
4417
|
+
*/
|
|
4418
|
+
function setOverlayPreview(state, twins, id, open) {
|
|
4419
|
+
if (!id)
|
|
4420
|
+
return;
|
|
4421
|
+
if (open) {
|
|
4422
|
+
state.setShown(id, CanvasShowState.Open);
|
|
4423
|
+
return;
|
|
4451
4424
|
}
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4425
|
+
const twin = twins?.get(id);
|
|
4426
|
+
if (twin?.close)
|
|
4427
|
+
twin.close();
|
|
4428
|
+
else
|
|
4429
|
+
state.dismiss(id);
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
const CANVAS_SCROLLER = '.preview-scroll';
|
|
4433
|
+
const PREVIEW_SCROLLER = '.iox-page-wrapper';
|
|
4434
|
+
/** What a page-long reading bar measures: the canvas's page frame, or Preview's page inside its scroller. */
|
|
4435
|
+
const CANVAS_PAGE = '#canvas-preview';
|
|
4436
|
+
const PREVIEW_PAGE = '.iox-page-wrapper > .page-mask';
|
|
4437
|
+
/**
|
|
4438
|
+
* Scenes on the canvas and in Preview: the SAME scene-core driver the live site runs, so a layer stands at the same
|
|
4439
|
+
* value at the same progress in every renderer (premium-sites plan §5b). The canvas's page never scrolls itself (its
|
|
4440
|
+
* Lenis scrolls a wrapper around a zoomed frame), so `position: sticky` cannot hold the stage there and the driver does;
|
|
4441
|
+
* Preview's page is a real scroller, and sticky works. Canvas-only state: it never writes a node, so nothing here is
|
|
4442
|
+
* saved or becomes an undo step.
|
|
4443
|
+
*/
|
|
4444
|
+
class SceneCanvasService {
|
|
4445
|
+
constructor() {
|
|
4446
|
+
this.zone = inject(NgZone);
|
|
4447
|
+
this.overlay = inject(OverlayService, { optional: true });
|
|
4448
|
+
/** The selection holds a reading bar full (R2): a design-state change plays the next frame. */
|
|
4449
|
+
this.designSub = inject(CanvasDesignStateService, { optional: true })?.state$.subscribe(() => this.wake()) ?? null;
|
|
4450
|
+
this.driver = null;
|
|
4451
|
+
this.rootsById = new Map();
|
|
4452
|
+
this.idsByRoot = new Map();
|
|
4453
|
+
/** Every frame on every Scene: the selection box and the timeline follow on it. */
|
|
4454
|
+
this.frames$ = new Subject();
|
|
4455
|
+
}
|
|
4456
|
+
/**
|
|
4457
|
+
* Drive a root. `onCanvas` comes from the caller, because only the render directive can read IOX_BUILDER_CANVAS: the
|
|
4458
|
+
* token is provided on the canvas's element, below where this service lives. Read here, it answered false, and the
|
|
4459
|
+
* canvas measured its Scenes against a page that moves with them, so they never left progress 0.
|
|
4460
|
+
*/
|
|
4461
|
+
attach(root, onCanvas, nodeId) {
|
|
4462
|
+
this.zone.runOutsideAngular(() => this.driverFor(onCanvas).add(root));
|
|
4463
|
+
if (nodeId) {
|
|
4464
|
+
this.rootsById.set(nodeId, root);
|
|
4465
|
+
this.idsByRoot.set(root, nodeId);
|
|
4459
4466
|
}
|
|
4460
|
-
return false;
|
|
4461
4467
|
}
|
|
4462
|
-
/**
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4468
|
+
/**
|
|
4469
|
+
* Play an element's `scrollProgress` interaction on THIS driver — the Scenes' — never a loop of its own (premium M6,
|
|
4470
|
+
* N7). The live site makes the same call (`attachScrollProgress`) on its own driver. Returns the way to let go.
|
|
4471
|
+
*/
|
|
4472
|
+
linkScroll(owner, interaction, options) {
|
|
4473
|
+
return this.zone.runOutsideAngular(() => {
|
|
4474
|
+
const driver = this.driverFor(options.onCanvas);
|
|
4475
|
+
const page = owner.closest(options.onCanvas ? CANVAS_PAGE : PREVIEW_PAGE)
|
|
4476
|
+
?? owner.ownerDocument.querySelector(options.onCanvas ? CANVAS_PAGE : PREVIEW_PAGE)
|
|
4477
|
+
?? owner.ownerDocument.documentElement;
|
|
4478
|
+
return attachScrollProgress(owner, interaction, { driver, page, targetsOf: action => options.targetsOf(action), held: options.held });
|
|
4479
|
+
});
|
|
4473
4480
|
}
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
const { onPress, onClick } = ix.reverseActions?.length ? { onPress: [], onClick: ix.actions } : splitPressFeedback(ix.actions);
|
|
4478
|
-
const handler = () => {
|
|
4479
|
-
if (ix.reverseActions?.length) {
|
|
4480
|
-
// Toggle: alternate between primary and reverse actions on each click.
|
|
4481
|
-
const isToggled = this.toggleStates.get(ix.id) ?? false;
|
|
4482
|
-
this.runActions(node, isToggled ? ix.reverseActions : ix.actions, element);
|
|
4483
|
-
this.toggleStates.set(ix.id, !isToggled);
|
|
4484
|
-
}
|
|
4485
|
-
else {
|
|
4486
|
-
this.runActions(node, onClick, element);
|
|
4487
|
-
}
|
|
4488
|
-
};
|
|
4489
|
-
const press = (e) => { if (e.button === 0 || e.pointerType !== 'mouse')
|
|
4490
|
-
this.runActions(node, onPress, element); };
|
|
4491
|
-
// CAPTURE, like the composite runtime: on the canvas every part's own listener stops the click to select
|
|
4492
|
-
// that part, so a click on a button's LABEL never bubbled to the button's click interaction — the live
|
|
4493
|
-
// site runs it (CC2.1: "Open an item" on a ButtonBlock did nothing on the canvas).
|
|
4494
|
-
element.addEventListener('click', handler, true);
|
|
4495
|
-
if (onPress.length)
|
|
4496
|
-
element.addEventListener('pointerdown', press, true);
|
|
4497
|
-
return () => {
|
|
4498
|
-
element.removeEventListener('click', handler, true);
|
|
4499
|
-
element.removeEventListener('pointerdown', press, true);
|
|
4500
|
-
};
|
|
4481
|
+
/** Something only the canvas knows moved (the selection held a reading bar full): play the next frame. */
|
|
4482
|
+
wake() {
|
|
4483
|
+
this.driver?.wake();
|
|
4501
4484
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
if (!isActionEnabled(action))
|
|
4512
|
-
continue;
|
|
4513
|
-
// Context actions have no "backwards" — the shared table decides what leave does
|
|
4514
|
-
// (clear, unless the action sets noReverse). Same call the SSR engine makes.
|
|
4515
|
-
if (isContextAction(action)) {
|
|
4516
|
-
this.applyContextAction(action, element, 'reverse');
|
|
4517
|
-
continue;
|
|
4518
|
-
}
|
|
4519
|
-
const target = this.resolveTarget(node, action, element);
|
|
4520
|
-
if (!target)
|
|
4521
|
-
continue;
|
|
4522
|
-
this.reverseAction(target, action);
|
|
4523
|
-
}
|
|
4524
|
-
};
|
|
4525
|
-
element.addEventListener('pointerenter', enterHandler);
|
|
4526
|
-
element.addEventListener('pointerleave', leaveHandler);
|
|
4527
|
-
return () => {
|
|
4528
|
-
element.removeEventListener('pointerenter', enterHandler);
|
|
4529
|
-
element.removeEventListener('pointerleave', leaveHandler);
|
|
4530
|
-
};
|
|
4485
|
+
driverFor(onCanvas) {
|
|
4486
|
+
this.driver ??= new ScrollDriver({
|
|
4487
|
+
pin: onCanvas,
|
|
4488
|
+
observe: true,
|
|
4489
|
+
originOf: el => el.closest(onCanvas ? CANVAS_SCROLLER : PREVIEW_SCROLLER),
|
|
4490
|
+
view: () => viewOf(document.querySelector(onCanvas ? CANVAS_SCROLLER : PREVIEW_SCROLLER)),
|
|
4491
|
+
onFrame: (el, state) => this.frames$.next({ nodeId: this.idsByRoot.get(el) ?? null, ...state }),
|
|
4492
|
+
});
|
|
4493
|
+
return this.driver;
|
|
4531
4494
|
}
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
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
|
-
}
|
|
4560
|
-
// Item actions (CC2.1 — composite T14): the canvas RUNS them on the target item's own runtime — the
|
|
4561
|
-
// one the live site runs — and brings an item that opened out of view into it.
|
|
4562
|
-
const itemAction = itemActionOf(action.type);
|
|
4563
|
-
if (itemAction) {
|
|
4564
|
-
this.executeItemAction(node, action, itemAction, selfEl);
|
|
4565
|
-
continue;
|
|
4566
|
-
}
|
|
4567
|
-
// Tier-A behaviour (A1c). The canvas-is-live rules are catalog DATA
|
|
4568
|
-
// (ELEMENT_ACTION_DESCRIPTORS): copy runs, scrollTo runs (scrollIntoView — the frame
|
|
4569
|
-
// scrolls natively here; the Lenis rule is the LIVE side's), navigate and submitForm
|
|
4570
|
-
// SIMULATE — a dispatched event the host surfaces as a toast, never a route change
|
|
4571
|
-
// or a fake submit under the author's feet.
|
|
4572
|
-
if (BEHAVIOUR_ACTION_TYPES.has(action.type)) {
|
|
4573
|
-
this.executeBehaviourAction(node, action, selfEl);
|
|
4574
|
-
continue;
|
|
4575
|
-
}
|
|
4576
|
-
// Tier-B server actions (A2). ALWAYS a simulation here: the canvas is the ADMIN's
|
|
4577
|
-
// browser and a server action is a public-site write (an email, a webhook, a content
|
|
4578
|
-
// row) — the toast says what the LIVE site would do.
|
|
4579
|
-
if (SERVER_ELEMENT_ACTION_TYPES.has(action.type)) {
|
|
4580
|
-
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
4581
|
-
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
4582
|
-
continue;
|
|
4583
|
-
}
|
|
4584
|
-
// Consent actions (C3). Recording ones SIMULATE — the canvas is the ADMIN's browser,
|
|
4585
|
-
// and an admin's cookie is not a visitor's decision; the toast says what would be
|
|
4586
|
-
// recorded. Opening the preferences dialog is dialog-opening and honestly RUNS.
|
|
4587
|
-
if (CONSENT_ACTION_TYPES.has(action.type)) {
|
|
4588
|
-
this.executeConsentAction(action);
|
|
4589
|
-
continue;
|
|
4590
|
-
}
|
|
4591
|
-
// Context actions write STATE, not animation. `selfEl` is deliberate: $item is read by
|
|
4592
|
-
// walking up from the element actually hovered/clicked, which for a repeater PREVIEW ROW
|
|
4593
|
-
// is the clone — so each row publishes its own item, exactly as on the live site.
|
|
4594
|
-
if (isContextAction(action)) {
|
|
4595
|
-
this.applyContextAction(action, selfEl ?? null, 'forward');
|
|
4596
|
-
continue;
|
|
4597
|
-
}
|
|
4598
|
-
// selfEl lets 'self'-targeted actions resolve for preview CLONES, which aren't
|
|
4599
|
-
// overlay-registered (getNodeRef would return undefined) — see attachTo. (issue #2)
|
|
4600
|
-
const target = this.resolveTarget(node, action, selfEl);
|
|
4601
|
-
if (!target)
|
|
4602
|
-
continue;
|
|
4603
|
-
this.executeAction(target, action);
|
|
4604
|
-
}
|
|
4495
|
+
detach(root) {
|
|
4496
|
+
this.driver?.remove(root);
|
|
4497
|
+
const id = this.idsByRoot.get(root);
|
|
4498
|
+
// A re-render attaches the new element before the old one detaches: it must not lose the new one.
|
|
4499
|
+
if (id && this.rootsById.get(id) === root)
|
|
4500
|
+
this.rootsById.delete(id);
|
|
4501
|
+
this.idsByRoot.delete(root);
|
|
4502
|
+
}
|
|
4503
|
+
/** The canvas's Lenis just moved the scroll, inside this frame: hold every stage in this same frame. */
|
|
4504
|
+
pinNow() {
|
|
4505
|
+
this.driver?.pinNow();
|
|
4506
|
+
}
|
|
4507
|
+
/** The damped progress a root's layers stand at. */
|
|
4508
|
+
progressOf(root) {
|
|
4509
|
+
return this.driver?.progressOf(root) ?? null;
|
|
4510
|
+
}
|
|
4511
|
+
/** Where the scroll stands in a Scene, 0 to 1. Null for a Scene that is not on the canvas. */
|
|
4512
|
+
positionOf(nodeId) {
|
|
4513
|
+
const root = nodeId ? this.rootsById.get(nodeId) : null;
|
|
4514
|
+
return root ? this.driver?.targetOf(root) ?? null : null;
|
|
4605
4515
|
}
|
|
4606
4516
|
/**
|
|
4607
|
-
*
|
|
4608
|
-
*
|
|
4609
|
-
* builder canvas and the live site behave identically.
|
|
4517
|
+
* Stand a Scene at `p` by really scrolling the canvas: there is one position, so the scrubber, the wheel, the pin and
|
|
4518
|
+
* the selection box can never disagree. It lands with no glide, because the author is waiting to see that moment.
|
|
4610
4519
|
*/
|
|
4611
|
-
|
|
4612
|
-
const
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
if (
|
|
4616
|
-
this.contextStore.clear(effect.slot);
|
|
4520
|
+
goTo(nodeId, p) {
|
|
4521
|
+
const root = nodeId ? this.rootsById.get(nodeId) : null;
|
|
4522
|
+
const offset = root ? this.driver?.offsetFor(root, p) : null;
|
|
4523
|
+
const scroller = root?.closest(CANVAS_SCROLLER);
|
|
4524
|
+
if (!root || offset == null || !scroller)
|
|
4617
4525
|
return;
|
|
4618
|
-
|
|
4619
|
-
const
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
});
|
|
4623
|
-
this.contextStore.set(effect.slot, value);
|
|
4526
|
+
const rect = root.getBoundingClientRect();
|
|
4527
|
+
const zoom = root.offsetHeight ? rect.height / root.offsetHeight : 1;
|
|
4528
|
+
scroller.scrollTop += rect.top - scroller.getBoundingClientRect().top + offset * zoom;
|
|
4529
|
+
this.driver?.settle(root);
|
|
4624
4530
|
}
|
|
4625
|
-
/**
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
const
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4531
|
+
/**
|
|
4532
|
+
* A layer's tracks were edited: its element carries them now and its Scene plays them on the next frame, with no
|
|
4533
|
+
* re-render, because a Scene's own DOM is what the author is looking at.
|
|
4534
|
+
*/
|
|
4535
|
+
refreshLayer(node, root) {
|
|
4536
|
+
const ref = this.overlay?.getNodeRef(node) ?? (node.id ? this.overlay?.findRefByNodeId(node.id)?.ref : undefined);
|
|
4537
|
+
if (ref?.element && this.overlay)
|
|
4538
|
+
syncSceneAttributes(this.overlay.getStyledElement(ref.element), node);
|
|
4539
|
+
const el = root?.id ? this.rootsById.get(root.id) : null;
|
|
4540
|
+
if (el)
|
|
4541
|
+
this.driver?.reread(el);
|
|
4634
4542
|
}
|
|
4635
|
-
/**
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4543
|
+
/**
|
|
4544
|
+
* A value a layer's own component draws changed (a Video's pictures): its instance takes it now. Its Scene reads the new
|
|
4545
|
+
* attribute when it lands, with no re-render of the page.
|
|
4546
|
+
*/
|
|
4547
|
+
setLayerInput(node, name, value) {
|
|
4548
|
+
const ref = this.overlay?.getNodeRef(node) ?? (node.id ? this.overlay?.findRefByNodeId(node.id)?.ref : undefined);
|
|
4549
|
+
const component = ref?.componentRef;
|
|
4550
|
+
if (!component?.instance)
|
|
4551
|
+
return;
|
|
4552
|
+
component.instance[name] = value;
|
|
4553
|
+
component.changeDetectorRef.detectChanges();
|
|
4639
4554
|
}
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
}
|
|
4648
|
-
case 'scrollTo': {
|
|
4649
|
-
const target = this.resolveTarget(node, action, selfEl);
|
|
4650
|
-
target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
4651
|
-
return;
|
|
4652
|
-
}
|
|
4653
|
-
case 'navigate':
|
|
4654
|
-
case 'submitForm': {
|
|
4655
|
-
// Simulated: the host (the admin app) listens and shows "would …" as a toast.
|
|
4656
|
-
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
4657
|
-
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
4658
|
-
return;
|
|
4659
|
-
}
|
|
4660
|
-
}
|
|
4555
|
+
ngOnDestroy() {
|
|
4556
|
+
this.designSub?.unsubscribe();
|
|
4557
|
+
this.driver?.destroy();
|
|
4558
|
+
this.driver = null;
|
|
4559
|
+
this.rootsById.clear();
|
|
4560
|
+
this.idsByRoot.clear();
|
|
4561
|
+
this.frames$.complete();
|
|
4661
4562
|
}
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4563
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SceneCanvasService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4564
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SceneCanvasService }); }
|
|
4565
|
+
}
|
|
4566
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SceneCanvasService, decorators: [{
|
|
4567
|
+
type: Injectable
|
|
4568
|
+
}] });
|
|
4569
|
+
/** The part of the page on screen: the scroller's own rectangle (the canvas's, Preview's). */
|
|
4570
|
+
function viewOf(scroller) {
|
|
4571
|
+
if (!scroller)
|
|
4572
|
+
return null;
|
|
4573
|
+
const rect = scroller.getBoundingClientRect();
|
|
4574
|
+
return { top: rect.top, height: rect.height };
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
/** What the canvas never writes inline (a transform inside the scaled page breaks Lenis). */
|
|
4578
|
+
const CANVAS_SKIPPED_PROPS = new Set(['transform']);
|
|
4579
|
+
/**
|
|
4580
|
+
* Wires ComponentNode interactions to live DOM elements via the
|
|
4581
|
+
* OverlayService node-element registry.
|
|
4582
|
+
*
|
|
4583
|
+
* Call `attach(node)` after the node is rendered and registered.
|
|
4584
|
+
* Call `detach(node)` on destroy to tear down listeners/observers.
|
|
4585
|
+
*/
|
|
4586
|
+
class InteractionEngineService {
|
|
4587
|
+
constructor(overlayService) {
|
|
4588
|
+
this.overlayService = overlayService;
|
|
4589
|
+
this.attached = new Map();
|
|
4590
|
+
/** Shared with the SSR engine — one store type, one set of semantics. */
|
|
4591
|
+
this.contextStore = inject(ContextStore);
|
|
4592
|
+
/** Resolved data for the canvas, so a row marker (source + index) resolves back to its item. */
|
|
4593
|
+
this.contextData = {};
|
|
4594
|
+
/** Elements that have had pre-state applied (hidden by attach). */
|
|
4595
|
+
this.preStatedElements = new Set();
|
|
4596
|
+
/**
|
|
4597
|
+
* Toggle state for click interactions with reverseActions.
|
|
4598
|
+
* false = next click runs actions (primary); true = next click runs reverseActions.
|
|
4599
|
+
*/
|
|
4600
|
+
this.toggleStates = new Map();
|
|
4601
|
+
/**
|
|
4602
|
+
* Pre-states queued for elements that weren't registered yet when the owner
|
|
4603
|
+
* node's attach() ran. Keyed by target node ID. Consumed when the target
|
|
4604
|
+
* element's own attach() fires.
|
|
4605
|
+
*/
|
|
4606
|
+
this.pendingPreStates = new Map();
|
|
4607
|
+
/** Action types that animate an element from hidden → visible / visible → hidden — the SHARED sets
|
|
4608
|
+
* (@vectoriox/iox-ui), the same ones the live site's runtime reads. */
|
|
4609
|
+
this.ENTRANCE_TYPES = ENTRANCE_ACTION_TYPES;
|
|
4610
|
+
this.EXIT_TYPES = EXIT_ACTION_TYPES;
|
|
4611
|
+
this.SWIPER_ACTIONS = new Set([
|
|
4612
|
+
'swiperNext', 'swiperPrev', 'swiperGoToSlide',
|
|
4613
|
+
'swiperAutoplayStart', 'swiperAutoplayStop', 'swiperAutoplayPause',
|
|
4614
|
+
]);
|
|
4615
|
+
this.instanceRegistry = inject(ComponentInstanceRegistryService);
|
|
4616
|
+
this.ngZone = inject(NgZone);
|
|
4617
|
+
/** The ONE scroll loop of this canvas (its Scenes' driver): `scrollProgress` interactions ride it as links (M6, N7). */
|
|
4618
|
+
this.sceneCanvas = inject(SceneCanvasService, { optional: true });
|
|
4619
|
+
this.designState = inject(CanvasDesignStateService, { optional: true });
|
|
4620
|
+
/** Numbers armed to count (M6, N5): put back when the node lets go. */
|
|
4621
|
+
this.armedCounts = new Map();
|
|
4669
4622
|
}
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4623
|
+
/**
|
|
4624
|
+
* Run all DOM-manipulation operations (pre-state, observers, animations)
|
|
4625
|
+
* outside the Angular zone so Zone.js does not schedule unnecessary CD ticks
|
|
4626
|
+
* for each RAF/IntersectionObserver/animation callback.
|
|
4627
|
+
* The caller is responsible for calling markForCheck() / refreshSelect() inside
|
|
4628
|
+
* the zone after this returns.
|
|
4629
|
+
*/
|
|
4630
|
+
runOutside(fn) {
|
|
4631
|
+
return this.ngZone.runOutsideAngular(fn);
|
|
4632
|
+
}
|
|
4633
|
+
/** Wire all interactions for a node to its rendered DOM element. */
|
|
4634
|
+
attach(node) { this.runOutside(() => this._attach(node)); }
|
|
4635
|
+
/** Attach interactions to a node whose element is NOT overlay-registered — e.g. repeater/slider
|
|
4636
|
+
* PREVIEW CLONES, which are non-selectable but must still animate (parity with the SSR runtime,
|
|
4637
|
+
* which animates every repeated row). See RenderDirective. */
|
|
4638
|
+
attachTo(node, element) { this.runOutside(() => this._attach(node, element)); }
|
|
4639
|
+
_attach(node, elementOverride) {
|
|
4640
|
+
const element = elementOverride ?? this.overlayService.getNodeRef(node)?.element;
|
|
4641
|
+
if (!element)
|
|
4674
4642
|
return;
|
|
4643
|
+
// Apply any pre-states queued for THIS element by earlier-registered nodes.
|
|
4644
|
+
// Pending entries are only added when appropriate (see loop below), so always consume.
|
|
4645
|
+
if (node.id) {
|
|
4646
|
+
const pending = this.pendingPreStates.get(node.id);
|
|
4647
|
+
if (pending) {
|
|
4648
|
+
for (const action of pending) {
|
|
4649
|
+
this.applyPreState(element, action);
|
|
4650
|
+
}
|
|
4651
|
+
this.pendingPreStates.delete(node.id);
|
|
4652
|
+
}
|
|
4675
4653
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
const dialog = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
4681
|
-
if (!dialog)
|
|
4654
|
+
// What its interactions say about its first paint (tabular digits, an empty reading bar) — the same markers every
|
|
4655
|
+
// renderer writes, kept in step with the Interactions panel.
|
|
4656
|
+
syncInteractionMarkers(element, node);
|
|
4657
|
+
if (!node.interactions?.length)
|
|
4682
4658
|
return;
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4659
|
+
const cleanups = [];
|
|
4660
|
+
for (const ix of node.interactions) {
|
|
4661
|
+
// Pre-state: hide target elements before their entrance animation fires so
|
|
4662
|
+
// the user sees a real hidden→visible animation on the canvas. Every trigger
|
|
4663
|
+
// runs live in the builder (see attachInteraction).
|
|
4664
|
+
// Under reduced motion a self-playing entrance starts shown and never moves (shared `entranceStartsHidden`).
|
|
4665
|
+
// A scroll-played entrance never starts hidden either (shared: the scroll stands it — M6).
|
|
4666
|
+
const skipPreState = !entranceStartsHidden(ix.trigger);
|
|
4667
|
+
if (!skipPreState) {
|
|
4668
|
+
for (const action of ix.actions) {
|
|
4669
|
+
// A disabled action must not pre-state: it would hide the element with nothing
|
|
4670
|
+
// left to reveal it.
|
|
4671
|
+
if (!isActionEnabled(action))
|
|
4672
|
+
continue;
|
|
4673
|
+
if (this.ENTRANCE_TYPES.has(action.type)) {
|
|
4674
|
+
const target = this.resolveTarget(node, action, element);
|
|
4675
|
+
if (target) {
|
|
4676
|
+
this.applyPreState(target, action);
|
|
4677
|
+
}
|
|
4678
|
+
else if (action.target && action.target !== 'self') {
|
|
4679
|
+
const list = this.pendingPreStates.get(action.target) ?? [];
|
|
4680
|
+
list.push(action);
|
|
4681
|
+
this.pendingPreStates.set(action.target, list);
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
const cleanup = this.attachInteraction(node, ix, element);
|
|
4687
|
+
cleanups.push({ node, interaction: ix, cleanup });
|
|
4697
4688
|
}
|
|
4689
|
+
this.armCounts(node, element);
|
|
4690
|
+
this.attached.set(node, cleanups);
|
|
4698
4691
|
}
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
case 'togglePopover':
|
|
4711
|
-
popover.toggle?.(selfEl ?? null);
|
|
4712
|
-
break;
|
|
4692
|
+
/** Tear down all interaction listeners for a node and reset its animated state. */
|
|
4693
|
+
detach(node) { this.runOutside(() => this._detach(node)); }
|
|
4694
|
+
_detach(node) {
|
|
4695
|
+
for (const el of this.armedCounts.get(node) ?? [])
|
|
4696
|
+
restoreCountUp(el);
|
|
4697
|
+
this.armedCounts.delete(node);
|
|
4698
|
+
const entries = this.attached.get(node);
|
|
4699
|
+
if (!entries)
|
|
4700
|
+
return;
|
|
4701
|
+
for (const entry of entries) {
|
|
4702
|
+
entry.cleanup();
|
|
4713
4703
|
}
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
const host = document.querySelector(`.iox-node-${CSS.escape(dialogId)}`);
|
|
4720
|
-
const swiper = host?.querySelector('.swiper')?.swiper;
|
|
4721
|
-
if (swiper?.slides?.length) {
|
|
4722
|
-
swiper.update?.();
|
|
4723
|
-
swiper.slideTo(slideIndexForRow(row, swiper.slides.length), 0);
|
|
4724
|
-
return;
|
|
4704
|
+
this.attached.delete(node);
|
|
4705
|
+
// Reset toggle state so re-attach starts from the primary actions.
|
|
4706
|
+
for (const entry of entries) {
|
|
4707
|
+
if (entry.interaction.reverseActions?.length) {
|
|
4708
|
+
this.toggleStates.delete(entry.interaction.id);
|
|
4725
4709
|
}
|
|
4726
|
-
if (++tries < 40)
|
|
4727
|
-
requestAnimationFrame(attempt);
|
|
4728
|
-
};
|
|
4729
|
-
requestAnimationFrame(attempt);
|
|
4730
|
-
}
|
|
4731
|
-
executeSwiperAction(action) {
|
|
4732
|
-
// Target is the nodeId of the SliderContainer to control.
|
|
4733
|
-
const swiper = this.instanceRegistry.get(action.target);
|
|
4734
|
-
if (!swiper)
|
|
4735
|
-
return;
|
|
4736
|
-
switch (action.type) {
|
|
4737
|
-
case 'swiperNext':
|
|
4738
|
-
swiper.slideNext();
|
|
4739
|
-
break;
|
|
4740
|
-
case 'swiperPrev':
|
|
4741
|
-
swiper.slidePrev();
|
|
4742
|
-
break;
|
|
4743
|
-
case 'swiperGoToSlide':
|
|
4744
|
-
swiper.slideTo(action.params?.['index'] ?? 0);
|
|
4745
|
-
break;
|
|
4746
|
-
case 'swiperAutoplayStart':
|
|
4747
|
-
swiper.autoplay?.start();
|
|
4748
|
-
break;
|
|
4749
|
-
case 'swiperAutoplayStop':
|
|
4750
|
-
swiper.autoplay?.stop();
|
|
4751
|
-
break;
|
|
4752
|
-
case 'swiperAutoplayPause':
|
|
4753
|
-
swiper.autoplay?.pause();
|
|
4754
|
-
break;
|
|
4755
4710
|
}
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4711
|
+
// Cancel Web Animations and strip any inline pre-state styles that
|
|
4712
|
+
// were applied by applyPreState() so the element returns to its natural CSS state.
|
|
4713
|
+
const ref = this.overlayService.getNodeRef(node);
|
|
4714
|
+
if (ref) {
|
|
4715
|
+
ref.element.getAnimations().forEach(a => a.cancel());
|
|
4716
|
+
this.clearInlineAnimationStyles(ref.element);
|
|
4760
4717
|
}
|
|
4761
|
-
//
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4718
|
+
// Also clean up pre-state on any target elements this node was animating.
|
|
4719
|
+
if (node.interactions) {
|
|
4720
|
+
for (const ix of node.interactions) {
|
|
4721
|
+
for (const action of ix.actions) {
|
|
4722
|
+
// Remove any pending pre-states for unregistered targets.
|
|
4723
|
+
if (action.target && action.target !== 'self') {
|
|
4724
|
+
const pending = this.pendingPreStates.get(action.target);
|
|
4725
|
+
if (pending) {
|
|
4726
|
+
const filtered = pending.filter(a => a !== action);
|
|
4727
|
+
if (filtered.length) {
|
|
4728
|
+
this.pendingPreStates.set(action.target, filtered);
|
|
4729
|
+
}
|
|
4730
|
+
else {
|
|
4731
|
+
this.pendingPreStates.delete(action.target);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
// Clean up inline styles on already-registered target elements.
|
|
4736
|
+
const target = this.resolveTarget(node, action);
|
|
4737
|
+
if (target && this.preStatedElements.has(target)) {
|
|
4738
|
+
target.getAnimations().forEach(a => a.cancel());
|
|
4739
|
+
this.clearInlineAnimationStyles(target);
|
|
4740
|
+
this.preStatedElements.delete(target);
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4765
4744
|
}
|
|
4766
|
-
return null;
|
|
4767
4745
|
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
// rule: an entrance releases to the stylesheet, an exit holds its hidden frame).
|
|
4781
|
-
if (!motionAllowed()) {
|
|
4782
|
-
settleInteraction(element, keyframes, settleModeFor(type), this.settleOptions());
|
|
4783
|
-
this.settleAfter(element, type);
|
|
4746
|
+
/** Re-attach interactions after they have been edited in the panel. */
|
|
4747
|
+
refresh(node) {
|
|
4748
|
+
this.runOutside(() => { this._detach(node); this._attach(node); });
|
|
4749
|
+
}
|
|
4750
|
+
/**
|
|
4751
|
+
* Replay all interactions on a node immediately, regardless of their trigger.
|
|
4752
|
+
* Used by the builder overlay play button for in-editor animation preview.
|
|
4753
|
+
* Uses fill:'none' so the element returns to its natural state after the preview.
|
|
4754
|
+
*/
|
|
4755
|
+
replay(node) { this.runOutside(() => this._replay(node)); }
|
|
4756
|
+
_replay(node) {
|
|
4757
|
+
if (!node.interactions?.length)
|
|
4784
4758
|
return;
|
|
4759
|
+
const ref = this.overlayService.getNodeRef(node);
|
|
4760
|
+
if (ref) {
|
|
4761
|
+
ref.element.getAnimations().forEach(a => a.cancel());
|
|
4762
|
+
// Clear any leftover inline styles so the pre-state applies cleanly.
|
|
4763
|
+
this.clearInlineAnimationStyles(ref.element);
|
|
4785
4764
|
}
|
|
4786
|
-
//
|
|
4787
|
-
//
|
|
4788
|
-
//
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
/** On the canvas a run never writes a transform inline (one inside the scaled page breaks Lenis). */
|
|
4798
|
-
settleOptions() {
|
|
4799
|
-
return this.isBuilderMode() ? { skip: CANVAS_SKIPPED_PROPS } : {};
|
|
4800
|
-
}
|
|
4801
|
-
/** What an element is left as once an action is done: a revealed one interactive, a hidden one inert. */
|
|
4802
|
-
settleAfter(element, type) {
|
|
4803
|
-
if (this.ENTRANCE_TYPES.has(type)) {
|
|
4804
|
-
element.style.removeProperty('pointer-events');
|
|
4805
|
-
this.preStatedElements.delete(element);
|
|
4806
|
-
// Builder: clear inline pre-state so cancelling animations later
|
|
4807
|
-
// doesn't leave the element invisible after a play preview.
|
|
4808
|
-
if (this.isBuilderMode())
|
|
4809
|
-
this.clearInlineAnimationStyles(element);
|
|
4765
|
+
// In builder mode pre-state is never applied on attach, so apply it now
|
|
4766
|
+
// to give an accurate hidden→visible preview. It is cleaned up via
|
|
4767
|
+
// executeAction's anim.finished handler once the animation completes.
|
|
4768
|
+
for (const ix of node.interactions) {
|
|
4769
|
+
for (const action of ix.actions) {
|
|
4770
|
+
if (this.ENTRANCE_TYPES.has(action.type)) {
|
|
4771
|
+
const target = this.resolveTarget(node, action);
|
|
4772
|
+
if (target)
|
|
4773
|
+
this.applyPreState(target, action);
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4810
4776
|
}
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
// silently block clicks underneath it.
|
|
4814
|
-
element.style.setProperty('pointer-events', 'none');
|
|
4777
|
+
for (const ix of node.interactions) {
|
|
4778
|
+
this.runActions(node, ix.actions);
|
|
4815
4779
|
}
|
|
4816
4780
|
}
|
|
4817
4781
|
/**
|
|
4818
|
-
*
|
|
4819
|
-
*
|
|
4820
|
-
*
|
|
4821
|
-
*
|
|
4822
|
-
* In builder mode only opacity:0 is applied — transforms on canvas elements
|
|
4823
|
-
* inside iox-page-component's scale() transform break Lenis scroll. The transform
|
|
4824
|
-
* jump at animation t=0 is invisible because the element is already opacity:0.
|
|
4825
|
-
* In production the full keyframe is applied + pointer-events:none.
|
|
4782
|
+
* Fire all interactions on the given node that match the specified trigger.
|
|
4783
|
+
* Used by Swiper (and future components) to dispatch their events into the
|
|
4784
|
+
* interaction engine without coupling to the engine's attach/detach lifecycle.
|
|
4826
4785
|
*/
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4786
|
+
replayTrigger(nodeId, trigger) { this.runOutside(() => this._replayTrigger(nodeId, trigger)); }
|
|
4787
|
+
_replayTrigger(nodeId, trigger) {
|
|
4788
|
+
for (const [node] of this.attached) {
|
|
4789
|
+
if (node.id !== nodeId)
|
|
4790
|
+
continue;
|
|
4791
|
+
for (const ix of (node.interactions ?? [])) {
|
|
4792
|
+
if (ix.trigger === trigger) {
|
|
4793
|
+
this.runActions(node, ix.actions);
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4830
4796
|
return;
|
|
4831
|
-
const first = entrancePreStateStyle(action.type, action.params);
|
|
4832
|
-
if (this.isBuilderMode()) {
|
|
4833
|
-
// No transforms — inline transforms on children inside iox-page-component's
|
|
4834
|
-
// scale() break Lenis scroll. pointer-events:none lets wheel events and
|
|
4835
|
-
// hover fall through to visible elements underneath. An entrance that hides
|
|
4836
|
-
// nothing (a picture settling) stays in view.
|
|
4837
|
-
if ('opacity' in first || 'visibility' in first)
|
|
4838
|
-
element.style.setProperty('opacity', '0');
|
|
4839
|
-
element.style.setProperty('pointer-events', 'none');
|
|
4840
4797
|
}
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4798
|
+
}
|
|
4799
|
+
// ── Private ──────────────────────────────────────────────
|
|
4800
|
+
attachInteraction(node, ix, element) {
|
|
4801
|
+
// All triggers run live in the builder canvas so the user sees the real
|
|
4802
|
+
// animation (pageLoad on attach, viewportEnter on scroll-into-view, click/hover).
|
|
4803
|
+
// The former NG0103 crash was caused by (a) Swiper's ResizeObserver created inside
|
|
4804
|
+
// the Angular zone — fixed in SliderContainerComponent via runOutsideAngular — and
|
|
4805
|
+
// (b) the overlay's Resize/MutationObservers synchronously re-dirtying the view
|
|
4806
|
+
// during synchronize() — fixed by coalescing overlay updates through a RAF.
|
|
4807
|
+
// scrollProgress is still unimplemented and returns a no-op below.
|
|
4808
|
+
switch (ix.trigger) {
|
|
4809
|
+
case 'pageLoad':
|
|
4810
|
+
return this.attachPageLoad(node, ix, element);
|
|
4811
|
+
case 'pageEnter':
|
|
4812
|
+
// In the builder canvas there is no navigation, so preview a page-enter the same
|
|
4813
|
+
// way as page-load (pre-stated hidden → animate in). In production the SSR engine
|
|
4814
|
+
// fires it only after the page transition completes (ClientInteractionService).
|
|
4815
|
+
return this.attachPageLoad(node, ix, element);
|
|
4816
|
+
case 'pageLeave':
|
|
4817
|
+
// Exit choreography that plays on navigate-away. There is no navigation in the
|
|
4818
|
+
// builder, so it does not auto-fire (element stays visible) — the overlay play
|
|
4819
|
+
// button previews it via replay(). In production the orchestrator awaits it.
|
|
4820
|
+
return () => { };
|
|
4821
|
+
case 'viewportEnter':
|
|
4822
|
+
return this.attachViewportEnter(node, ix, element);
|
|
4823
|
+
case 'click':
|
|
4824
|
+
return this.attachClick(node, ix, element);
|
|
4825
|
+
case 'hover':
|
|
4826
|
+
return this.attachHover(node, ix, element);
|
|
4827
|
+
case 'scrollProgress':
|
|
4828
|
+
return this.attachScrollProgress(node, ix, element);
|
|
4829
|
+
case 'itemOn':
|
|
4830
|
+
case 'itemOff':
|
|
4831
|
+
return this.attachItemState(node, ix, element, ix.trigger === 'itemOn');
|
|
4832
|
+
default:
|
|
4833
|
+
return () => { };
|
|
4845
4834
|
}
|
|
4846
|
-
element.setAttribute(INTERACTION_HIDDEN_ATTRIBUTE, '');
|
|
4847
|
-
this.preStatedElements.add(element);
|
|
4848
4835
|
}
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4836
|
+
/**
|
|
4837
|
+
* `scrollProgress` (premium M6, N7): the actions follow where the element stands in the canvas's page, on the canvas's
|
|
4838
|
+
* ONE scroll loop — the Scenes' driver (iox-ui `attachScrollProgress`, the live site's same call). While a reading bar
|
|
4839
|
+
* is selected (R2) it stands full, so it can be seen and styled.
|
|
4840
|
+
*/
|
|
4841
|
+
attachScrollProgress(node, ix, element) {
|
|
4842
|
+
if (!this.sceneCanvas || ix.trigger !== SCROLL_PROGRESS_TRIGGER)
|
|
4843
|
+
return () => { };
|
|
4844
|
+
return this.sceneCanvas.linkScroll(element, ix, {
|
|
4845
|
+
onCanvas: this.isBuilderMode(),
|
|
4846
|
+
targetsOf: action => {
|
|
4847
|
+
const target = this.resolveTarget(node, action, element);
|
|
4848
|
+
return target ? [target] : [];
|
|
4849
|
+
},
|
|
4850
|
+
held: () => (node.id && this.designState?.isShown(node.id) ? 1 : null),
|
|
4851
|
+
});
|
|
4856
4852
|
}
|
|
4857
|
-
/**
|
|
4858
|
-
*
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
if (!motionAllowed()) {
|
|
4866
|
-
settleInteraction(element, frames, settleModeFor(type, true), this.settleOptions());
|
|
4867
|
-
return;
|
|
4868
|
-
}
|
|
4869
|
-
playInteraction(element, { type, params: action.params }, frames, {
|
|
4870
|
-
duration: action.duration,
|
|
4871
|
-
easing: resolveInteractionEasing(action.easing, element),
|
|
4872
|
-
}, { ...this.settleOptions(), reverse: true });
|
|
4853
|
+
/**
|
|
4854
|
+
* A count (premium M6, N5) shows its start value once armed, so it can count when it comes into view; armed after
|
|
4855
|
+
* every pre-state, so a number its entrance hides is armed and one the author can already read keeps its value.
|
|
4856
|
+
*/
|
|
4857
|
+
armCounts(node, element) {
|
|
4858
|
+
const armed = armInteractionCounts(node.interactions, action => this.resolveTarget(node, action, element));
|
|
4859
|
+
if (armed.length)
|
|
4860
|
+
this.armedCounts.set(node, armed);
|
|
4873
4861
|
}
|
|
4874
|
-
/**
|
|
4875
|
-
|
|
4876
|
-
|
|
4862
|
+
/**
|
|
4863
|
+
* `itemOn` / `itemOff` (CC2): run when the part's accordion ITEM opens / closes — announced by the shared
|
|
4864
|
+
* composite-core runtime on the item, on the canvas as on the site. An item already open when the
|
|
4865
|
+
* listener attaches has opened: its `itemOn` runs at once (its entrance was pre-stated hidden).
|
|
4866
|
+
*/
|
|
4867
|
+
attachItemState(node, ix, element, on) {
|
|
4868
|
+
const run = () => this.runActions(node, ix.actions, element);
|
|
4869
|
+
const off = onCompositeItemState(element, on, run);
|
|
4870
|
+
const timer = on && isCompositeItemOn(element) ? setTimeout(run, 50) : null;
|
|
4871
|
+
return () => { off(); if (timer)
|
|
4872
|
+
clearTimeout(timer); };
|
|
4877
4873
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
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
|
-
}
|
|
4895
|
-
|
|
4896
|
-
// Virtual trait composition now lives in @vectoriox/iox-ui so the SSR engine (which does NOT depend
|
|
4897
|
-
// on iox-builder) can use the SAME logic — otherwise the engine emitted raw virtual keys
|
|
4898
|
-
// (translate-x, filter-blur…) as invalid CSS. This is a re-export passthrough to keep existing
|
|
4899
|
-
// iox-builder imports (`../style-compose`) working with no duplicated implementation.
|
|
4900
|
-
|
|
4901
|
-
/**
|
|
4902
|
-
* Parts that are HIDDEN AT REST (complex-components R2, board 3): on the live site they are not on
|
|
4903
|
-
* screen until something opens them, so on the canvas they show only while selected into or switched
|
|
4904
|
-
* open ("Showing on canvas"). The layer tree marks them with the eye-slash glyph. A Dialog (the mobile
|
|
4905
|
-
* menu's drawer, a consent banner) by its type; an Accordion's PANEL by its role (CC2 — closed until its
|
|
4906
|
-
* item opens); a Disclosure's panel and its "Read less" word (CC2.1 — shown only while it is open).
|
|
4907
|
-
*/
|
|
4908
|
-
const HIDDEN_AT_REST_TYPES = ['Dialog', 'Popover'];
|
|
4909
|
-
const HIDDEN_AT_REST_ROLES = [AccordionRole.Panel, DisclosureRole.Panel, DisclosureRole.Less];
|
|
4910
|
-
function isHiddenAtRest(node) {
|
|
4911
|
-
return (!!node?.type && HIDDEN_AT_REST_TYPES.includes(node.type)) || (!!node?.role && HIDDEN_AT_REST_ROLES.includes(node.role));
|
|
4912
|
-
}
|
|
4913
|
-
/**
|
|
4914
|
-
* How the selection shows a node hidden at rest — the two approved rules, by what the node IS:
|
|
4915
|
-
* - a PART of a composite (it carries a `role`: the mobile menu's drawer) follows R2 (board 3): the
|
|
4916
|
-
* selection is the reveal, and moving it out of the part hides it again;
|
|
4917
|
-
* - a free-standing overlay (a plain Dialog) follows board A4: selecting it opens it, and it stays open
|
|
4918
|
-
* while the author works elsewhere, until its X, its backdrop or the eye closes it.
|
|
4919
|
-
*/
|
|
4920
|
-
var SelectionReveal;
|
|
4921
|
-
(function (SelectionReveal) {
|
|
4922
|
-
SelectionReveal["WhileSelected"] = "while-selected";
|
|
4923
|
-
SelectionReveal["UntilClosed"] = "until-closed";
|
|
4924
|
-
})(SelectionReveal || (SelectionReveal = {}));
|
|
4925
|
-
/**
|
|
4926
|
-
* Parts hidden at rest that live in a composite's TOGGLE, not in its panel (CC2.1 follow-up, approved 2026-10-21): a
|
|
4927
|
-
* click on one on the canvas toggles the composite live — selecting it never forces the reveal, so "Read less"
|
|
4928
|
-
* closes the disclosure it was clicked in. Only a part inside a PANEL is revealed by the selection.
|
|
4929
|
-
*/
|
|
4930
|
-
const TOGGLE_PART_ROLES = [DisclosureRole.Less];
|
|
4931
|
-
/**
|
|
4932
|
-
* Parts the selection REVEALS though they are not hidden at rest (CC4a): a tab PANEL — one is always on, so the layer
|
|
4933
|
-
* tree draws it no eye-slash (board 3), but selecting anything inside an inactive one shows its tab while selected.
|
|
4934
|
-
*/
|
|
4935
|
-
const REVEALED_WHILE_SELECTED_ROLES = [TabsRole.Panel];
|
|
4936
|
-
function selectionRevealOf(node) {
|
|
4937
|
-
if (!!node?.role && REVEALED_WHILE_SELECTED_ROLES.includes(node.role))
|
|
4938
|
-
return SelectionReveal.WhileSelected;
|
|
4939
|
-
if (!isHiddenAtRest(node) || (!!node?.role && TOGGLE_PART_ROLES.includes(node.role)))
|
|
4940
|
-
return null;
|
|
4941
|
-
return node?.role ? SelectionReveal.WhileSelected : SelectionReveal.UntilClosed;
|
|
4942
|
-
}
|
|
4943
|
-
/** Root → `target`, inclusive, across `roots` (page layout and globals), or [] when it is not there. */
|
|
4944
|
-
function pathToNode(roots, target) {
|
|
4945
|
-
for (const root of roots) {
|
|
4946
|
-
if (root === target)
|
|
4947
|
-
return [root];
|
|
4948
|
-
const below = pathToNode(root.children ?? [], target);
|
|
4949
|
-
if (below.length)
|
|
4950
|
-
return [root, ...below];
|
|
4874
|
+
// ── Triggers ─────────────────────────────────────────────
|
|
4875
|
+
attachPageLoad(node, ix, element) {
|
|
4876
|
+
const timer = setTimeout(() => {
|
|
4877
|
+
if (this.hasPreStatedAncestor(element)) {
|
|
4878
|
+
this.deferUntilVisible(element, () => this.runActions(node, ix.actions, element));
|
|
4879
|
+
}
|
|
4880
|
+
else {
|
|
4881
|
+
this.runActions(node, ix.actions, element);
|
|
4882
|
+
}
|
|
4883
|
+
}, 50);
|
|
4884
|
+
return () => clearTimeout(timer);
|
|
4951
4885
|
}
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
const
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4886
|
+
attachViewportEnter(node, ix, element) {
|
|
4887
|
+
const firedSet = new Set();
|
|
4888
|
+
const fire = () => {
|
|
4889
|
+
const key = ix.id;
|
|
4890
|
+
if (ix.actions.some(a => a.once) && firedSet.has(key))
|
|
4891
|
+
return;
|
|
4892
|
+
if (this.hasPreStatedAncestor(element)) {
|
|
4893
|
+
this.deferUntilVisible(element, () => {
|
|
4894
|
+
if (ix.actions.some(a => a.once) && firedSet.has(key))
|
|
4895
|
+
return;
|
|
4896
|
+
firedSet.add(key);
|
|
4897
|
+
this.runActions(node, ix.actions, element);
|
|
4898
|
+
});
|
|
4899
|
+
return;
|
|
4900
|
+
}
|
|
4901
|
+
firedSet.add(key);
|
|
4902
|
+
this.runActions(node, ix.actions, element);
|
|
4903
|
+
};
|
|
4904
|
+
// IntersectionObserver with no root uses the browser viewport.
|
|
4905
|
+
// It respects overflow:hidden clipping from .preview-scroll so it fires
|
|
4906
|
+
// correctly as Lenis scrolls (Lenis changes scrollTop on .preview-scroll).
|
|
4907
|
+
const observer = new IntersectionObserver((entries) => {
|
|
4908
|
+
for (const entry of entries) {
|
|
4909
|
+
if (entry.isIntersecting) {
|
|
4910
|
+
cleanup();
|
|
4911
|
+
fire();
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
}, { threshold: 0.1 });
|
|
4915
|
+
observer.observe(element);
|
|
4916
|
+
// Builder: also listen to Lenis scroll events on .preview-lenis-content
|
|
4917
|
+
// as a fallback in case IntersectionObserver misses elements behind a
|
|
4918
|
+
// CSS transform (scale) on iox-page-component.
|
|
4919
|
+
const lenisEl = this.overlayService.getScrollContainer();
|
|
4920
|
+
let scrollFired = false;
|
|
4921
|
+
const onScroll = () => {
|
|
4922
|
+
if (scrollFired)
|
|
4923
|
+
return;
|
|
4924
|
+
const rootEl = this.overlayService.getIntersectionRoot();
|
|
4925
|
+
if (!rootEl)
|
|
4926
|
+
return;
|
|
4927
|
+
const rootRect = rootEl.getBoundingClientRect();
|
|
4928
|
+
const elRect = element.getBoundingClientRect();
|
|
4929
|
+
const overlapH = Math.min(elRect.bottom, rootRect.bottom) - Math.max(elRect.top, rootRect.top);
|
|
4930
|
+
const elH = elRect.bottom - elRect.top;
|
|
4931
|
+
if (elH > 0 && overlapH / elH >= 0.1) {
|
|
4932
|
+
scrollFired = true;
|
|
4933
|
+
cleanup();
|
|
4934
|
+
fire();
|
|
4935
|
+
}
|
|
4936
|
+
};
|
|
4937
|
+
if (lenisEl)
|
|
4938
|
+
lenisEl.addEventListener('scroll', onScroll, { passive: true });
|
|
4939
|
+
const cleanup = () => {
|
|
4940
|
+
observer.disconnect();
|
|
4941
|
+
scrollFired = true;
|
|
4942
|
+
if (lenisEl)
|
|
4943
|
+
lenisEl.removeEventListener('scroll', onScroll);
|
|
4944
|
+
};
|
|
4945
|
+
return cleanup;
|
|
4963
4946
|
}
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
function phoneMenuOf(path, globals = []) {
|
|
4976
|
-
if (!inPhoneMenuContext(path))
|
|
4977
|
-
return null;
|
|
4978
|
-
const root = path.find(n => n.role === NavRole.Root) ?? null;
|
|
4979
|
-
const scopes = root ? [root, ...globals] : [...globals];
|
|
4980
|
-
const drawer = path.find(n => n.role === NavRole.Drawer) ?? findRole(scopes, NavRole.Drawer);
|
|
4981
|
-
if (!drawer)
|
|
4982
|
-
return null;
|
|
4983
|
-
const menuButton = findRole(scopes, NavRole.MenuButton);
|
|
4984
|
-
const at = navShapeBreakpoint(scopes);
|
|
4985
|
-
return { root, drawer, menuButton, at };
|
|
4986
|
-
}
|
|
4987
|
-
/** True when the canvas, at `breakpoint`, shows the phone menu that takes over at `at` (that width or narrower). */
|
|
4988
|
-
function phoneMenuShowsAt(at, breakpoint) {
|
|
4989
|
-
return cascadeChain(breakpoint).includes(at);
|
|
4990
|
-
}
|
|
4991
|
-
const OPENER_TRIGGER = 'click';
|
|
4992
|
-
/** The dialog actions a phone menu's opener runs (generation-core stamps Toggle; CC1 menus still hold Open). */
|
|
4993
|
-
var PhoneMenuOpenerAction;
|
|
4994
|
-
(function (PhoneMenuOpenerAction) {
|
|
4995
|
-
PhoneMenuOpenerAction["Open"] = "openDialog";
|
|
4996
|
-
PhoneMenuOpenerAction["Toggle"] = "toggleDialog";
|
|
4997
|
-
})(PhoneMenuOpenerAction || (PhoneMenuOpenerAction = {}));
|
|
4998
|
-
const OPENER_ACTIONS = new Set(Object.values(PhoneMenuOpenerAction));
|
|
4999
|
-
/** True when `interaction` is the phone menu's own opener: the menu button's (or More's) click opening the drawer. */
|
|
5000
|
-
function isPhoneMenuOpener(node, interaction, drawerId) {
|
|
5001
|
-
if (!drawerId || (node?.role !== NavRole.MenuButton && node?.role !== NavRole.More))
|
|
4947
|
+
isBuilderMode() {
|
|
4948
|
+
return document.body.classList.contains('builder-active');
|
|
4949
|
+
}
|
|
4950
|
+
/** Returns true if any DOM ancestor of element is currently faded out via pre-state. */
|
|
4951
|
+
hasPreStatedAncestor(element) {
|
|
4952
|
+
let el = element.parentElement;
|
|
4953
|
+
while (el && el !== document.body) {
|
|
4954
|
+
if (this.preStatedElements.has(el))
|
|
4955
|
+
return true;
|
|
4956
|
+
el = el.parentElement;
|
|
4957
|
+
}
|
|
5002
4958
|
return false;
|
|
5003
|
-
const actions = interaction?.actions ?? [];
|
|
5004
|
-
return interaction?.trigger === OPENER_TRIGGER && actions.length === 1
|
|
5005
|
-
&& OPENER_ACTIONS.has(actions[0].type ?? '') && actions[0].target === drawerId;
|
|
5006
|
-
}
|
|
5007
|
-
|
|
5008
|
-
/** What a hidden-at-rest part shows on the canvas when the author picks it in "Showing on canvas". */
|
|
5009
|
-
var CanvasShowState;
|
|
5010
|
-
(function (CanvasShowState) {
|
|
5011
|
-
CanvasShowState["Closed"] = "closed";
|
|
5012
|
-
CanvasShowState["Open"] = "open";
|
|
5013
|
-
})(CanvasShowState || (CanvasShowState = {}));
|
|
5014
|
-
const EMPTY_SNAPSHOT = () => ({
|
|
5015
|
-
revealed: new Set(), selectedTriggers: new Set(), pinned: new Set(), triggeredBySelection: new Set(), shown: new Map(), items: new Map(),
|
|
5016
|
-
runtime: new Set(), menu: null, tooltips: new Set(), toast: null,
|
|
5017
|
-
});
|
|
5018
|
-
/**
|
|
5019
|
-
* The canvas's DESIGN state (complex-components R2; composite plan §5a's CompositeDesignStateService,
|
|
5020
|
-
* started here for the navigation and reused by Accordion in CC2): which parts that are hidden at rest
|
|
5021
|
-
* — a closed drawer today, a collapsed panel or an inactive tab next — the canvas shows right now.
|
|
5022
|
-
*
|
|
5023
|
-
* Two sources, the Dialog's "selection is the reveal" and the Form's switcher, generalised:
|
|
5024
|
-
* - **the selection** — selecting anything inside a hidden part (tree, breadcrumb, an Items row,
|
|
5025
|
-
* SELECT_NODE) shows it; deselecting hides it again (`setSelectionPath`);
|
|
5026
|
-
* - **"Showing on canvas"** — the Bindings panel's switcher, for the one case a selection cannot express:
|
|
5027
|
-
* the page with the menu open while a part OUTSIDE it is selected (`setShown`).
|
|
5028
|
-
*
|
|
5029
|
-
* Canvas-only: never saved, never a history step. Also carries the canvas page's path, so a link to the
|
|
5030
|
-
* page being edited renders as the current page (N2) — the same decision the live site makes.
|
|
5031
|
-
*/
|
|
5032
|
-
class CanvasDesignStateService {
|
|
5033
|
-
constructor() {
|
|
5034
|
-
this._state$ = new BehaviorSubject(EMPTY_SNAPSHOT());
|
|
5035
|
-
this.state$ = this._state$.asObservable();
|
|
5036
|
-
this._pagePath$ = new BehaviorSubject(null);
|
|
5037
|
-
/** The app path of the page on the canvas (`/work`), or null when unknown. */
|
|
5038
|
-
this.pagePath$ = this._pagePath$.asObservable();
|
|
5039
4959
|
}
|
|
5040
|
-
|
|
5041
|
-
|
|
4960
|
+
/** Polls via RAF until no ancestor is in pre-state, then fires callback. */
|
|
4961
|
+
deferUntilVisible(element, callback) {
|
|
4962
|
+
const poll = () => {
|
|
4963
|
+
if (!this.hasPreStatedAncestor(element)) {
|
|
4964
|
+
callback();
|
|
4965
|
+
}
|
|
4966
|
+
else {
|
|
4967
|
+
requestAnimationFrame(poll);
|
|
4968
|
+
}
|
|
4969
|
+
};
|
|
4970
|
+
requestAnimationFrame(poll);
|
|
4971
|
+
}
|
|
4972
|
+
attachClick(node, ix, element) {
|
|
4973
|
+
// G7: press FEEDBACK (an animation of the pressed element that hides and shows nothing) lands on pointer-down,
|
|
4974
|
+
// at once; the rest commits on the click. A toggle keeps its whole list on the click (the shared split, iox-ui).
|
|
4975
|
+
const { onPress, onClick } = ix.reverseActions?.length ? { onPress: [], onClick: ix.actions } : splitPressFeedback(ix.actions);
|
|
4976
|
+
const handler = () => {
|
|
4977
|
+
if (ix.reverseActions?.length) {
|
|
4978
|
+
// Toggle: alternate between primary and reverse actions on each click.
|
|
4979
|
+
const isToggled = this.toggleStates.get(ix.id) ?? false;
|
|
4980
|
+
this.runActions(node, isToggled ? ix.reverseActions : ix.actions, element);
|
|
4981
|
+
this.toggleStates.set(ix.id, !isToggled);
|
|
4982
|
+
}
|
|
4983
|
+
else {
|
|
4984
|
+
this.runActions(node, onClick, element);
|
|
4985
|
+
}
|
|
4986
|
+
};
|
|
4987
|
+
const press = (e) => { if (e.button === 0 || e.pointerType !== 'mouse')
|
|
4988
|
+
this.runActions(node, onPress, element); };
|
|
4989
|
+
// CAPTURE, like the composite runtime: on the canvas every part's own listener stops the click to select
|
|
4990
|
+
// that part, so a click on a button's LABEL never bubbled to the button's click interaction — the live
|
|
4991
|
+
// site runs it (CC2.1: "Open an item" on a ButtonBlock did nothing on the canvas).
|
|
4992
|
+
element.addEventListener('click', handler, true);
|
|
4993
|
+
if (onPress.length)
|
|
4994
|
+
element.addEventListener('pointerdown', press, true);
|
|
4995
|
+
return () => {
|
|
4996
|
+
element.removeEventListener('click', handler, true);
|
|
4997
|
+
element.removeEventListener('pointerdown', press, true);
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
5000
|
+
attachHover(node, ix, element) {
|
|
5001
|
+
// G7: a hover trigger answers a mouse or a pen — a finger's emulated hover stuck until the next tap elsewhere.
|
|
5002
|
+
const enterHandler = (e) => { if (isHoverPointer(e))
|
|
5003
|
+
this.runActions(node, ix.actions, element); };
|
|
5004
|
+
const leaveHandler = (e) => {
|
|
5005
|
+
if (!isHoverPointer(e))
|
|
5006
|
+
return;
|
|
5007
|
+
// Reverse animations on leave for hover triggers
|
|
5008
|
+
for (const action of ix.actions) {
|
|
5009
|
+
if (!isActionEnabled(action))
|
|
5010
|
+
continue;
|
|
5011
|
+
// Context actions have no "backwards" — the shared table decides what leave does
|
|
5012
|
+
// (clear, unless the action sets noReverse). Same call the SSR engine makes.
|
|
5013
|
+
if (isContextAction(action)) {
|
|
5014
|
+
this.applyContextAction(action, element, 'reverse');
|
|
5015
|
+
continue;
|
|
5016
|
+
}
|
|
5017
|
+
const target = this.resolveTarget(node, action, element);
|
|
5018
|
+
if (!target)
|
|
5019
|
+
continue;
|
|
5020
|
+
this.reverseAction(target, action);
|
|
5021
|
+
}
|
|
5022
|
+
};
|
|
5023
|
+
element.addEventListener('pointerenter', enterHandler);
|
|
5024
|
+
element.addEventListener('pointerleave', leaveHandler);
|
|
5025
|
+
return () => {
|
|
5026
|
+
element.removeEventListener('pointerenter', enterHandler);
|
|
5027
|
+
element.removeEventListener('pointerleave', leaveHandler);
|
|
5028
|
+
};
|
|
5029
|
+
}
|
|
5030
|
+
// ── Actions ──────────────────────────────────────────────
|
|
5031
|
+
runActions(node, actions, selfEl) {
|
|
5032
|
+
for (const action of actions) {
|
|
5033
|
+
if (!isActionEnabled(action))
|
|
5034
|
+
continue; // switched off by the author
|
|
5035
|
+
if (this.SWIPER_ACTIONS.has(action.type)) {
|
|
5036
|
+
this.executeSwiperAction(action);
|
|
5037
|
+
continue;
|
|
5038
|
+
}
|
|
5039
|
+
// Dialog actions (A1b): the canvas RUNS them — styling a dialog you cannot open is
|
|
5040
|
+
// impossible. The canvas twin registered open/close/toggle under its nodeId, exactly
|
|
5041
|
+
// the Swiper-method pattern; a missing/mistargeted id is a silent no-op, never a throw.
|
|
5042
|
+
if (DIALOG_ACTION_TYPES.has(action.type)) {
|
|
5043
|
+
this.executeDialogAction(action, selfEl);
|
|
5044
|
+
continue;
|
|
5045
|
+
}
|
|
5046
|
+
// Popover actions (premium M5): the canvas RUNS them too, through the twin's registry — beside the element
|
|
5047
|
+
// whose click ran them (the opener the live runtime anchors to).
|
|
5048
|
+
if (POPOVER_ACTION_TYPE_SET.has(action.type)) {
|
|
5049
|
+
this.executePopoverAction(action, selfEl);
|
|
5050
|
+
continue;
|
|
5051
|
+
}
|
|
5052
|
+
// A toast (premium M5): RUN inside the page's frame — the layer of the token scope the element sits in (the
|
|
5053
|
+
// page, or the globals' overlay), so it wears the look and zooms with the canvas.
|
|
5054
|
+
if (TOAST_ACTION_TYPES.has(action.type)) {
|
|
5055
|
+
showCanvasToast(action.params?.['text'], action.params?.['edge'], selfEl ?? null);
|
|
5056
|
+
continue;
|
|
5057
|
+
}
|
|
5058
|
+
// Item actions (CC2.1 — composite T14): the canvas RUNS them on the target item's own runtime — the
|
|
5059
|
+
// one the live site runs — and brings an item that opened out of view into it.
|
|
5060
|
+
const itemAction = itemActionOf(action.type);
|
|
5061
|
+
if (itemAction) {
|
|
5062
|
+
this.executeItemAction(node, action, itemAction, selfEl);
|
|
5063
|
+
continue;
|
|
5064
|
+
}
|
|
5065
|
+
// Tier-A behaviour (A1c). The canvas-is-live rules are catalog DATA
|
|
5066
|
+
// (ELEMENT_ACTION_DESCRIPTORS): copy runs, scrollTo runs (scrollIntoView — the frame
|
|
5067
|
+
// scrolls natively here; the Lenis rule is the LIVE side's), navigate and submitForm
|
|
5068
|
+
// SIMULATE — a dispatched event the host surfaces as a toast, never a route change
|
|
5069
|
+
// or a fake submit under the author's feet.
|
|
5070
|
+
if (BEHAVIOUR_ACTION_TYPES.has(action.type)) {
|
|
5071
|
+
this.executeBehaviourAction(node, action, selfEl);
|
|
5072
|
+
continue;
|
|
5073
|
+
}
|
|
5074
|
+
// Tier-B server actions (A2). ALWAYS a simulation here: the canvas is the ADMIN's
|
|
5075
|
+
// browser and a server action is a public-site write (an email, a webhook, a content
|
|
5076
|
+
// row) — the toast says what the LIVE site would do.
|
|
5077
|
+
if (SERVER_ELEMENT_ACTION_TYPES.has(action.type)) {
|
|
5078
|
+
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
5079
|
+
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
5080
|
+
continue;
|
|
5081
|
+
}
|
|
5082
|
+
// Consent actions (C3). Recording ones SIMULATE — the canvas is the ADMIN's browser,
|
|
5083
|
+
// and an admin's cookie is not a visitor's decision; the toast says what would be
|
|
5084
|
+
// recorded. Opening the preferences dialog is dialog-opening and honestly RUNS.
|
|
5085
|
+
if (CONSENT_ACTION_TYPES.has(action.type)) {
|
|
5086
|
+
this.executeConsentAction(action);
|
|
5087
|
+
continue;
|
|
5088
|
+
}
|
|
5089
|
+
// Context actions write STATE, not animation. `selfEl` is deliberate: $item is read by
|
|
5090
|
+
// walking up from the element actually hovered/clicked, which for a repeater PREVIEW ROW
|
|
5091
|
+
// is the clone — so each row publishes its own item, exactly as on the live site.
|
|
5092
|
+
if (isContextAction(action)) {
|
|
5093
|
+
this.applyContextAction(action, selfEl ?? null, 'forward');
|
|
5094
|
+
continue;
|
|
5095
|
+
}
|
|
5096
|
+
// selfEl lets 'self'-targeted actions resolve for preview CLONES, which aren't
|
|
5097
|
+
// overlay-registered (getNodeRef would return undefined) — see attachTo. (issue #2)
|
|
5098
|
+
const target = this.resolveTarget(node, action, selfEl);
|
|
5099
|
+
if (!target)
|
|
5100
|
+
continue;
|
|
5101
|
+
this.executeAction(target, action);
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
/**
|
|
5105
|
+
* Execute a context action against the shared store. The forward/reverse semantics come from
|
|
5106
|
+
* `contextActionEffect` in @vectoriox/iox-ui — the same function the SSR engine calls, so the
|
|
5107
|
+
* builder canvas and the live site behave identically.
|
|
5108
|
+
*/
|
|
5109
|
+
applyContextAction(action, el, pass) {
|
|
5110
|
+
const effect = contextActionEffect(action, pass);
|
|
5111
|
+
if (!effect)
|
|
5112
|
+
return;
|
|
5113
|
+
if (effect.op === 'clear') {
|
|
5114
|
+
this.contextStore.clear(effect.slot);
|
|
5115
|
+
return;
|
|
5116
|
+
}
|
|
5117
|
+
const value = resolveContextValue(effect.value, {
|
|
5118
|
+
item: this.rowItemFor(el),
|
|
5119
|
+
resolvedData: this.contextData,
|
|
5120
|
+
});
|
|
5121
|
+
this.contextStore.set(effect.slot, value);
|
|
5122
|
+
}
|
|
5123
|
+
/** The repeater item this element belongs to — the nearest marked row ancestor. */
|
|
5124
|
+
rowItemFor(el) {
|
|
5125
|
+
const row = el?.closest?.('[data-iox-row-idx]');
|
|
5126
|
+
if (!row)
|
|
5127
|
+
return undefined;
|
|
5128
|
+
const source = row.getAttribute('data-iox-row-src') ?? '';
|
|
5129
|
+
const index = Number(row.getAttribute('data-iox-row-idx'));
|
|
5130
|
+
const items = this.contextData?.[source];
|
|
5131
|
+
return Array.isArray(items) && Number.isInteger(index) ? items[index] : undefined;
|
|
5042
5132
|
}
|
|
5043
|
-
|
|
5044
|
-
|
|
5133
|
+
/** Resolved data for the canvas, kept current by the repeater as it fetches. */
|
|
5134
|
+
setContextData(data) {
|
|
5135
|
+
// MERGE, not replace — a page can hold several repeaters, each publishing its own source.
|
|
5136
|
+
this.contextData = { ...this.contextData, ...(data ?? {}) };
|
|
5045
5137
|
}
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5138
|
+
executeBehaviourAction(node, action, selfEl) {
|
|
5139
|
+
switch (action.type) {
|
|
5140
|
+
case 'copyToClipboard': {
|
|
5141
|
+
const text = String(action.params?.['text'] ?? '');
|
|
5142
|
+
if (text)
|
|
5143
|
+
navigator.clipboard?.writeText(text).catch(() => { });
|
|
5144
|
+
return;
|
|
5145
|
+
}
|
|
5146
|
+
case 'scrollTo': {
|
|
5147
|
+
const target = this.resolveTarget(node, action, selfEl);
|
|
5148
|
+
target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
5149
|
+
return;
|
|
5150
|
+
}
|
|
5151
|
+
case 'navigate':
|
|
5152
|
+
case 'submitForm': {
|
|
5153
|
+
// Simulated: the host (the admin app) listens and shows "would …" as a toast.
|
|
5154
|
+
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
5155
|
+
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
5156
|
+
return;
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5050
5159
|
}
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
*/
|
|
5056
|
-
setSelectionPath(path, globals = []) {
|
|
5057
|
-
const ids = (reveal) => path.filter(n => selectionRevealOf(n) === reveal).map(n => n.id).filter((id) => !!id);
|
|
5058
|
-
const revealed = new Set(ids(SelectionReveal.WhileSelected));
|
|
5059
|
-
const selectedTriggers = new Set(path.filter(n => isExclusiveTrigger(n)).map(n => n.id).filter((id) => !!id));
|
|
5060
|
-
const triggeredBySelection = new Set(path.map(displayTriggerOf).filter((id) => !!id));
|
|
5061
|
-
const { revealed: was, pinned: held, selectedTriggers: wasTriggers, triggeredBySelection: wasTriggered } = this._state$.value;
|
|
5062
|
-
const pinned = new Set([...held, ...ids(SelectionReveal.UntilClosed)]);
|
|
5063
|
-
const menu = phoneMenuOf(path, globals);
|
|
5064
|
-
if (sameSet(revealed, was) && sameSet(pinned, held) && sameSet(selectedTriggers, wasTriggers) && sameSet(triggeredBySelection, wasTriggered)
|
|
5065
|
-
&& sameMenu(menu, this._state$.value.menu))
|
|
5160
|
+
executeItemAction(node, action, itemAction, selfEl) {
|
|
5161
|
+
const target = this.resolveTarget(node, action, selfEl);
|
|
5162
|
+
const open = runCompositeItemAction(target, itemAction);
|
|
5163
|
+
if (!open || !target)
|
|
5066
5164
|
return;
|
|
5067
|
-
|
|
5165
|
+
if (shouldScrollToItem(target.getBoundingClientRect().top, window.innerHeight))
|
|
5166
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
5068
5167
|
}
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5168
|
+
executeConsentAction(action) {
|
|
5169
|
+
if (action.type === 'openConsentPreferences') {
|
|
5170
|
+
const dialog = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
5171
|
+
dialog?.open?.();
|
|
5073
5172
|
return;
|
|
5074
|
-
|
|
5075
|
-
|
|
5173
|
+
}
|
|
5174
|
+
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
5175
|
+
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
5176
|
+
}
|
|
5177
|
+
executeDialogAction(action, selfEl) {
|
|
5178
|
+
const dialog = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
5179
|
+
if (!dialog)
|
|
5076
5180
|
return;
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5181
|
+
switch (action.type) {
|
|
5182
|
+
case 'openDialog':
|
|
5183
|
+
dialog.open?.();
|
|
5184
|
+
// CC3 "Open at the clicked item": the lightbox's slider goes to the clicked row's slide — the same
|
|
5185
|
+
// rule the live site runs (composite-core `slideIndexForRow`), once the canvas twin has rendered it.
|
|
5186
|
+
if (opensAtClickedItem(action))
|
|
5187
|
+
this.slideDialogToRow(action.target, rowIndexOf$1(selfEl));
|
|
5188
|
+
break;
|
|
5189
|
+
case 'closeDialog':
|
|
5190
|
+
dialog.close?.();
|
|
5191
|
+
break;
|
|
5192
|
+
case 'toggleDialog':
|
|
5193
|
+
dialog.toggle?.();
|
|
5194
|
+
break;
|
|
5195
|
+
}
|
|
5086
5196
|
}
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
if (!
|
|
5197
|
+
executePopoverAction(action, selfEl) {
|
|
5198
|
+
const popover = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
5199
|
+
if (!popover)
|
|
5090
5200
|
return;
|
|
5091
|
-
|
|
5201
|
+
switch (action.type) {
|
|
5202
|
+
case 'openPopover':
|
|
5203
|
+
popover.open?.(selfEl ?? null);
|
|
5204
|
+
break;
|
|
5205
|
+
case 'closePopover':
|
|
5206
|
+
popover.close?.();
|
|
5207
|
+
break;
|
|
5208
|
+
case 'togglePopover':
|
|
5209
|
+
popover.toggle?.(selfEl ?? null);
|
|
5210
|
+
break;
|
|
5211
|
+
}
|
|
5092
5212
|
}
|
|
5093
|
-
/** The
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5213
|
+
/** The slider inside the opened dialog (its canvas twin renders on the next passes) → the row's slide. */
|
|
5214
|
+
slideDialogToRow(dialogId, row) {
|
|
5215
|
+
let tries = 0;
|
|
5216
|
+
const attempt = () => {
|
|
5217
|
+
const host = document.querySelector(`.iox-node-${CSS.escape(dialogId)}`);
|
|
5218
|
+
const swiper = host?.querySelector('.swiper')?.swiper;
|
|
5219
|
+
if (swiper?.slides?.length) {
|
|
5220
|
+
swiper.update?.();
|
|
5221
|
+
swiper.slideTo(slideIndexForRow(row, swiper.slides.length), 0);
|
|
5222
|
+
return;
|
|
5223
|
+
}
|
|
5224
|
+
if (++tries < 40)
|
|
5225
|
+
requestAnimationFrame(attempt);
|
|
5226
|
+
};
|
|
5227
|
+
requestAnimationFrame(attempt);
|
|
5101
5228
|
}
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5229
|
+
executeSwiperAction(action) {
|
|
5230
|
+
// Target is the nodeId of the SliderContainer to control.
|
|
5231
|
+
const swiper = this.instanceRegistry.get(action.target);
|
|
5232
|
+
if (!swiper)
|
|
5105
5233
|
return;
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5234
|
+
switch (action.type) {
|
|
5235
|
+
case 'swiperNext':
|
|
5236
|
+
swiper.slideNext();
|
|
5237
|
+
break;
|
|
5238
|
+
case 'swiperPrev':
|
|
5239
|
+
swiper.slidePrev();
|
|
5240
|
+
break;
|
|
5241
|
+
case 'swiperGoToSlide':
|
|
5242
|
+
swiper.slideTo(action.params?.['index'] ?? 0);
|
|
5243
|
+
break;
|
|
5244
|
+
case 'swiperAutoplayStart':
|
|
5245
|
+
swiper.autoplay?.start();
|
|
5246
|
+
break;
|
|
5247
|
+
case 'swiperAutoplayStop':
|
|
5248
|
+
swiper.autoplay?.stop();
|
|
5249
|
+
break;
|
|
5250
|
+
case 'swiperAutoplayPause':
|
|
5251
|
+
swiper.autoplay?.pause();
|
|
5252
|
+
break;
|
|
5253
|
+
}
|
|
5116
5254
|
}
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5255
|
+
resolveTarget(ownerNode, action, selfEl) {
|
|
5256
|
+
if (action.target === 'self' || !action.target) {
|
|
5257
|
+
return selfEl ?? this.overlayService.getNodeRef(ownerNode)?.element ?? null;
|
|
5258
|
+
}
|
|
5259
|
+
// Find by node ID
|
|
5260
|
+
for (const [n, ref] of this.overlayService.getAllNodeEntries()) {
|
|
5261
|
+
if (n.id === action.target)
|
|
5262
|
+
return ref.element;
|
|
5263
|
+
}
|
|
5264
|
+
return null;
|
|
5120
5265
|
}
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
const
|
|
5126
|
-
|
|
5266
|
+
executeAction(element, action) {
|
|
5267
|
+
// toggleVisibility HIDES a visible element and SHOWS a hidden one — resolved against the element's
|
|
5268
|
+
// own state, the same rule the live site applies (shared `effectiveActionType`, CC1 N5).
|
|
5269
|
+
const type = effectiveActionType(action.type, element.hasAttribute(INTERACTION_HIDDEN_ATTRIBUTE));
|
|
5270
|
+
const keyframes = this.keyframesFor(type, action);
|
|
5271
|
+
// A count has no frames (its digits are written from its own clock) and is still an action.
|
|
5272
|
+
if (!keyframes.length && type !== COUNT_UP_ACTION)
|
|
5273
|
+
return;
|
|
5274
|
+
if (this.ENTRANCE_TYPES.has(type))
|
|
5275
|
+
element.removeAttribute(INTERACTION_HIDDEN_ATTRIBUTE);
|
|
5276
|
+
else if (this.EXIT_TYPES.has(type))
|
|
5277
|
+
element.setAttribute(INTERACTION_HIDDEN_ATTRIBUTE, '');
|
|
5278
|
+
// Reduced motion (CC1 R1): no animation runs — the element is left as the run would leave it (the shared settle
|
|
5279
|
+
// rule: an entrance releases to the stylesheet, an exit holds its hidden frame).
|
|
5280
|
+
if (!motionAllowed()) {
|
|
5281
|
+
settleInteraction(element, keyframes, settleModeFor(type), this.settleOptions());
|
|
5282
|
+
this.settleAfter(element, type);
|
|
5283
|
+
return;
|
|
5284
|
+
}
|
|
5285
|
+
// The ONE call the live site makes too: fill both (holds the first frame through a delay), started from the live
|
|
5286
|
+
// value, settled by the shared rule — an element that has entered keeps its hover and press (iox-ui#41). A look
|
|
5287
|
+
// entrance on a Repeater's template staggers its rows: each copy adds its row's share (shared `entranceDelay`).
|
|
5288
|
+
const anim = playInteraction(element, { type, params: action.params }, keyframes, {
|
|
5289
|
+
duration: action.duration,
|
|
5290
|
+
delay: entranceDelay(action, repeatedRowIndex(element)),
|
|
5291
|
+
// A keyword, a curve, or the look's easing token as this element sees it (G6) — the live site's resolver.
|
|
5292
|
+
easing: resolveInteractionEasing(action.easing, element),
|
|
5293
|
+
}, this.settleOptions());
|
|
5294
|
+
anim.finished.then(() => this.settleAfter(element, type)).catch(() => { });
|
|
5127
5295
|
}
|
|
5128
|
-
/**
|
|
5129
|
-
|
|
5130
|
-
return
|
|
5296
|
+
/** On the canvas a run never writes a transform inline (one inside the scaled page breaks Lenis). */
|
|
5297
|
+
settleOptions() {
|
|
5298
|
+
return this.isBuilderMode() ? { skip: CANVAS_SKIPPED_PROPS } : {};
|
|
5131
5299
|
}
|
|
5132
|
-
/**
|
|
5133
|
-
|
|
5134
|
-
|
|
5300
|
+
/** What an element is left as once an action is done: a revealed one interactive, a hidden one inert. */
|
|
5301
|
+
settleAfter(element, type) {
|
|
5302
|
+
if (this.ENTRANCE_TYPES.has(type)) {
|
|
5303
|
+
element.style.removeProperty('pointer-events');
|
|
5304
|
+
this.preStatedElements.delete(element);
|
|
5305
|
+
// Builder: clear inline pre-state so cancelling animations later
|
|
5306
|
+
// doesn't leave the element invisible after a play preview.
|
|
5307
|
+
if (this.isBuilderMode())
|
|
5308
|
+
this.clearInlineAnimationStyles(element);
|
|
5309
|
+
}
|
|
5310
|
+
else if (this.EXIT_TYPES.has(type)) {
|
|
5311
|
+
// Once the exit is done the element is invisible — disable pointer events so it doesn't
|
|
5312
|
+
// silently block clicks underneath it.
|
|
5313
|
+
element.style.setProperty('pointer-events', 'none');
|
|
5314
|
+
}
|
|
5135
5315
|
}
|
|
5136
|
-
/**
|
|
5137
|
-
|
|
5138
|
-
|
|
5316
|
+
/**
|
|
5317
|
+
* Freeze an element at the animation's starting frame immediately, before any
|
|
5318
|
+
* trigger fires. Called for ALL entrance-type actions regardless of trigger so
|
|
5319
|
+
* that a click-triggered fadeIn on a menu overlay also starts it hidden.
|
|
5320
|
+
*
|
|
5321
|
+
* In builder mode only opacity:0 is applied — transforms on canvas elements
|
|
5322
|
+
* inside iox-page-component's scale() transform break Lenis scroll. The transform
|
|
5323
|
+
* jump at animation t=0 is invisible because the element is already opacity:0.
|
|
5324
|
+
* In production the full keyframe is applied + pointer-events:none.
|
|
5325
|
+
*/
|
|
5326
|
+
applyPreState(element, action) {
|
|
5327
|
+
const keyframes = this.keyframesFor(action.type, action);
|
|
5328
|
+
if (!keyframes.length)
|
|
5139
5329
|
return;
|
|
5140
|
-
const
|
|
5141
|
-
if (
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5330
|
+
const first = entrancePreStateStyle(action.type, action.params);
|
|
5331
|
+
if (this.isBuilderMode()) {
|
|
5332
|
+
// No transforms — inline transforms on children inside iox-page-component's
|
|
5333
|
+
// scale() break Lenis scroll. pointer-events:none lets wheel events and
|
|
5334
|
+
// hover fall through to visible elements underneath. An entrance that hides
|
|
5335
|
+
// nothing (a picture settling) stays in view.
|
|
5336
|
+
if ('opacity' in first || 'visibility' in first)
|
|
5337
|
+
element.style.setProperty('opacity', '0');
|
|
5338
|
+
element.style.setProperty('pointer-events', 'none');
|
|
5339
|
+
}
|
|
5340
|
+
else {
|
|
5341
|
+
for (const [prop, val] of Object.entries(first))
|
|
5342
|
+
element.style.setProperty(prop, val);
|
|
5343
|
+
element.style.setProperty('pointer-events', 'none');
|
|
5344
|
+
}
|
|
5345
|
+
element.setAttribute(INTERACTION_HIDDEN_ATTRIBUTE, '');
|
|
5346
|
+
this.preStatedElements.add(element);
|
|
5146
5347
|
}
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5348
|
+
clearInlineAnimationStyles(element) {
|
|
5349
|
+
element.style.removeProperty('opacity');
|
|
5350
|
+
element.style.removeProperty('transform');
|
|
5351
|
+
element.style.removeProperty('clip-path');
|
|
5352
|
+
element.style.removeProperty('visibility');
|
|
5353
|
+
element.style.removeProperty('pointer-events');
|
|
5354
|
+
element.removeAttribute(INTERACTION_HIDDEN_ATTRIBUTE);
|
|
5150
5355
|
}
|
|
5151
|
-
/**
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5356
|
+
/** A hover-out: the action played back from where the element IS (never from its end frame), settled by the shared
|
|
5357
|
+
* rule — played back to hidden, it holds hidden; to shown, it releases. */
|
|
5358
|
+
reverseAction(element, action) {
|
|
5359
|
+
const type = effectiveActionType(action.type, element.hasAttribute(INTERACTION_HIDDEN_ATTRIBUTE));
|
|
5360
|
+
const keyframes = this.keyframesFor(type, action);
|
|
5361
|
+
if (!keyframes.length)
|
|
5155
5362
|
return;
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5363
|
+
const frames = [...keyframes].reverse();
|
|
5364
|
+
if (!motionAllowed()) {
|
|
5365
|
+
settleInteraction(element, frames, settleModeFor(type, true), this.settleOptions());
|
|
5366
|
+
return;
|
|
5367
|
+
}
|
|
5368
|
+
playInteraction(element, { type, params: action.params }, frames, {
|
|
5369
|
+
duration: action.duration,
|
|
5370
|
+
easing: resolveInteractionEasing(action.easing, element),
|
|
5371
|
+
}, { ...this.settleOptions(), reverse: true });
|
|
5161
5372
|
}
|
|
5162
|
-
/**
|
|
5163
|
-
|
|
5164
|
-
|
|
5373
|
+
/** The action's keyframes from the ONE shared table both runtimes animate from (composite T2). */
|
|
5374
|
+
keyframesFor(type, action) {
|
|
5375
|
+
return buildInteractionKeyframes(type, action.params) ?? [];
|
|
5165
5376
|
}
|
|
5166
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type:
|
|
5167
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type:
|
|
5377
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, deps: [{ token: OverlayService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5378
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService }); }
|
|
5168
5379
|
}
|
|
5169
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type:
|
|
5380
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, decorators: [{
|
|
5170
5381
|
type: Injectable
|
|
5171
|
-
}] });
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
function sameSet(a, b) {
|
|
5178
|
-
return a.size === b.size && [...a].every(id => b.has(id));
|
|
5382
|
+
}], ctorParameters: () => [{ type: OverlayService }] });
|
|
5383
|
+
/** The canvas's token scopes: the page frame and the globals' overlay (each declares the org's tokens). */
|
|
5384
|
+
const CANVAS_TOKEN_SCOPE = '.iox-token-scope';
|
|
5385
|
+
/** The floating layer's home on the canvas for an element: the token scope (the zoomed frame) it sits in. */
|
|
5386
|
+
function canvasFloatingContainer(from) {
|
|
5387
|
+
return from?.closest?.(CANVAS_TOKEN_SCOPE) ?? from?.ownerDocument?.querySelector(CANVAS_TOKEN_SCOPE) ?? null;
|
|
5179
5388
|
}
|
|
5180
|
-
/**
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
* opener), so a drawer shown because the selection sits in it closes too.
|
|
5185
|
-
*/
|
|
5186
|
-
function setOverlayPreview(state, twins, id, open) {
|
|
5187
|
-
if (!id)
|
|
5188
|
-
return;
|
|
5189
|
-
if (open) {
|
|
5190
|
-
state.setShown(id, CanvasShowState.Open);
|
|
5191
|
-
return;
|
|
5192
|
-
}
|
|
5193
|
-
const twin = twins?.get(id);
|
|
5194
|
-
if (twin?.close)
|
|
5195
|
-
twin.close();
|
|
5196
|
-
else
|
|
5197
|
-
state.dismiss(id);
|
|
5389
|
+
/** Raise a toast on the canvas (a showToast action the canvas runs): inside the frame, never over the builder. */
|
|
5390
|
+
function showCanvasToast(text, edge, from) {
|
|
5391
|
+
const doc = from?.ownerDocument ?? (typeof document !== 'undefined' ? document : null);
|
|
5392
|
+
showToast(doc, typeof text === 'string' ? text : '', { edge: typeof edge === 'string' ? edge : null, from, topLayer: false, container: canvasFloatingContainer(from) });
|
|
5198
5393
|
}
|
|
5199
5394
|
|
|
5395
|
+
// Virtual trait composition now lives in @vectoriox/iox-ui so the SSR engine (which does NOT depend
|
|
5396
|
+
// on iox-builder) can use the SAME logic — otherwise the engine emitted raw virtual keys
|
|
5397
|
+
// (translate-x, filter-blur…) as invalid CSS. This is a re-export passthrough to keep existing
|
|
5398
|
+
// iox-builder imports (`../style-compose`) working with no duplicated implementation.
|
|
5399
|
+
|
|
5200
5400
|
/** The node whose "Showing on canvas" choice a rendered root follows: itself, or — a preview row's copy — its
|
|
5201
5401
|
* template (a copy shares its template's style id). */
|
|
5202
5402
|
function designIdOf(node) {
|
|
@@ -5222,11 +5422,12 @@ class CompositeCanvasService {
|
|
|
5222
5422
|
this.zone = zone;
|
|
5223
5423
|
this.attached = new Map();
|
|
5224
5424
|
this.exclusive = new Map();
|
|
5425
|
+
this.marquees = new Map();
|
|
5225
5426
|
this.sub = designState?.state$.subscribe(() => this.applyDesignState()) ?? null;
|
|
5226
5427
|
}
|
|
5227
5428
|
ngOnDestroy() {
|
|
5228
5429
|
this.sub?.unsubscribe();
|
|
5229
|
-
for (const id of [...this.attached.keys(), ...this.exclusive.keys()])
|
|
5430
|
+
for (const id of [...this.attached.keys(), ...this.exclusive.keys(), ...this.marquees.keys()])
|
|
5230
5431
|
this.detach(id);
|
|
5231
5432
|
}
|
|
5232
5433
|
/** A composite root rendered on the canvas (the template, or a preview row's copy): run it. `element` is the
|
|
@@ -5235,6 +5436,11 @@ class CompositeCanvasService {
|
|
|
5235
5436
|
if (!node.id)
|
|
5236
5437
|
return;
|
|
5237
5438
|
this.detach(node.id);
|
|
5439
|
+
if (isMarqueeRoot(node)) {
|
|
5440
|
+
this.marquees.set(node.id, { node, element, runtime: null });
|
|
5441
|
+
this.applyMarquee(this.marquees.get(node.id));
|
|
5442
|
+
return;
|
|
5443
|
+
}
|
|
5238
5444
|
const cssId = node.styleId ?? node.id;
|
|
5239
5445
|
const rootId = () => compositeScopeId(cssId, compositeRowKeys(element));
|
|
5240
5446
|
if (isExclusiveRoot(node)) {
|
|
@@ -5280,6 +5486,12 @@ class CompositeCanvasService {
|
|
|
5280
5486
|
tabs?.runtime.destroy();
|
|
5281
5487
|
tabs?.release();
|
|
5282
5488
|
this.exclusive.delete(nodeId);
|
|
5489
|
+
this.marquees.get(nodeId)?.runtime?.destroy();
|
|
5490
|
+
this.marquees.delete(nodeId);
|
|
5491
|
+
}
|
|
5492
|
+
/** The runtime of a Marquee moving on the canvas, or null while it stands still. */
|
|
5493
|
+
marqueeRuntimeOf(nodeId) {
|
|
5494
|
+
return (nodeId && this.marquees.get(nodeId)?.runtime) || null;
|
|
5283
5495
|
}
|
|
5284
5496
|
/** The root's behaviour changed in its panel (Allow several open, Open by default; Activate on, Orientation). */
|
|
5285
5497
|
refreshBehaviour(node) {
|
|
@@ -5316,6 +5528,18 @@ class CompositeCanvasService {
|
|
|
5316
5528
|
}
|
|
5317
5529
|
for (const entry of this.exclusive.values())
|
|
5318
5530
|
this.applyExclusive(entry);
|
|
5531
|
+
for (const entry of this.marquees.values())
|
|
5532
|
+
this.applyMarquee(entry);
|
|
5533
|
+
}
|
|
5534
|
+
/** Moving while "Showing on canvas" says so (the live site's runtime: its copy, its pause control), else still. */
|
|
5535
|
+
applyMarquee(entry) {
|
|
5536
|
+
const moving = this.designState?.shownState(designIdOf(entry.node)) === CanvasShowState.Open;
|
|
5537
|
+
if (moving && !entry.runtime)
|
|
5538
|
+
entry.runtime = this.zone.runOutsideAngular(() => new MarqueeRuntime(entry.element));
|
|
5539
|
+
else if (!moving && entry.runtime) {
|
|
5540
|
+
entry.runtime.destroy();
|
|
5541
|
+
entry.runtime = null;
|
|
5542
|
+
}
|
|
5319
5543
|
}
|
|
5320
5544
|
/** A Tabs on the canvas follows "Showing on canvas" and a selected tab (live), then shows any forced panel. */
|
|
5321
5545
|
applyExclusive(entry) {
|
|
@@ -5380,119 +5604,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
5380
5604
|
type: Optional
|
|
5381
5605
|
}] }, { type: i0.NgZone }] });
|
|
5382
5606
|
|
|
5383
|
-
const CANVAS_SCROLLER = '.preview-scroll';
|
|
5384
|
-
const PREVIEW_SCROLLER = '.iox-page-wrapper';
|
|
5385
|
-
/**
|
|
5386
|
-
* Scenes on the canvas and in Preview: the SAME scene-core driver the live site runs, so a layer stands at the same
|
|
5387
|
-
* value at the same progress in every renderer (premium-sites plan §5b). The canvas's page never scrolls itself (its
|
|
5388
|
-
* Lenis scrolls a wrapper around a zoomed frame), so `position: sticky` cannot hold the stage there and the driver does;
|
|
5389
|
-
* Preview's page is a real scroller, and sticky works. Canvas-only state: it never writes a node, so nothing here is
|
|
5390
|
-
* saved or becomes an undo step.
|
|
5391
|
-
*/
|
|
5392
|
-
class SceneCanvasService {
|
|
5393
|
-
constructor() {
|
|
5394
|
-
this.zone = inject(NgZone);
|
|
5395
|
-
this.overlay = inject(OverlayService, { optional: true });
|
|
5396
|
-
this.driver = null;
|
|
5397
|
-
this.rootsById = new Map();
|
|
5398
|
-
this.idsByRoot = new Map();
|
|
5399
|
-
/** Every frame on every Scene: the selection box and the timeline follow on it. */
|
|
5400
|
-
this.frames$ = new Subject();
|
|
5401
|
-
}
|
|
5402
|
-
/**
|
|
5403
|
-
* Drive a root. `onCanvas` comes from the caller, because only the render directive can read IOX_BUILDER_CANVAS: the
|
|
5404
|
-
* token is provided on the canvas's element, below where this service lives. Read here, it answered false, and the
|
|
5405
|
-
* canvas measured its Scenes against a page that moves with them, so they never left progress 0.
|
|
5406
|
-
*/
|
|
5407
|
-
attach(root, onCanvas, nodeId) {
|
|
5408
|
-
this.zone.runOutsideAngular(() => {
|
|
5409
|
-
this.driver ??= new ScrollDriver({
|
|
5410
|
-
pin: onCanvas,
|
|
5411
|
-
observe: true,
|
|
5412
|
-
originOf: el => el.closest(onCanvas ? CANVAS_SCROLLER : PREVIEW_SCROLLER),
|
|
5413
|
-
onFrame: (el, state) => this.frames$.next({ nodeId: this.idsByRoot.get(el) ?? null, ...state }),
|
|
5414
|
-
});
|
|
5415
|
-
this.driver.add(root);
|
|
5416
|
-
});
|
|
5417
|
-
if (nodeId) {
|
|
5418
|
-
this.rootsById.set(nodeId, root);
|
|
5419
|
-
this.idsByRoot.set(root, nodeId);
|
|
5420
|
-
}
|
|
5421
|
-
}
|
|
5422
|
-
detach(root) {
|
|
5423
|
-
this.driver?.remove(root);
|
|
5424
|
-
const id = this.idsByRoot.get(root);
|
|
5425
|
-
// A re-render attaches the new element before the old one detaches: it must not lose the new one.
|
|
5426
|
-
if (id && this.rootsById.get(id) === root)
|
|
5427
|
-
this.rootsById.delete(id);
|
|
5428
|
-
this.idsByRoot.delete(root);
|
|
5429
|
-
}
|
|
5430
|
-
/** The canvas's Lenis just moved the scroll, inside this frame: hold every stage in this same frame. */
|
|
5431
|
-
pinNow() {
|
|
5432
|
-
this.driver?.pinNow();
|
|
5433
|
-
}
|
|
5434
|
-
/** The damped progress a root's layers stand at. */
|
|
5435
|
-
progressOf(root) {
|
|
5436
|
-
return this.driver?.progressOf(root) ?? null;
|
|
5437
|
-
}
|
|
5438
|
-
/** Where the scroll stands in a Scene, 0 to 1. Null for a Scene that is not on the canvas. */
|
|
5439
|
-
positionOf(nodeId) {
|
|
5440
|
-
const root = nodeId ? this.rootsById.get(nodeId) : null;
|
|
5441
|
-
return root ? this.driver?.targetOf(root) ?? null : null;
|
|
5442
|
-
}
|
|
5443
|
-
/**
|
|
5444
|
-
* Stand a Scene at `p` by really scrolling the canvas: there is one position, so the scrubber, the wheel, the pin and
|
|
5445
|
-
* the selection box can never disagree. It lands with no glide, because the author is waiting to see that moment.
|
|
5446
|
-
*/
|
|
5447
|
-
goTo(nodeId, p) {
|
|
5448
|
-
const root = nodeId ? this.rootsById.get(nodeId) : null;
|
|
5449
|
-
const offset = root ? this.driver?.offsetFor(root, p) : null;
|
|
5450
|
-
const scroller = root?.closest(CANVAS_SCROLLER);
|
|
5451
|
-
if (!root || offset == null || !scroller)
|
|
5452
|
-
return;
|
|
5453
|
-
const rect = root.getBoundingClientRect();
|
|
5454
|
-
const zoom = root.offsetHeight ? rect.height / root.offsetHeight : 1;
|
|
5455
|
-
scroller.scrollTop += rect.top - scroller.getBoundingClientRect().top + offset * zoom;
|
|
5456
|
-
this.driver?.settle(root);
|
|
5457
|
-
}
|
|
5458
|
-
/**
|
|
5459
|
-
* A layer's tracks were edited: its element carries them now and its Scene plays them on the next frame, with no
|
|
5460
|
-
* re-render, because a Scene's own DOM is what the author is looking at.
|
|
5461
|
-
*/
|
|
5462
|
-
refreshLayer(node, root) {
|
|
5463
|
-
const ref = this.overlay?.getNodeRef(node) ?? (node.id ? this.overlay?.findRefByNodeId(node.id)?.ref : undefined);
|
|
5464
|
-
if (ref?.element && this.overlay)
|
|
5465
|
-
syncSceneAttributes(this.overlay.getStyledElement(ref.element), node);
|
|
5466
|
-
const el = root?.id ? this.rootsById.get(root.id) : null;
|
|
5467
|
-
if (el)
|
|
5468
|
-
this.driver?.reread(el);
|
|
5469
|
-
}
|
|
5470
|
-
/**
|
|
5471
|
-
* A value a layer's own component draws changed (a Video's pictures): its instance takes it now. Its Scene reads the new
|
|
5472
|
-
* attribute when it lands, with no re-render of the page.
|
|
5473
|
-
*/
|
|
5474
|
-
setLayerInput(node, name, value) {
|
|
5475
|
-
const ref = this.overlay?.getNodeRef(node) ?? (node.id ? this.overlay?.findRefByNodeId(node.id)?.ref : undefined);
|
|
5476
|
-
const component = ref?.componentRef;
|
|
5477
|
-
if (!component?.instance)
|
|
5478
|
-
return;
|
|
5479
|
-
component.instance[name] = value;
|
|
5480
|
-
component.changeDetectorRef.detectChanges();
|
|
5481
|
-
}
|
|
5482
|
-
ngOnDestroy() {
|
|
5483
|
-
this.driver?.destroy();
|
|
5484
|
-
this.driver = null;
|
|
5485
|
-
this.rootsById.clear();
|
|
5486
|
-
this.idsByRoot.clear();
|
|
5487
|
-
this.frames$.complete();
|
|
5488
|
-
}
|
|
5489
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SceneCanvasService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5490
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SceneCanvasService }); }
|
|
5491
|
-
}
|
|
5492
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SceneCanvasService, decorators: [{
|
|
5493
|
-
type: Injectable
|
|
5494
|
-
}] });
|
|
5495
|
-
|
|
5496
5607
|
/** The input a component declares to receive its empty-image placeholder (the Image). */
|
|
5497
5608
|
const EMPTY_SRC_INPUT = 'emptySrc';
|
|
5498
5609
|
/** The editing canvas's "Add image" label, through the host's i18n. */
|
|
@@ -6026,7 +6137,7 @@ class RenderDirective {
|
|
|
6026
6137
|
// A composite root runs its behaviour on the canvas — the live site's runtime (CC2; the Disclosure, CC2.1).
|
|
6027
6138
|
// A preview row's copy runs too, scoped by its row (vectoriox/iox-ui#20): its ids are its own and it shows
|
|
6028
6139
|
// the saved default, as every row does on the live site.
|
|
6029
|
-
if (nativeEl && node.id && isAnyCompositeRoot(node) && this.compositeCanvas) {
|
|
6140
|
+
if (nativeEl && node.id && (isAnyCompositeRoot(node) || isMarqueeRoot(node)) && this.compositeCanvas) {
|
|
6030
6141
|
this.compositeCanvas.attach(node, nativeEl);
|
|
6031
6142
|
this.compositeRootId = node.id;
|
|
6032
6143
|
}
|
|
@@ -6603,6 +6714,9 @@ var CompositePresetId;
|
|
|
6603
6714
|
CompositePresetId["Segmented"] = "segmented";
|
|
6604
6715
|
CompositePresetId["Lightbox"] = "lightbox";
|
|
6605
6716
|
CompositePresetId["Scene"] = "scene";
|
|
6717
|
+
CompositePresetId["Marquee"] = "marquee";
|
|
6718
|
+
CompositePresetId["Counter"] = "counter";
|
|
6719
|
+
CompositePresetId["ReadingBar"] = "reading-bar";
|
|
6606
6720
|
})(CompositePresetId || (CompositePresetId = {}));
|
|
6607
6721
|
/** Where a composite sits in the Components panel (composite plan §6d): content blocks, or the interactive
|
|
6608
6722
|
* composites (the Accordion — CC2, the Disclosure — CC2.1, Tabs — CC4a, the Segmented control — CC4b; the floating
|
|
@@ -6658,6 +6772,22 @@ const COMPOSITE_PRESETS = Object.freeze([
|
|
|
6658
6772
|
description: 'A stage that holds still while the page scrolls past it, and everything on it moves. Add your pictures and words as layers.',
|
|
6659
6773
|
build: create => buildSceneRecipe(create),
|
|
6660
6774
|
},
|
|
6775
|
+
// Premium components M6.
|
|
6776
|
+
{
|
|
6777
|
+
id: CompositePresetId.Marquee, group: CompositePaletteGroup.Interactive, name: 'Marquee', icon: 'ph-thin ph-arrows-left-right',
|
|
6778
|
+
description: 'A band of words that moves on its own, with a pause button. Still when the visitor asks for less motion.',
|
|
6779
|
+
build: create => buildMarqueeRecipe(create),
|
|
6780
|
+
},
|
|
6781
|
+
{
|
|
6782
|
+
id: CompositePresetId.Counter, group: CompositePaletteGroup.Interactive, name: 'Counter', icon: 'ph-thin ph-number-circle-one',
|
|
6783
|
+
description: 'A number that counts up once, the first time it comes into view — the real number is always in the page.',
|
|
6784
|
+
build: create => buildCounterRecipe(create),
|
|
6785
|
+
},
|
|
6786
|
+
{
|
|
6787
|
+
id: CompositePresetId.ReadingBar, group: CompositePaletteGroup.Interactive, name: 'Reading bar', icon: 'ph-thin ph-line-segment',
|
|
6788
|
+
description: 'A thin bar that fills as the page is read — or its section: change it in Interactions.',
|
|
6789
|
+
build: create => buildReadingBarRecipe(create),
|
|
6790
|
+
},
|
|
6661
6791
|
]);
|
|
6662
6792
|
/**
|
|
6663
6793
|
* The builder's node factory for composite-core recipes: a NEW node as the palette creates one
|
|
@@ -14746,6 +14876,7 @@ class BuilderContainerComponentConfig extends ComponentConfig {
|
|
|
14746
14876
|
buildColorSchemeTrait(),
|
|
14747
14877
|
...buildCompositeBehaviourTraits(),
|
|
14748
14878
|
...buildHeaderBehaviourTraits(),
|
|
14879
|
+
...buildMarqueeTraits(),
|
|
14749
14880
|
];
|
|
14750
14881
|
this.styleTraits = buildFullStyleTraits();
|
|
14751
14882
|
this.applyStyleDefaults({
|
|
@@ -15592,5 +15723,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
|
|
|
15592
15723
|
* Generated bundle index. Do not edit.
|
|
15593
15724
|
*/
|
|
15594
15725
|
|
|
15595
|
-
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 };
|
|
15726
|
+
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, isReadingBar, 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 };
|
|
15596
15727
|
//# sourceMappingURL=vectoriox-iox-builder.mjs.map
|