@vectoriox/iox-builder 9.9.0 → 9.11.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 +690 -289
- package/fesm2022/vectoriox-iox-builder.mjs.map +1 -1
- package/index.d.ts +138 -23
- package/package.json +2 -2
|
@@ -7,27 +7,131 @@ import { FormsModule } from '@angular/forms';
|
|
|
7
7
|
import * as i5 from '@angular/cdk/drag-drop';
|
|
8
8
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
9
9
|
import { AccordionModule } from 'primeng/accordion';
|
|
10
|
-
import * as
|
|
10
|
+
import * as i19 from 'primeng/button';
|
|
11
11
|
import { ButtonModule } from 'primeng/button';
|
|
12
12
|
import * as i2$1 from 'primeng/popover';
|
|
13
13
|
import { PopoverModule } from 'primeng/popover';
|
|
14
14
|
import { InputTextModule } from 'primeng/inputtext';
|
|
15
15
|
import { SelectModule } from 'primeng/select';
|
|
16
16
|
import { TooltipModule } from 'primeng/tooltip';
|
|
17
|
+
import * as i20 from 'primeng/dialog';
|
|
17
18
|
import { DialogModule } from 'primeng/dialog';
|
|
18
19
|
import * as i1$1 from '@vectoriox/iox-ui';
|
|
19
|
-
import { resolveBoundValue, VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, TEXT_DIRECTION_KEY, TEXT_ALIGN_KEY, TextAlign, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, LinkType, MAX_LINES_KEY, A11yTrait, BREAKPOINTS, Breakpoint, withSchemeInk, stripSuppressedProps, DeclarationContext, buildPartSelector, planResponsiveRules, resolveStyleProps, targetedStateSelector, stateSelectorSuffix, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, applyElementStyle, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, ContextStore, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, motionAllowed, finalKeyframe, keyframeStyleEntries, buildInteractionKeyframes, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, nodeTraitValue, syncNodeA11yAttributes, nodeA11yAttributes, safeSetInput, colorSchemeOf, composeVirtualTraits, withDefaultHoverTransition, stateNamesIn, partClassName, switchElementStyle, countOverrides, setOverride, breakpointDef, breakpointForWidth, FormRenderState, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, IS_PREVIEW, buttonBlockHref, IoxPageModule, PALETTE_PRESETS, elementStyleById, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
|
|
20
|
+
import { resolveBoundValue, VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, TEXT_DIRECTION_KEY, TEXT_ALIGN_KEY, TextAlign, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, LinkType, MAX_LINES_KEY, A11yTrait, BREAKPOINTS, Breakpoint, withSchemeInk, stripSuppressedProps, DeclarationContext, buildPartSelector, planResponsiveRules, resolveStyleProps, targetedStateSelector, stateSelectorSuffix, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, applyElementStyle, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, ContextStore, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, motionAllowed, finalKeyframe, keyframeStyleEntries, buildInteractionKeyframes, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, nodeTraitValue, syncNodeA11yAttributes, nodeA11yAttributes, safeSetInput, colorSchemeOf, composeVirtualTraits, withDefaultHoverTransition, stateNamesIn, partClassName, switchElementStyle, releaseElementStyle, countOverrides, setOverride, breakpointDef, breakpointForWidth, FormRenderState, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, IS_PREVIEW, buttonBlockHref, bootSlider, IoxPageModule, PALETTE_PRESETS, elementStyleById, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
|
|
20
21
|
export { ComponentInstanceRegistryService, VIRTUAL_TRAIT_KEYS, composeVirtualTraits } from '@vectoriox/iox-ui';
|
|
21
|
-
import { StructureEdit, isAccordionPart, isDisclosurePart, structureRefusal, AccordionRole, CompositeBehaviourTrait, ACCORDION_DEFAULT_ONS,
|
|
22
|
+
import { isExclusiveItemPart, exclusiveRootOf, isTabsFromContent, TabsRole, tabTriggerOf, tabLabel, removeTabPair, tabPanelOf, tabsListOf, tabsPanelsOf, nextTabKey, StructureEdit, isAccordionPart, isDisclosurePart, isSliderPart, isTabsPart, structureRefusal, StructureRefusal, AccordionRole, CompositeBehaviourTrait, ACCORDION_DEFAULT_ONS, TabsBehaviourTrait, TABS_ACTIVATE_ONS, TABS_ORIENTATIONS, onCompositeItemState, isCompositeItemOn, runCompositeItemAction, shouldScrollToItem, rowIndexOf as rowIndexOf$1, slideIndexForRow, DisclosureRole, isExclusiveTrigger, compositeScopeId, compositeRowKeys, isExclusiveRoot, ExclusiveRuntime, tabsLabel, tabsBehaviour, compositeKindOfRole, AccordionRuntime, accordionBehaviour, isToggleKind, closestAccordionItem, isAnyCompositeRoot, isAccordionRoot, isExclusiveList, buildCardRecipe, buildBadge, buildChip, buildAccordionRecipe, buildDisclosureRecipe, buildTabsRecipe, buildLightboxRecipe, NO_ELEMENT_STYLE, CompositeKind, SliderRole, accordionItemLabel, isExclusivePanels, isLockedPart, accordionRootOf, isAccordionFromContent, accordionItemOf, addAccordionItem, addTabPair, isCompositeItem, accordionHeadingLevel, isCompositePanel, isCompositeTrigger, splitSliderChildren, hasSliderParts } from '@vectoriox/iox-ui/composite-core';
|
|
22
23
|
import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
|
|
23
24
|
import interact from 'interactjs';
|
|
24
25
|
import { formContentInHost, isInteractiveHost, findFormContentInHosts } from '@vectoriox/iox-ui/forms-core';
|
|
25
26
|
import { map, catchError } from 'rxjs/operators';
|
|
26
27
|
import * as i1 from '@angular/common/http';
|
|
27
|
-
import { itemActionOf, BEHAVIOUR_ACTION_TYPES, SERVER_ELEMENT_ACTION_TYPES, CONSENT_ACTION_TYPES } from '@vectoriox/iox-ui/actions-core';
|
|
28
|
+
import { itemActionOf, BEHAVIOUR_ACTION_TYPES, SERVER_ELEMENT_ACTION_TYPES, CONSENT_ACTION_TYPES, opensAtClickedItem } from '@vectoriox/iox-ui/actions-core';
|
|
28
29
|
import Lenis from 'lenis';
|
|
29
30
|
import Moveable from 'moveable';
|
|
30
31
|
import Selecto from 'selecto';
|
|
32
|
+
import * as i18 from 'primeng/api';
|
|
33
|
+
import { planVideo, VideoSource, VIDEO_PROVIDER_LABEL, VideoTrait, VideoMode } from '@vectoriox/iox-ui/media-core';
|
|
34
|
+
import { ConsentCategory } from '@vectoriox/iox-ui/consent-core';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* node-path — pure ancestor-chain helpers for the builder's node tree.
|
|
38
|
+
*
|
|
39
|
+
* The builder needs a node's ancestor chain in several places: the breadcrumb bar,
|
|
40
|
+
* "select parent", and the parent-scoped style traits (Child Layout). Each of those
|
|
41
|
+
* used to re-walk the tree with its own private recursion. `nodeAncestorPath` is the
|
|
42
|
+
* single walk they all derive from.
|
|
43
|
+
*
|
|
44
|
+
* Builder-only on purpose: the SSR engine never needs a breadcrumb, so this stays in
|
|
45
|
+
* `iox-builder` rather than `@vectoriox/iox-ui` (which is the home for logic BOTH
|
|
46
|
+
* renderers consume). Structural helpers the two renderers share live in
|
|
47
|
+
* `@vectoriox/iox-ui` `style-compiler/tree-relations.ts`.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* The chain of nodes from a root down to (and including) `target`, matched by
|
|
51
|
+
* REFERENCE identity. Returns `null` when `target` is not in the tree.
|
|
52
|
+
*
|
|
53
|
+
* Depth-first, pre-order; first match wins. Reference identity (not `id`) is the right
|
|
54
|
+
* matcher here because the builder mutates nodes in place and repeater/slider clones
|
|
55
|
+
* deliberately share a template's `styleId` — matching by id would return the template's
|
|
56
|
+
* path for a clone.
|
|
57
|
+
*/
|
|
58
|
+
function nodeAncestorPath(roots, target) {
|
|
59
|
+
if (!roots?.length || !target)
|
|
60
|
+
return null;
|
|
61
|
+
for (const node of roots) {
|
|
62
|
+
if (node === target)
|
|
63
|
+
return [node];
|
|
64
|
+
const childPath = nodeAncestorPath(node.children, target);
|
|
65
|
+
if (childPath)
|
|
66
|
+
return [node, ...childPath];
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The immediate parent of `target`, or `null` when it is a root node or not in the tree.
|
|
72
|
+
* Derived from `nodeAncestorPath` so there is exactly one tree walk to keep correct.
|
|
73
|
+
*/
|
|
74
|
+
function findParentOf(roots, target) {
|
|
75
|
+
const path = nodeAncestorPath(roots, target);
|
|
76
|
+
return path && path.length >= 2 ? path[path.length - 2] : null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The ancestor chain across BOTH builder trees — the page layout and the global
|
|
80
|
+
* elements overlay, which are separate roots. Returns the first tree that contains
|
|
81
|
+
* the node, or `null` when neither does.
|
|
82
|
+
*/
|
|
83
|
+
function nodeAncestorPathInEither(layout, globalElements, target) {
|
|
84
|
+
return nodeAncestorPath(layout, target) ?? nodeAncestorPath(globalElements, target);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A tab and its panel go TOGETHER on the canvas (CC4a — composite T18): deleting either deletes both ("Delete tab and
|
|
89
|
+
* its panel"), duplicating either duplicates both under one fresh key. The pairing is composite-core's (`itemKey`);
|
|
90
|
+
* this is the builder's tree surgery over it, shared by every path (toolbar, layer tree, the host's Cmd+D / Delete).
|
|
91
|
+
*/
|
|
92
|
+
/** The Tabs a tab or a panel belongs to — a MANUAL one (From content, the rows are not nodes) — or null. */
|
|
93
|
+
function tabsRootForPart(layout, globals, node) {
|
|
94
|
+
if (!isExclusiveItemPart(node))
|
|
95
|
+
return null;
|
|
96
|
+
const path = nodeAncestorPathInEither(layout, globals, node) ?? [];
|
|
97
|
+
const root = exclusiveRootOf(node, path.filter(n => n !== node));
|
|
98
|
+
return root && !isTabsFromContent(root) ? root : null;
|
|
99
|
+
}
|
|
100
|
+
/** The words the pair is named by in the confirm: its tab's. */
|
|
101
|
+
function tabPairLabel(root, node) {
|
|
102
|
+
const tree = root;
|
|
103
|
+
const tab = node.role === TabsRole.Panel ? tabTriggerOf(tree, node) : node;
|
|
104
|
+
return tab ? tabLabel(tab) : '';
|
|
105
|
+
}
|
|
106
|
+
/** Delete a tab and its panel. True when the pair was removed. */
|
|
107
|
+
function deleteTabPair(root, node) {
|
|
108
|
+
return removeTabPair(root, node);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Duplicate a tab and its panel: copies (`clone` gives fresh ids) placed right after the originals, both under ONE new
|
|
112
|
+
* key — so the copy's tab shows the copy's panel. Returns the new tab, or null when `node` is no tab or panel of `root`.
|
|
113
|
+
*/
|
|
114
|
+
function duplicateTabPair(root, node, clone) {
|
|
115
|
+
const tree = root;
|
|
116
|
+
const tab = (node.role === TabsRole.Panel ? tabTriggerOf(tree, node) : node);
|
|
117
|
+
const panel = tab ? tabPanelOf(tree, tab) : undefined;
|
|
118
|
+
const list = tabsListOf(tree);
|
|
119
|
+
const panels = tabsPanelsOf(tree);
|
|
120
|
+
if (!tab || !panel || !list?.children || !panels?.children)
|
|
121
|
+
return null;
|
|
122
|
+
const key = nextTabKey(tree);
|
|
123
|
+
const tabCopy = clone(tab);
|
|
124
|
+
const panelCopy = clone(panel);
|
|
125
|
+
tabCopy.itemKey = panelCopy.itemKey = key;
|
|
126
|
+
list.children = insertAfter(list.children, tab, tabCopy);
|
|
127
|
+
panels.children = insertAfter(panels.children, panel, panelCopy);
|
|
128
|
+
return tabCopy;
|
|
129
|
+
}
|
|
130
|
+
function insertAfter(list, anchor, node) {
|
|
131
|
+
const out = [...list];
|
|
132
|
+
out.splice(out.indexOf(anchor) + 1, 0, node);
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
31
135
|
|
|
32
136
|
var PanelEventTypes;
|
|
33
137
|
(function (PanelEventTypes) {
|
|
@@ -212,12 +316,28 @@ const BUILDER_CHROME_EN = Object.freeze({
|
|
|
212
316
|
'builder.chrome.kindCard': 'Card',
|
|
213
317
|
'builder.chrome.kindAccordion': 'Accordion',
|
|
214
318
|
'builder.chrome.kindDisclosure': 'Disclosure',
|
|
319
|
+
'builder.chrome.kindSlider': 'Slider',
|
|
320
|
+
'builder.chrome.kindVideo': 'Video',
|
|
321
|
+
'builder.chrome.videoPosterOnly': 'poster only',
|
|
322
|
+
'builder.chrome.videoFile': 'Uploaded file',
|
|
323
|
+
'builder.chrome.refuseLockedSliderPartMove': 'This control belongs to its slider — it can’t be moved out.',
|
|
324
|
+
'builder.chrome.refuseLockedSliderPause': 'The pause button stays while Autoplay is on — moving content must be pausable. Turn Autoplay off in the slider’s Bindings to remove it.',
|
|
215
325
|
'builder.chrome.refuseLockedDisclosurePart': 'A disclosure keeps its toggle and its panel. Delete the whole disclosure instead.',
|
|
216
326
|
'builder.chrome.refuseLockedDisclosurePartMove': 'This part belongs to its disclosure — it can’t be moved out.',
|
|
217
327
|
'builder.chrome.refuseLockedItemPart': 'An item keeps its header and its panel. Delete the whole item, or the accordion.',
|
|
218
328
|
'builder.chrome.refuseLockedItemPartMove': 'This part belongs to its item — it can’t be moved out.',
|
|
219
329
|
'builder.chrome.refuseLastItem': 'An accordion keeps at least one item. Delete the whole accordion instead.',
|
|
220
330
|
'builder.chrome.addItem': 'Add item',
|
|
331
|
+
'builder.chrome.kindTabs': 'Tabs',
|
|
332
|
+
'builder.chrome.addTab': 'Add tab',
|
|
333
|
+
'builder.chrome.refuseLockedTabsPart': 'Tabs keep their tab list and their panels. Delete the whole Tabs instead.',
|
|
334
|
+
'builder.chrome.refuseLockedTabsPartMove': 'This part belongs to its Tabs — it can’t be moved out.',
|
|
335
|
+
'builder.chrome.refuseTabsAccepts': 'A tab list holds only tabs, and the panels only panels. Put it inside a panel instead.',
|
|
336
|
+
'builder.chrome.refuseLastTab': 'Tabs keep at least one tab. Delete the whole Tabs instead.',
|
|
337
|
+
'builder.chrome.deletePairTitle': 'Delete tab and its panel?',
|
|
338
|
+
'builder.chrome.deletePairBody': '“{tab}” and the panel it shows go together: deleting one deletes both. Undo brings them back.',
|
|
339
|
+
'builder.chrome.deletePair': 'Delete tab and its panel',
|
|
340
|
+
'builder.chrome.cancel': 'Cancel',
|
|
221
341
|
'builder.chrome.fromContent': 'From {source} · {count} rows',
|
|
222
342
|
'builder.chrome.emptySource': 'No rows in “{alias}”.',
|
|
223
343
|
'builder.chrome.emptySourceHint': 'The {type} content type has no published items that match this source’s filters. Add items in Content ▸ {type}, or change the source.',
|
|
@@ -470,6 +590,21 @@ var InsertRefusal;
|
|
|
470
590
|
InsertRefusal["LockedDisclosurePartMove"] = "lockedDisclosurePartMove";
|
|
471
591
|
/** A disclosure's toggle or panel (CC2.1) deleted on its own. */
|
|
472
592
|
InsertRefusal["LockedDisclosurePart"] = "lockedDisclosurePart";
|
|
593
|
+
/** A Slider's control (CC3) works only as its own direct child. */
|
|
594
|
+
InsertRefusal["LockedSliderPartMove"] = "lockedSliderPartMove";
|
|
595
|
+
/** The pause control stays while Autoplay is on (WCAG 2.2.2) — the Autoplay switch removes it. */
|
|
596
|
+
InsertRefusal["LockedSliderPause"] = "lockedSliderPause";
|
|
597
|
+
/** A Tabs' tab list or panels holder (CC4a) moved out of it. */
|
|
598
|
+
InsertRefusal["LockedTabsPartMove"] = "lockedTabsPartMove";
|
|
599
|
+
/** A Tabs' tab list or panels holder (CC4a) deleted on its own. */
|
|
600
|
+
InsertRefusal["LockedTabsPart"] = "lockedTabsPart";
|
|
601
|
+
/** Something other than a tab dropped into a tab list, or other than a panel into the panels holder (CC4a T18). */
|
|
602
|
+
InsertRefusal["TabsAccepts"] = "tabsAccepts";
|
|
603
|
+
/** A Tabs' LAST tab (composite plan T18): it keeps at least one. */
|
|
604
|
+
InsertRefusal["LastTab"] = "lastTab";
|
|
605
|
+
/** A tab or a panel deleted ALONE: the pair goes together — the host offers "Delete tab and its panel". Never said
|
|
606
|
+
* as a refusal toast. */
|
|
607
|
+
InsertRefusal["PairedPart"] = "pairedPart";
|
|
473
608
|
})(InsertRefusal || (InsertRefusal = {}));
|
|
474
609
|
const FORM_STATE_BLOCK_TYPES = new Set(['FormSuccess', 'FormError']);
|
|
475
610
|
/** The builder-chrome label that says each refusal on screen (English in `BUILDER_CHROME_EN`). */
|
|
@@ -484,7 +619,16 @@ const INSERT_REFUSAL_LABEL_KEYS = Object.freeze({
|
|
|
484
619
|
[InsertRefusal.LastItem]: 'builder.chrome.refuseLastItem',
|
|
485
620
|
[InsertRefusal.LockedDisclosurePartMove]: 'builder.chrome.refuseLockedDisclosurePartMove',
|
|
486
621
|
[InsertRefusal.LockedDisclosurePart]: 'builder.chrome.refuseLockedDisclosurePart',
|
|
622
|
+
[InsertRefusal.LockedSliderPartMove]: 'builder.chrome.refuseLockedSliderPartMove',
|
|
623
|
+
[InsertRefusal.LockedSliderPause]: 'builder.chrome.refuseLockedSliderPause',
|
|
624
|
+
[InsertRefusal.LockedTabsPartMove]: 'builder.chrome.refuseLockedTabsPartMove',
|
|
625
|
+
[InsertRefusal.LockedTabsPart]: 'builder.chrome.refuseLockedTabsPart',
|
|
626
|
+
[InsertRefusal.TabsAccepts]: 'builder.chrome.refuseTabsAccepts',
|
|
627
|
+
[InsertRefusal.LastTab]: 'builder.chrome.refuseLastTab',
|
|
628
|
+
[InsertRefusal.PairedPart]: 'builder.chrome.deletePairTitle',
|
|
487
629
|
});
|
|
630
|
+
/** The refusals that are not said as a toast: the host asks instead (a tab's pair). */
|
|
631
|
+
const SILENT_REFUSALS = new Set([InsertRefusal.PairedPart]);
|
|
488
632
|
/** Which refusal says a locked part's move / delete, by the composite it belongs to (a card's is the default). */
|
|
489
633
|
function lockedRefusal(node, edit) {
|
|
490
634
|
const move = edit === StructureEdit.Move;
|
|
@@ -492,6 +636,10 @@ function lockedRefusal(node, edit) {
|
|
|
492
636
|
return move ? InsertRefusal.LockedItemPartMove : InsertRefusal.LockedItemPart;
|
|
493
637
|
if (isDisclosurePart(node))
|
|
494
638
|
return move ? InsertRefusal.LockedDisclosurePartMove : InsertRefusal.LockedDisclosurePart;
|
|
639
|
+
if (isSliderPart(node))
|
|
640
|
+
return move ? InsertRefusal.LockedSliderPartMove : InsertRefusal.LockedSliderPause;
|
|
641
|
+
if (isTabsPart(node))
|
|
642
|
+
return move ? InsertRefusal.LockedTabsPartMove : InsertRefusal.LockedTabsPart;
|
|
495
643
|
return move ? InsertRefusal.LockedPartMove : InsertRefusal.LockedPart;
|
|
496
644
|
}
|
|
497
645
|
/** The refusal in the host's language (the library's English when no host resolver is provided). */
|
|
@@ -519,18 +667,28 @@ function insertRefusal(node, ancestors) {
|
|
|
519
667
|
return InsertRefusal.NestedForm;
|
|
520
668
|
if (formContentInHost(node, ancestors.some(isInteractiveHost)))
|
|
521
669
|
return InsertRefusal.FormInInteractive;
|
|
522
|
-
|
|
670
|
+
const structural = structureRefusal(node, StructureEdit.Move, ancestors);
|
|
671
|
+
if (structural === StructureRefusal.NotAccepted)
|
|
672
|
+
return InsertRefusal.TabsAccepts;
|
|
673
|
+
if (structural)
|
|
523
674
|
return lockedRefusal(node, StructureEdit.Move);
|
|
524
675
|
return null;
|
|
525
676
|
}
|
|
526
677
|
/** Why `node` may not be REMOVED from where it is (deleted, cut, made global), or null when it may: a
|
|
527
|
-
* composite's locked part stays — its Parts switch or deleting the whole composite are the ways out —
|
|
528
|
-
* accordion keeps its last item
|
|
678
|
+
* composite's locked part stays — its Parts switch or deleting the whole composite are the ways out — an
|
|
679
|
+
* accordion keeps its last item, Tabs their last tab, and a tab or a panel never leaves without its pair (the host
|
|
680
|
+
* offers to delete both). `siblings` = the list `node` sits in (where the caller has it). */
|
|
529
681
|
function removalRefusal(node, siblings) {
|
|
530
682
|
if (structureRefusal(node, StructureEdit.Delete))
|
|
531
683
|
return lockedRefusal(node, StructureEdit.Delete);
|
|
532
684
|
if (node?.role === AccordionRole.Item && siblings && siblings.filter(s => s?.role === AccordionRole.Item).length <= 1)
|
|
533
685
|
return InsertRefusal.LastItem;
|
|
686
|
+
if (isExclusiveItemPart(node)) {
|
|
687
|
+
const role = node.role === TabsRole.Trigger ? TabsRole.Trigger : TabsRole.Panel;
|
|
688
|
+
if (siblings && siblings.filter(s => s?.role === role).length <= 1)
|
|
689
|
+
return InsertRefusal.LastTab;
|
|
690
|
+
return InsertRefusal.PairedPart;
|
|
691
|
+
}
|
|
534
692
|
return null;
|
|
535
693
|
}
|
|
536
694
|
|
|
@@ -1231,13 +1389,17 @@ function buildColorSchemeTrait() {
|
|
|
1231
1389
|
}
|
|
1232
1390
|
/**
|
|
1233
1391
|
* A composite ROOT's behaviour (composite plan §3 — ordinary traits on the root; CC2's Accordion: Allow
|
|
1234
|
-
* several open, Open by default
|
|
1235
|
-
* composite root's recipe writes them. Edited in the
|
|
1392
|
+
* several open, Open by default; CC4a's Tabs: Activate on, Orientation, and the tab list's accessible name). No
|
|
1393
|
+
* default value: a plain Container or Repeater saves none — only a composite root's recipe writes them. Edited in the
|
|
1394
|
+
* root's Behaviour (and Accessibility) section, never as generic rows.
|
|
1236
1395
|
*/
|
|
1237
1396
|
function buildCompositeBehaviourTraits() {
|
|
1238
1397
|
return [
|
|
1239
1398
|
new TraitConfig(CompositeBehaviourTrait.Multiple, 'Allow several open', TraitInputType.Checkbox),
|
|
1240
1399
|
new TraitConfig(CompositeBehaviourTrait.DefaultOn, 'Open by default', TraitInputType.Select, ACCORDION_DEFAULT_ONS.map(v => ({ label: v, value: v }))),
|
|
1400
|
+
new TraitConfig(TabsBehaviourTrait.ActivateOn, 'Activate on', TraitInputType.Select, TABS_ACTIVATE_ONS.map(v => ({ label: v, value: v }))),
|
|
1401
|
+
new TraitConfig(TabsBehaviourTrait.Orientation, 'Orientation', TraitInputType.Select, TABS_ORIENTATIONS.map(v => ({ label: v, value: v }))),
|
|
1402
|
+
new TraitConfig(TabsBehaviourTrait.Label, 'Accessible name', TraitInputType.Text),
|
|
1241
1403
|
];
|
|
1242
1404
|
}
|
|
1243
1405
|
const LINK_TARGETS = [
|
|
@@ -2347,6 +2509,8 @@ class DragEngineService {
|
|
|
2347
2509
|
this.overlayService = overlayService;
|
|
2348
2510
|
/** Every insert the builder refused, for the host to say on screen (a toast). */
|
|
2349
2511
|
this.insertRefused$ = new Subject();
|
|
2512
|
+
/** A tab or a panel asked to leave ALONE (CC4a): the builder offers "Delete tab and its panel" for it. */
|
|
2513
|
+
this.pairRemoval$ = new Subject();
|
|
2350
2514
|
// ── Drop-zone registry ────────────────────────────────────
|
|
2351
2515
|
this.ids = [];
|
|
2352
2516
|
this.elMap = new Map();
|
|
@@ -2439,7 +2603,9 @@ class DragEngineService {
|
|
|
2439
2603
|
*/
|
|
2440
2604
|
refuseRemoval(node, siblings) {
|
|
2441
2605
|
const reason = removalRefusal(node, siblings);
|
|
2442
|
-
if (reason)
|
|
2606
|
+
if (reason === InsertRefusal.PairedPart)
|
|
2607
|
+
this.pairRemoval$.next(node);
|
|
2608
|
+
else if (reason && !SILENT_REFUSALS.has(reason))
|
|
2443
2609
|
this.insertRefused$.next({ reason, message: insertRefusalLabel(reason, this.labels) });
|
|
2444
2610
|
return reason;
|
|
2445
2611
|
}
|
|
@@ -3602,8 +3768,8 @@ class InteractionEngineService {
|
|
|
3602
3768
|
*/
|
|
3603
3769
|
attachItemState(node, ix, element, on) {
|
|
3604
3770
|
const run = () => this.runActions(node, ix.actions, element);
|
|
3605
|
-
const off =
|
|
3606
|
-
const timer = on &&
|
|
3771
|
+
const off = onCompositeItemState(element, on, run);
|
|
3772
|
+
const timer = on && isCompositeItemOn(element) ? setTimeout(run, 50) : null;
|
|
3607
3773
|
return () => { off(); if (timer)
|
|
3608
3774
|
clearTimeout(timer); };
|
|
3609
3775
|
}
|
|
@@ -3762,7 +3928,7 @@ class InteractionEngineService {
|
|
|
3762
3928
|
// impossible. The canvas twin registered open/close/toggle under its nodeId, exactly
|
|
3763
3929
|
// the Swiper-method pattern; a missing/mistargeted id is a silent no-op, never a throw.
|
|
3764
3930
|
if (DIALOG_ACTION_TYPES.has(action.type)) {
|
|
3765
|
-
this.executeDialogAction(action);
|
|
3931
|
+
this.executeDialogAction(action, selfEl);
|
|
3766
3932
|
continue;
|
|
3767
3933
|
}
|
|
3768
3934
|
// Item actions (CC2.1 — composite T14): the canvas RUNS them on the target item's own runtime — the
|
|
@@ -3884,13 +4050,17 @@ class InteractionEngineService {
|
|
|
3884
4050
|
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
3885
4051
|
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
3886
4052
|
}
|
|
3887
|
-
executeDialogAction(action) {
|
|
4053
|
+
executeDialogAction(action, selfEl) {
|
|
3888
4054
|
const dialog = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
3889
4055
|
if (!dialog)
|
|
3890
4056
|
return;
|
|
3891
4057
|
switch (action.type) {
|
|
3892
4058
|
case 'openDialog':
|
|
3893
4059
|
dialog.open?.();
|
|
4060
|
+
// CC3 "Open at the clicked item": the lightbox's slider goes to the clicked row's slide — the same
|
|
4061
|
+
// rule the live site runs (composite-core `slideIndexForRow`), once the canvas twin has rendered it.
|
|
4062
|
+
if (opensAtClickedItem(action))
|
|
4063
|
+
this.slideDialogToRow(action.target, rowIndexOf$1(selfEl));
|
|
3894
4064
|
break;
|
|
3895
4065
|
case 'closeDialog':
|
|
3896
4066
|
dialog.close?.();
|
|
@@ -3900,6 +4070,22 @@ class InteractionEngineService {
|
|
|
3900
4070
|
break;
|
|
3901
4071
|
}
|
|
3902
4072
|
}
|
|
4073
|
+
/** The slider inside the opened dialog (its canvas twin renders on the next passes) → the row's slide. */
|
|
4074
|
+
slideDialogToRow(dialogId, row) {
|
|
4075
|
+
let tries = 0;
|
|
4076
|
+
const attempt = () => {
|
|
4077
|
+
const host = document.querySelector(`.iox-node-${CSS.escape(dialogId)}`);
|
|
4078
|
+
const swiper = host?.querySelector('.swiper')?.swiper;
|
|
4079
|
+
if (swiper?.slides?.length) {
|
|
4080
|
+
swiper.update?.();
|
|
4081
|
+
swiper.slideTo(slideIndexForRow(row, swiper.slides.length), 0);
|
|
4082
|
+
return;
|
|
4083
|
+
}
|
|
4084
|
+
if (++tries < 40)
|
|
4085
|
+
requestAnimationFrame(attempt);
|
|
4086
|
+
};
|
|
4087
|
+
requestAnimationFrame(attempt);
|
|
4088
|
+
}
|
|
3903
4089
|
executeSwiperAction(action) {
|
|
3904
4090
|
// Target is the nodeId of the SliderContainer to control.
|
|
3905
4091
|
const swiper = this.instanceRegistry.get(action.target);
|
|
@@ -4062,8 +4248,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
4062
4248
|
* screen until something opens them, so on the canvas they show only while selected into or switched
|
|
4063
4249
|
* open ("Showing on canvas"). The layer tree marks them with the eye-slash glyph. A Dialog (the mobile
|
|
4064
4250
|
* menu's drawer, a consent banner) by its type; an Accordion's PANEL by its role (CC2 — closed until its
|
|
4065
|
-
* item opens); a Disclosure's panel and its "Read less" word (CC2.1 — shown only while it is open).
|
|
4066
|
-
* tab joins in CC4.
|
|
4251
|
+
* item opens); a Disclosure's panel and its "Read less" word (CC2.1 — shown only while it is open).
|
|
4067
4252
|
*/
|
|
4068
4253
|
const HIDDEN_AT_REST_TYPES = ['Dialog'];
|
|
4069
4254
|
const HIDDEN_AT_REST_ROLES = [AccordionRole.Panel, DisclosureRole.Panel, DisclosureRole.Less];
|
|
@@ -4082,8 +4267,21 @@ var SelectionReveal;
|
|
|
4082
4267
|
SelectionReveal["WhileSelected"] = "while-selected";
|
|
4083
4268
|
SelectionReveal["UntilClosed"] = "until-closed";
|
|
4084
4269
|
})(SelectionReveal || (SelectionReveal = {}));
|
|
4270
|
+
/**
|
|
4271
|
+
* Parts hidden at rest that live in a composite's TOGGLE, not in its panel (CC2.1 follow-up, approved 2026-10-21): a
|
|
4272
|
+
* click on one on the canvas toggles the composite live — selecting it never forces the reveal, so "Read less"
|
|
4273
|
+
* closes the disclosure it was clicked in. Only a part inside a PANEL is revealed by the selection.
|
|
4274
|
+
*/
|
|
4275
|
+
const TOGGLE_PART_ROLES = [DisclosureRole.Less];
|
|
4276
|
+
/**
|
|
4277
|
+
* Parts the selection REVEALS though they are not hidden at rest (CC4a): a tab PANEL — one is always on, so the layer
|
|
4278
|
+
* tree draws it no eye-slash (board 3), but selecting anything inside an inactive one shows its tab while selected.
|
|
4279
|
+
*/
|
|
4280
|
+
const REVEALED_WHILE_SELECTED_ROLES = [TabsRole.Panel];
|
|
4085
4281
|
function selectionRevealOf(node) {
|
|
4086
|
-
if (
|
|
4282
|
+
if (!!node?.role && REVEALED_WHILE_SELECTED_ROLES.includes(node.role))
|
|
4283
|
+
return SelectionReveal.WhileSelected;
|
|
4284
|
+
if (!isHiddenAtRest(node) || (!!node?.role && TOGGLE_PART_ROLES.includes(node.role)))
|
|
4087
4285
|
return null;
|
|
4088
4286
|
return node?.role ? SelectionReveal.WhileSelected : SelectionReveal.UntilClosed;
|
|
4089
4287
|
}
|
|
@@ -4121,7 +4319,7 @@ var CanvasShowState;
|
|
|
4121
4319
|
*/
|
|
4122
4320
|
class CanvasDesignStateService {
|
|
4123
4321
|
constructor() {
|
|
4124
|
-
this._state$ = new BehaviorSubject({ revealed: new Set(), pinned: new Set(), shown: new Map() });
|
|
4322
|
+
this._state$ = new BehaviorSubject({ revealed: new Set(), selectedTriggers: new Set(), pinned: new Set(), shown: new Map(), items: new Map() });
|
|
4125
4323
|
this.state$ = this._state$.asObservable();
|
|
4126
4324
|
this._pagePath$ = new BehaviorSubject(null);
|
|
4127
4325
|
/** The app path of the page on the canvas (`/work`), or null when unknown. */
|
|
@@ -4145,11 +4343,12 @@ class CanvasDesignStateService {
|
|
|
4145
4343
|
setSelectionPath(path) {
|
|
4146
4344
|
const ids = (reveal) => path.filter(n => selectionRevealOf(n) === reveal).map(n => n.id).filter((id) => !!id);
|
|
4147
4345
|
const revealed = new Set(ids(SelectionReveal.WhileSelected));
|
|
4148
|
-
const
|
|
4346
|
+
const selectedTriggers = new Set(path.filter(n => isExclusiveTrigger(n)).map(n => n.id).filter((id) => !!id));
|
|
4347
|
+
const { revealed: was, pinned: held, selectedTriggers: wasTriggers } = this._state$.value;
|
|
4149
4348
|
const pinned = new Set([...held, ...ids(SelectionReveal.UntilClosed)]);
|
|
4150
|
-
if (sameSet(revealed, was) && sameSet(pinned, held))
|
|
4349
|
+
if (sameSet(revealed, was) && sameSet(pinned, held) && sameSet(selectedTriggers, wasTriggers))
|
|
4151
4350
|
return;
|
|
4152
|
-
this._state$.next({ ...this._state$.value, revealed, pinned });
|
|
4351
|
+
this._state$.next({ ...this._state$.value, revealed, pinned, selectedTriggers });
|
|
4153
4352
|
}
|
|
4154
4353
|
/** Closed on the canvas (its X, its backdrop, the eye): no longer pinned or switched open. */
|
|
4155
4354
|
dismiss(nodeId) {
|
|
@@ -4173,6 +4372,21 @@ class CanvasDesignStateService {
|
|
|
4173
4372
|
shown.delete(nodeId);
|
|
4174
4373
|
this._state$.next({ ...this._state$.value, shown });
|
|
4175
4374
|
}
|
|
4375
|
+
/** Tabs' "Showing on canvas": the item a root shows (null = whatever the canvas shows live). */
|
|
4376
|
+
setShownItem(rootId, key) {
|
|
4377
|
+
if ((this._state$.value.items.get(rootId) ?? null) === key)
|
|
4378
|
+
return;
|
|
4379
|
+
const items = new Map(this._state$.value.items);
|
|
4380
|
+
if (key)
|
|
4381
|
+
items.set(rootId, key);
|
|
4382
|
+
else
|
|
4383
|
+
items.delete(rootId);
|
|
4384
|
+
this._state$.next({ ...this._state$.value, items });
|
|
4385
|
+
}
|
|
4386
|
+
/** The item a root's "Showing on canvas" shows, or null. */
|
|
4387
|
+
shownItem(rootId) {
|
|
4388
|
+
return (rootId && this._state$.value.items.get(rootId)) || null;
|
|
4389
|
+
}
|
|
4176
4390
|
/** The switcher's current choice for one part. */
|
|
4177
4391
|
shownState(nodeId) {
|
|
4178
4392
|
return (nodeId && this._state$.value.shown.get(nodeId)) || CanvasShowState.Closed;
|
|
@@ -4190,7 +4404,7 @@ class CanvasDesignStateService {
|
|
|
4190
4404
|
}
|
|
4191
4405
|
/** A new page on the canvas: nothing is revealed or switched open. */
|
|
4192
4406
|
reset() {
|
|
4193
|
-
this._state$.next({ revealed: new Set(), pinned: new Set(), shown: new Map() });
|
|
4407
|
+
this._state$.next({ revealed: new Set(), selectedTriggers: new Set(), pinned: new Set(), shown: new Map(), items: new Map() });
|
|
4194
4408
|
}
|
|
4195
4409
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4196
4410
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CanvasDesignStateService }); }
|
|
@@ -4208,14 +4422,15 @@ function designIdOf(node) {
|
|
|
4208
4422
|
return node.__previewClone ? (node.styleId ?? node.id) : node.id;
|
|
4209
4423
|
}
|
|
4210
4424
|
/**
|
|
4211
|
-
* The composites'
|
|
4212
|
-
* CC2.1) opens and closes on the canvas exactly as on the site: the SAME composite-core
|
|
4213
|
-
* engine attaches. A composite repeated in a Repeater's preview rows runs
|
|
4214
|
-
* (its row folded into the scope id — vectoriox/iox-ui#20). On top of the live
|
|
4215
|
-
* designing (R2, CC1's design state):
|
|
4216
|
-
* - selecting anything inside a CLOSED panel (canvas, tree, crumb, an Items row, SELECT_NODE)
|
|
4217
|
-
* while it is selected, and deselecting
|
|
4218
|
-
* -
|
|
4425
|
+
* The composites' runtimes ON THE CANVAS (CC2, CC4a) — the canvas is a live runtime, so an Accordion (and a Disclosure,
|
|
4426
|
+
* CC2.1) opens and closes, and Tabs switch, on the canvas exactly as on the site: the SAME composite-core runtimes the
|
|
4427
|
+
* live engine attaches (`AccordionRuntime`, `ExclusiveRuntime`). A composite repeated in a Repeater's preview rows runs
|
|
4428
|
+
* one per row, each with its own ARIA ids (its row folded into the scope id — vectoriox/iox-ui#20). On top of the live
|
|
4429
|
+
* state it shows what the author is designing (R2, CC1's design state):
|
|
4430
|
+
* - selecting anything inside a CLOSED panel or an INACTIVE tab panel (canvas, tree, crumb, an Items row, SELECT_NODE)
|
|
4431
|
+
* shows it while it is selected, and deselecting goes back;
|
|
4432
|
+
* - selecting a TAB (from anywhere) turns it on, live, like a click on it;
|
|
4433
|
+
* - "Showing on canvas": All open / As saved (accordion), one option per tab (Tabs).
|
|
4219
4434
|
* Canvas-only: it never writes a node, so the saved page is unchanged and nothing becomes an undo step.
|
|
4220
4435
|
*/
|
|
4221
4436
|
class CompositeCanvasService {
|
|
@@ -4223,11 +4438,12 @@ class CompositeCanvasService {
|
|
|
4223
4438
|
this.designState = designState;
|
|
4224
4439
|
this.zone = zone;
|
|
4225
4440
|
this.attached = new Map();
|
|
4441
|
+
this.exclusive = new Map();
|
|
4226
4442
|
this.sub = designState?.state$.subscribe(() => this.applyDesignState()) ?? null;
|
|
4227
4443
|
}
|
|
4228
4444
|
ngOnDestroy() {
|
|
4229
4445
|
this.sub?.unsubscribe();
|
|
4230
|
-
for (const id of [...this.attached.keys()])
|
|
4446
|
+
for (const id of [...this.attached.keys(), ...this.exclusive.keys()])
|
|
4231
4447
|
this.detach(id);
|
|
4232
4448
|
}
|
|
4233
4449
|
/** A composite root rendered on the canvas (the template, or a preview row's copy): run it. `element` is the
|
|
@@ -4237,9 +4453,28 @@ class CompositeCanvasService {
|
|
|
4237
4453
|
return;
|
|
4238
4454
|
this.detach(node.id);
|
|
4239
4455
|
const cssId = node.styleId ?? node.id;
|
|
4456
|
+
const rootId = () => compositeScopeId(cssId, compositeRowKeys(element));
|
|
4457
|
+
if (isExclusiveRoot(node)) {
|
|
4458
|
+
const runtime = this.zone.runOutsideAngular(() => new ExclusiveRuntime(element, {
|
|
4459
|
+
rootId,
|
|
4460
|
+
behaviour: tabsBehaviour(node),
|
|
4461
|
+
label: () => tabsLabel(node),
|
|
4462
|
+
observe: true,
|
|
4463
|
+
forced: () => this.forcedTab(node, element),
|
|
4464
|
+
// The switcher follows the TEMPLATE's clicks; a preview row's copy switches on its own.
|
|
4465
|
+
onChange: key => { if (!node.__previewClone && node.id)
|
|
4466
|
+
this.designState?.setShownItem(node.id, key); },
|
|
4467
|
+
}));
|
|
4468
|
+
this.exclusive.set(node.id, { node, runtime, selected: new Set() });
|
|
4469
|
+
const shown = this.designState?.shownItem(designIdOf(node));
|
|
4470
|
+
if (shown)
|
|
4471
|
+
runtime.activate(shown);
|
|
4472
|
+
return;
|
|
4473
|
+
}
|
|
4474
|
+
const kind = compositeKindOfRole(node.role);
|
|
4240
4475
|
const runtime = this.zone.runOutsideAngular(() => new AccordionRuntime(element, {
|
|
4241
|
-
kind:
|
|
4242
|
-
rootId
|
|
4476
|
+
kind: isToggleKind(kind) ? kind : undefined,
|
|
4477
|
+
rootId,
|
|
4243
4478
|
behaviour: accordionBehaviour(node),
|
|
4244
4479
|
observe: true,
|
|
4245
4480
|
forced: () => this.forcedFor(node, element),
|
|
@@ -4251,16 +4486,24 @@ class CompositeCanvasService {
|
|
|
4251
4486
|
return;
|
|
4252
4487
|
this.attached.get(nodeId)?.runtime.destroy();
|
|
4253
4488
|
this.attached.delete(nodeId);
|
|
4489
|
+
this.exclusive.get(nodeId)?.runtime.destroy();
|
|
4490
|
+
this.exclusive.delete(nodeId);
|
|
4254
4491
|
}
|
|
4255
|
-
/** The root's behaviour changed in its panel (Allow several open, Open by default). */
|
|
4492
|
+
/** The root's behaviour changed in its panel (Allow several open, Open by default; Activate on, Orientation). */
|
|
4256
4493
|
refreshBehaviour(node) {
|
|
4257
4494
|
const entry = node.id ? this.attached.get(node.id) : undefined;
|
|
4258
4495
|
entry?.runtime.setBehaviour(accordionBehaviour(node));
|
|
4496
|
+
const tabs = node.id ? this.exclusive.get(node.id) : undefined;
|
|
4497
|
+
tabs?.runtime.setBehaviour(tabsBehaviour(node));
|
|
4259
4498
|
}
|
|
4260
|
-
/** The runtime of an attached root (the e2e harness and the panels read its state). */
|
|
4499
|
+
/** The runtime of an attached Accordion / Disclosure root (the e2e harness and the panels read its state). */
|
|
4261
4500
|
runtimeOf(nodeId) {
|
|
4262
4501
|
return (nodeId && this.attached.get(nodeId)?.runtime) || null;
|
|
4263
4502
|
}
|
|
4503
|
+
/** The runtime of an attached Tabs root (CC4a — "Showing on canvas" reads its tabs and the one on). */
|
|
4504
|
+
tabsRuntimeOf(nodeId) {
|
|
4505
|
+
return (nodeId && this.exclusive.get(nodeId)?.runtime) || null;
|
|
4506
|
+
}
|
|
4264
4507
|
applyDesignState() {
|
|
4265
4508
|
for (const entry of this.attached.values()) {
|
|
4266
4509
|
const shown = this.designState?.shownState(designIdOf(entry.node)) ?? CanvasShowState.Closed;
|
|
@@ -4271,6 +4514,26 @@ class CompositeCanvasService {
|
|
|
4271
4514
|
entry.runtime.refresh();
|
|
4272
4515
|
entry.shown = shown;
|
|
4273
4516
|
}
|
|
4517
|
+
for (const entry of this.exclusive.values())
|
|
4518
|
+
this.applyExclusive(entry);
|
|
4519
|
+
}
|
|
4520
|
+
/** A Tabs on the canvas follows "Showing on canvas" and a selected tab (live), then shows any forced panel. */
|
|
4521
|
+
applyExclusive(entry) {
|
|
4522
|
+
const state = this.designState;
|
|
4523
|
+
if (!state)
|
|
4524
|
+
return;
|
|
4525
|
+
const selected = state.snapshot.selectedTriggers;
|
|
4526
|
+
const fresh = [...selected].filter(id => !entry.selected.has(id));
|
|
4527
|
+
entry.selected = new Set(selected);
|
|
4528
|
+
for (const id of fresh) {
|
|
4529
|
+
const key = entry.runtime.keyAt(entry.runtime.root.querySelector(`.iox-node-${CSS.escape(id)}`));
|
|
4530
|
+
if (key)
|
|
4531
|
+
entry.runtime.activate(key);
|
|
4532
|
+
}
|
|
4533
|
+
const shown = state.shownItem(designIdOf(entry.node));
|
|
4534
|
+
if (shown && shown !== entry.runtime.liveKey)
|
|
4535
|
+
entry.runtime.activate(shown);
|
|
4536
|
+
entry.runtime.refresh();
|
|
4274
4537
|
}
|
|
4275
4538
|
/** The keys the design state holds open: every item ("All open"), or the items whose hidden part the
|
|
4276
4539
|
* selection sits in. A preview row shares its template's classes, so the template (row 1) answers — and a
|
|
@@ -4294,6 +4557,19 @@ class CompositeCanvasService {
|
|
|
4294
4557
|
}
|
|
4295
4558
|
return forced;
|
|
4296
4559
|
}
|
|
4560
|
+
/** The tab forced on: the one whose panel the selection sits in (the template's, in a preview row's copy). */
|
|
4561
|
+
forcedTab(node, root) {
|
|
4562
|
+
const state = this.designState;
|
|
4563
|
+
const runtime = node.id ? this.exclusive.get(node.id)?.runtime : undefined;
|
|
4564
|
+
if (!state || !runtime)
|
|
4565
|
+
return null;
|
|
4566
|
+
for (const id of state.snapshot.revealed) {
|
|
4567
|
+
const key = runtime.keyAt(root.querySelector(`.iox-node-${CSS.escape(id)}`));
|
|
4568
|
+
if (key)
|
|
4569
|
+
return key;
|
|
4570
|
+
}
|
|
4571
|
+
return null;
|
|
4572
|
+
}
|
|
4297
4573
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CompositeCanvasService, deps: [{ token: CanvasDesignStateService, optional: true }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4298
4574
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: CompositeCanvasService }); }
|
|
4299
4575
|
}
|
|
@@ -4806,7 +5082,7 @@ class RenderDirective {
|
|
|
4806
5082
|
// A composite root runs its behaviour on the canvas — the live site's runtime (CC2; the Disclosure, CC2.1).
|
|
4807
5083
|
// A preview row's copy runs too, scoped by its row (vectoriox/iox-ui#20): its ids are its own and it shows
|
|
4808
5084
|
// the saved default, as every row does on the live site.
|
|
4809
|
-
if (nativeEl && node.id &&
|
|
5085
|
+
if (nativeEl && node.id && isAnyCompositeRoot(node) && this.compositeCanvas) {
|
|
4810
5086
|
this.compositeCanvas.attach(node, nativeEl);
|
|
4811
5087
|
this.compositeRootId = node.id;
|
|
4812
5088
|
}
|
|
@@ -4946,7 +5222,8 @@ class BuilderRepeaterComponent {
|
|
|
4946
5222
|
}
|
|
4947
5223
|
/** A composite whose source answered with no rows: the placeholder takes the template's place. */
|
|
4948
5224
|
get showsEmptySource() {
|
|
4949
|
-
|
|
5225
|
+
// An Accordion's — or a Tabs' list (CC4a; its panels holder says nothing twice) — source that came back empty.
|
|
5226
|
+
return (isAccordionRoot(this.node) || isExclusiveList(this.node)) && !!this._source && this.fetched && !this.isLoading && !this.items.length;
|
|
4950
5227
|
}
|
|
4951
5228
|
get emptySourceTitle() {
|
|
4952
5229
|
return this.label(EMPTY_SOURCE_KEY).replace('{alias}', this._source);
|
|
@@ -5234,7 +5511,7 @@ class BuilderRepeaterComponent {
|
|
|
5234
5511
|
</ng-container>
|
|
5235
5512
|
</div>
|
|
5236
5513
|
</div>
|
|
5237
|
-
`, isInline: true, styles: [".repeater-wrapper{position:relative;box-sizing:border-box}.repeater-badge{display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px dashed rgba(203,144,144,.4);border-bottom:none;border-radius:4px 4px 0 0;-webkit-user-select:none;user-select:none}.repeater-wrapper.has-children>.repeater-badge{position:absolute;top:0;left:0;z-index:10;border-radius:0 0 4px;border:1px solid rgba(203,144,144,.5);opacity:0;cursor:pointer;transition:opacity .15s}.repeater-wrapper.has-children:hover>.repeater-badge{opacity:1}.repeater-badge i{font-size:13px}.repeater-badge-empty{opacity:.55}.repeater-badge-count{margin-left:auto;font-weight:600}.repeater-badge-loading i{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.repeater-root{box-sizing:border-box}.repeater-wrapper:not(.has-children) .repeater-root{border:1px dashed rgba(203,144,144,.4);border-top:none;border-radius:0 0 4px 4px;min-height:40px}.repeater-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1.5rem 1rem;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.repeater-placeholder i{font-size:20px}.repeater-placeholder--source{border:1px dashed rgba(203,144,144,.5);border-radius:4px;text-align:center;color:#475569d9;font-size:13px;line-height:1.5}.repeater-placeholder--source strong{font-weight:600;color:#1e293be6}.repeater-item-template{min-width:0}.repeater-preview-child{opacity:.55;pointer-events:none;min-width:0}:host(.iox-role-accordion) .repeater-preview-child{pointer-events:auto}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId", "ioxPhrasing"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId", "ioxDragDisabled"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
|
|
5514
|
+
`, isInline: true, styles: [".repeater-wrapper{position:relative;box-sizing:border-box}.repeater-badge{display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px dashed rgba(203,144,144,.4);border-bottom:none;border-radius:4px 4px 0 0;-webkit-user-select:none;user-select:none}.repeater-wrapper.has-children>.repeater-badge{position:absolute;top:0;left:0;z-index:10;border-radius:0 0 4px;border:1px solid rgba(203,144,144,.5);opacity:0;cursor:pointer;transition:opacity .15s}.repeater-wrapper.has-children:hover>.repeater-badge{opacity:1}.repeater-badge i{font-size:13px}.repeater-badge-empty{opacity:.55}.repeater-badge-count{margin-left:auto;font-weight:600}.repeater-badge-loading i{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.repeater-root{box-sizing:border-box}.repeater-wrapper:not(.has-children) .repeater-root{border:1px dashed rgba(203,144,144,.4);border-top:none;border-radius:0 0 4px 4px;min-height:40px}.repeater-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1.5rem 1rem;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.repeater-placeholder i{font-size:20px}.repeater-placeholder--source{border:1px dashed rgba(203,144,144,.5);border-radius:4px;text-align:center;color:#475569d9;font-size:13px;line-height:1.5}.repeater-placeholder--source strong{font-weight:600;color:#1e293be6}.repeater-item-template{min-width:0}.repeater-preview-child{opacity:.55;pointer-events:none;min-width:0}:host(.iox-role-accordion) .repeater-preview-child,:host(.iox-role-tabs-list) .repeater-preview-child,:host(.iox-role-tabs-panels) .repeater-preview-child{pointer-events:auto}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId", "ioxPhrasing"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId", "ioxDragDisabled"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
|
|
5238
5515
|
}
|
|
5239
5516
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderRepeaterComponent, decorators: [{
|
|
5240
5517
|
type: Component,
|
|
@@ -5328,7 +5605,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
5328
5605
|
</ng-container>
|
|
5329
5606
|
</div>
|
|
5330
5607
|
</div>
|
|
5331
|
-
`, standalone: false, styles: [".repeater-wrapper{position:relative;box-sizing:border-box}.repeater-badge{display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px dashed rgba(203,144,144,.4);border-bottom:none;border-radius:4px 4px 0 0;-webkit-user-select:none;user-select:none}.repeater-wrapper.has-children>.repeater-badge{position:absolute;top:0;left:0;z-index:10;border-radius:0 0 4px;border:1px solid rgba(203,144,144,.5);opacity:0;cursor:pointer;transition:opacity .15s}.repeater-wrapper.has-children:hover>.repeater-badge{opacity:1}.repeater-badge i{font-size:13px}.repeater-badge-empty{opacity:.55}.repeater-badge-count{margin-left:auto;font-weight:600}.repeater-badge-loading i{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.repeater-root{box-sizing:border-box}.repeater-wrapper:not(.has-children) .repeater-root{border:1px dashed rgba(203,144,144,.4);border-top:none;border-radius:0 0 4px 4px;min-height:40px}.repeater-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1.5rem 1rem;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.repeater-placeholder i{font-size:20px}.repeater-placeholder--source{border:1px dashed rgba(203,144,144,.5);border-radius:4px;text-align:center;color:#475569d9;font-size:13px;line-height:1.5}.repeater-placeholder--source strong{font-weight:600;color:#1e293be6}.repeater-item-template{min-width:0}.repeater-preview-child{opacity:.55;pointer-events:none;min-width:0}:host(.iox-role-accordion) .repeater-preview-child{pointer-events:auto}\n"] }]
|
|
5608
|
+
`, standalone: false, styles: [".repeater-wrapper{position:relative;box-sizing:border-box}.repeater-badge{display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px dashed rgba(203,144,144,.4);border-bottom:none;border-radius:4px 4px 0 0;-webkit-user-select:none;user-select:none}.repeater-wrapper.has-children>.repeater-badge{position:absolute;top:0;left:0;z-index:10;border-radius:0 0 4px;border:1px solid rgba(203,144,144,.5);opacity:0;cursor:pointer;transition:opacity .15s}.repeater-wrapper.has-children:hover>.repeater-badge{opacity:1}.repeater-badge i{font-size:13px}.repeater-badge-empty{opacity:.55}.repeater-badge-count{margin-left:auto;font-weight:600}.repeater-badge-loading i{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.repeater-root{box-sizing:border-box}.repeater-wrapper:not(.has-children) .repeater-root{border:1px dashed rgba(203,144,144,.4);border-top:none;border-radius:0 0 4px 4px;min-height:40px}.repeater-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:1.5rem 1rem;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.repeater-placeholder i{font-size:20px}.repeater-placeholder--source{border:1px dashed rgba(203,144,144,.5);border-radius:4px;text-align:center;color:#475569d9;font-size:13px;line-height:1.5}.repeater-placeholder--source strong{font-weight:600;color:#1e293be6}.repeater-item-template{min-width:0}.repeater-preview-child{opacity:.55;pointer-events:none;min-width:0}:host(.iox-role-accordion) .repeater-preview-child,:host(.iox-role-tabs-list) .repeater-preview-child,:host(.iox-role-tabs-panels) .repeater-preview-child{pointer-events:auto}\n"] }]
|
|
5332
5609
|
}], ctorParameters: () => [{ type: DragEngineService }, { type: OverlayService }, { type: BuilderDataSourceService }, { type: DataSourceRegistryService }, { type: PanelEventService }, { type: i0.ChangeDetectorRef }, { type: InteractionEngineService }, { type: undefined, decorators: [{
|
|
5333
5610
|
type: Optional
|
|
5334
5611
|
}, {
|
|
@@ -5373,9 +5650,11 @@ var CompositePresetId;
|
|
|
5373
5650
|
CompositePresetId["Chip"] = "chip";
|
|
5374
5651
|
CompositePresetId["Accordion"] = "accordion";
|
|
5375
5652
|
CompositePresetId["Disclosure"] = "disclosure";
|
|
5653
|
+
CompositePresetId["Tabs"] = "tabs";
|
|
5654
|
+
CompositePresetId["Lightbox"] = "lightbox";
|
|
5376
5655
|
})(CompositePresetId || (CompositePresetId = {}));
|
|
5377
5656
|
/** Where a composite sits in the Components panel (composite plan §6d): content blocks, or the interactive
|
|
5378
|
-
* composites (the Accordion — CC2, the Disclosure — CC2.1
|
|
5657
|
+
* composites (the Accordion — CC2, the Disclosure — CC2.1, Tabs — CC4a; the floating family later). */
|
|
5379
5658
|
var CompositePaletteGroup;
|
|
5380
5659
|
(function (CompositePaletteGroup) {
|
|
5381
5660
|
CompositePaletteGroup["ContentBlocks"] = "content-blocks";
|
|
@@ -5407,6 +5686,16 @@ const COMPOSITE_PRESETS = Object.freeze([
|
|
|
5407
5686
|
description: '"Read more" — a button that reveals the text below it and reads "Read less" while it is open.',
|
|
5408
5687
|
build: create => buildDisclosureRecipe(create),
|
|
5409
5688
|
},
|
|
5689
|
+
{
|
|
5690
|
+
id: CompositePresetId.Tabs, group: CompositePaletteGroup.Interactive, name: 'Tabs', icon: 'ph-thin ph-tabs',
|
|
5691
|
+
description: 'Tabs that switch what shows below them — three tabs, the first on. Every tab and panel is its own element.',
|
|
5692
|
+
build: create => buildTabsRecipe(create),
|
|
5693
|
+
},
|
|
5694
|
+
{
|
|
5695
|
+
id: CompositePresetId.Lightbox, group: CompositePaletteGroup.Interactive, name: 'Lightbox', icon: 'ph-thin ph-frame-corners',
|
|
5696
|
+
description: 'A full-screen dialog with a slider — bind its slider to your gallery\'s source, and each thumbnail opens it at its own image.',
|
|
5697
|
+
build: create => buildLightboxRecipe(create),
|
|
5698
|
+
},
|
|
5410
5699
|
]);
|
|
5411
5700
|
/**
|
|
5412
5701
|
* The builder's node factory for composite-core recipes: a NEW node as the palette creates one
|
|
@@ -5426,6 +5715,11 @@ function builderCompositeFactory(createConfig, baselineStyles) {
|
|
|
5426
5715
|
if (spec.elementStyle && spec.elementStyle !== node.elementStyle) {
|
|
5427
5716
|
switchElementStyle(node, node.elementStyle, spec.elementStyle, baselineStyles(spec.type));
|
|
5428
5717
|
}
|
|
5718
|
+
else if (spec.elementStyle === NO_ELEMENT_STYLE && node.elementStyle) {
|
|
5719
|
+
// NO_ELEMENT_STYLE asks for no style (undefined / null keep the type's default): a migrated slider's bare
|
|
5720
|
+
// chevrons, a lightbox's slide (CC3).
|
|
5721
|
+
releaseElementStyle(node, node.elementStyle, baselineStyles(spec.type));
|
|
5722
|
+
}
|
|
5429
5723
|
if (node.children)
|
|
5430
5724
|
node.children = [];
|
|
5431
5725
|
return node;
|
|
@@ -5543,57 +5837,6 @@ function overlayGlobals(globals) {
|
|
|
5543
5837
|
return { before, pinnedAfter };
|
|
5544
5838
|
}
|
|
5545
5839
|
|
|
5546
|
-
/**
|
|
5547
|
-
* node-path — pure ancestor-chain helpers for the builder's node tree.
|
|
5548
|
-
*
|
|
5549
|
-
* The builder needs a node's ancestor chain in several places: the breadcrumb bar,
|
|
5550
|
-
* "select parent", and the parent-scoped style traits (Child Layout). Each of those
|
|
5551
|
-
* used to re-walk the tree with its own private recursion. `nodeAncestorPath` is the
|
|
5552
|
-
* single walk they all derive from.
|
|
5553
|
-
*
|
|
5554
|
-
* Builder-only on purpose: the SSR engine never needs a breadcrumb, so this stays in
|
|
5555
|
-
* `iox-builder` rather than `@vectoriox/iox-ui` (which is the home for logic BOTH
|
|
5556
|
-
* renderers consume). Structural helpers the two renderers share live in
|
|
5557
|
-
* `@vectoriox/iox-ui` `style-compiler/tree-relations.ts`.
|
|
5558
|
-
*/
|
|
5559
|
-
/**
|
|
5560
|
-
* The chain of nodes from a root down to (and including) `target`, matched by
|
|
5561
|
-
* REFERENCE identity. Returns `null` when `target` is not in the tree.
|
|
5562
|
-
*
|
|
5563
|
-
* Depth-first, pre-order; first match wins. Reference identity (not `id`) is the right
|
|
5564
|
-
* matcher here because the builder mutates nodes in place and repeater/slider clones
|
|
5565
|
-
* deliberately share a template's `styleId` — matching by id would return the template's
|
|
5566
|
-
* path for a clone.
|
|
5567
|
-
*/
|
|
5568
|
-
function nodeAncestorPath(roots, target) {
|
|
5569
|
-
if (!roots?.length || !target)
|
|
5570
|
-
return null;
|
|
5571
|
-
for (const node of roots) {
|
|
5572
|
-
if (node === target)
|
|
5573
|
-
return [node];
|
|
5574
|
-
const childPath = nodeAncestorPath(node.children, target);
|
|
5575
|
-
if (childPath)
|
|
5576
|
-
return [node, ...childPath];
|
|
5577
|
-
}
|
|
5578
|
-
return null;
|
|
5579
|
-
}
|
|
5580
|
-
/**
|
|
5581
|
-
* The immediate parent of `target`, or `null` when it is a root node or not in the tree.
|
|
5582
|
-
* Derived from `nodeAncestorPath` so there is exactly one tree walk to keep correct.
|
|
5583
|
-
*/
|
|
5584
|
-
function findParentOf(roots, target) {
|
|
5585
|
-
const path = nodeAncestorPath(roots, target);
|
|
5586
|
-
return path && path.length >= 2 ? path[path.length - 2] : null;
|
|
5587
|
-
}
|
|
5588
|
-
/**
|
|
5589
|
-
* The ancestor chain across BOTH builder trees — the page layout and the global
|
|
5590
|
-
* elements overlay, which are separate roots. Returns the first tree that contains
|
|
5591
|
-
* the node, or `null` when neither does.
|
|
5592
|
-
*/
|
|
5593
|
-
function nodeAncestorPathInEither(layout, globalElements, target) {
|
|
5594
|
-
return nodeAncestorPath(layout, target) ?? nodeAncestorPath(globalElements, target);
|
|
5595
|
-
}
|
|
5596
|
-
|
|
5597
5840
|
/** Counts for ONE node (the selection). Breakpoints with nothing overridden are omitted. */
|
|
5598
5841
|
function nodeBreakpointCounts(node) {
|
|
5599
5842
|
const counts = {};
|
|
@@ -6855,10 +7098,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
6855
7098
|
* An accordion's and a disclosure's parts are named by the part alone (`trigger`, "Accordion · trigger", board 3);
|
|
6856
7099
|
* a card keeps its full role (`card.title`, "Card · card.title", board 8).
|
|
6857
7100
|
*/
|
|
6858
|
-
const SHORT_ROLE_KINDS = new Set([CompositeKind.Accordion, CompositeKind.Disclosure]);
|
|
7101
|
+
const SHORT_ROLE_KINDS = new Set([CompositeKind.Accordion, CompositeKind.Disclosure, CompositeKind.Tabs]);
|
|
6859
7102
|
/** A composite kind's glyph (Phosphor, weight-less): the badge, the root's crumb and its tree row carry it
|
|
6860
7103
|
* (board 3: "the composite root carrying its kind icon"). A card keeps the generic stack glyph. */
|
|
6861
|
-
const KIND_GLYPHS = { [CompositeKind.Accordion]: 'ph-rows', [CompositeKind.Disclosure]: 'ph-caret-circle-down' };
|
|
7104
|
+
const KIND_GLYPHS = { [CompositeKind.Accordion]: 'ph-rows', [CompositeKind.Disclosure]: 'ph-caret-circle-down', [CompositeKind.Tabs]: 'ph-tabs', slider: 'ph-slideshow' };
|
|
6862
7105
|
const DEFAULT_KIND_GLYPH = 'ph-stack';
|
|
6863
7106
|
/** The tree icon of an accordion's structural parts (board 3): rows on the root, a stack on an item and a
|
|
6864
7107
|
* panel, a cursor on the header, a sparkle on the icon. Other parts keep their type's icon. */
|
|
@@ -6874,6 +7117,17 @@ const PART_ICONS = {
|
|
|
6874
7117
|
[DisclosureRole.Trigger]: 'ph-thin ph-cursor',
|
|
6875
7118
|
[DisclosureRole.Panel]: 'ph-thin ph-stack',
|
|
6876
7119
|
[DisclosureRole.Icon]: 'ph-thin ph-sparkle',
|
|
7120
|
+
// CC4a — Tabs (board 3): the tabs glyph on the root, a stack on the list, the panels and each panel, a cursor on a tab.
|
|
7121
|
+
[TabsRole.Tabs]: 'ph-thin ph-tabs',
|
|
7122
|
+
[TabsRole.List]: 'ph-thin ph-stack',
|
|
7123
|
+
[TabsRole.Trigger]: 'ph-thin ph-cursor',
|
|
7124
|
+
[TabsRole.Panels]: 'ph-thin ph-stack',
|
|
7125
|
+
[TabsRole.Panel]: 'ph-thin ph-stack',
|
|
7126
|
+
// CC3 — a Slider's controls (board 6): a cursor on the buttons, six dots on the pagination.
|
|
7127
|
+
[SliderRole.Pause]: 'ph-thin ph-cursor',
|
|
7128
|
+
[SliderRole.Prev]: 'ph-thin ph-cursor',
|
|
7129
|
+
[SliderRole.Next]: 'ph-thin ph-cursor',
|
|
7130
|
+
[SliderRole.Pagination]: 'ph-thin ph-dots-six',
|
|
6877
7131
|
};
|
|
6878
7132
|
/** The glyph of a role's composite kind (`ph-rows` for an accordion and its parts). */
|
|
6879
7133
|
function compositeKindGlyph(role) {
|
|
@@ -6896,11 +7150,20 @@ function roleBadgeLabel(role, kindLabel) {
|
|
|
6896
7150
|
return part ? `${kindLabel} · ${part}` : kindLabel;
|
|
6897
7151
|
}
|
|
6898
7152
|
/** What a part's row and crumb say after its name: an accordion item names its question ("Item · Do you take
|
|
6899
|
-
* projects…", board 3),
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
7153
|
+
* projects…", board 3), and a tab AND its panel name the tab's words ("Tab · Kitchens", "Panel · Kitchens") — so
|
|
7154
|
+
* three rows called "Item" or "Panel" can be told apart. `ancestors` = the node's ancestors, root first (a panel finds
|
|
7155
|
+
* its tab through its Tabs). */
|
|
7156
|
+
function compositePartSub(node, ancestors = []) {
|
|
7157
|
+
if (node.role === AccordionRole.Item)
|
|
7158
|
+
return accordionItemLabel(node) || null;
|
|
7159
|
+
if (node.role === TabsRole.Trigger)
|
|
7160
|
+
return tabLabel(node) || null;
|
|
7161
|
+
if (node.role === TabsRole.Panel) {
|
|
7162
|
+
const root = exclusiveRootOf(node, ancestors);
|
|
7163
|
+
const tab = root ? tabTriggerOf(root, node) : undefined;
|
|
7164
|
+
return tab ? tabLabel(tab) || null : null;
|
|
7165
|
+
}
|
|
7166
|
+
return null;
|
|
6904
7167
|
}
|
|
6905
7168
|
|
|
6906
7169
|
/**
|
|
@@ -6958,9 +7221,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
6958
7221
|
const FROM_ROW_KEY = 'builder.chrome.fromRow';
|
|
6959
7222
|
const ADD_ITEM_KEY = 'builder.chrome.addItem';
|
|
6960
7223
|
/** The kind label a composite part's selection badge shows (Card v2: "Card · card.title"). */
|
|
6961
|
-
const COMPOSITE_KIND_KEYS = { card: 'builder.chrome.kindCard', accordion: 'builder.chrome.kindAccordion', disclosure: 'builder.chrome.kindDisclosure' };
|
|
7224
|
+
const COMPOSITE_KIND_KEYS = { card: 'builder.chrome.kindCard', accordion: 'builder.chrome.kindAccordion', disclosure: 'builder.chrome.kindDisclosure', tabs: 'builder.chrome.kindTabs', slider: 'builder.chrome.kindSlider' };
|
|
7225
|
+
const VIDEO_TYPE = 'Video';
|
|
7226
|
+
const VIDEO_KIND_KEY = 'builder.chrome.kindVideo';
|
|
7227
|
+
const VIDEO_POSTER_ONLY_KEY = 'builder.chrome.videoPosterOnly';
|
|
7228
|
+
const VIDEO_FILE_KEY = 'builder.chrome.videoFile';
|
|
6962
7229
|
/** The parts that offer "Add item" on the selection toolbar (board 3: the "Add slide" pattern). */
|
|
6963
|
-
const ADD_ITEM_ROLES = new Set([
|
|
7230
|
+
const ADD_ITEM_ROLES = new Set([
|
|
7231
|
+
AccordionRole.Accordion, AccordionRole.Item, AccordionRole.Trigger, AccordionRole.Panel,
|
|
7232
|
+
TabsRole.Tabs, TabsRole.List, TabsRole.Trigger, TabsRole.Panels, TabsRole.Panel,
|
|
7233
|
+
]);
|
|
7234
|
+
/** Tabs say "Add tab" (board 3) — the button adds a tab AND its panel. */
|
|
7235
|
+
const ADD_TAB_KEY = 'builder.chrome.addTab';
|
|
6964
7236
|
class OverlayComponent {
|
|
6965
7237
|
constructor(elRef, instanceRegistry, overlayService, viewportService, presetRegistry, symbolRegistry, interactionPresetRegistry, interactionEngine, clipboard, styleRegistry, panelEventService, cdr, dragEngine, labels) {
|
|
6966
7238
|
this.elRef = elRef;
|
|
@@ -7175,11 +7447,17 @@ class OverlayComponent {
|
|
|
7175
7447
|
}
|
|
7176
7448
|
/** The kind badge's glyph: the composite's kind icon (board 9: "kind icon + 'Accordion · trigger'"). */
|
|
7177
7449
|
get selectedRoleIcon() {
|
|
7450
|
+
if (this.selectEntry?.node?.type === VIDEO_TYPE && !this.selectEntry.node.role)
|
|
7451
|
+
return 'ph-fill ph-film-strip';
|
|
7178
7452
|
return `ph-fill ${compositeKindGlyph(this.selectEntry?.node?.role ?? '')}`;
|
|
7179
7453
|
}
|
|
7180
|
-
/** A composite part's kind + role under the selection (board 3/8 "Selection kind badge"), or null.
|
|
7454
|
+
/** A composite part's kind + role under the selection (board 3/8 "Selection kind badge"), or null. A Video says
|
|
7455
|
+
* where it plays from and that the canvas shows its poster only (board 5: "Video · YouTube · poster only"). */
|
|
7181
7456
|
get selectedRoleBadge() {
|
|
7182
|
-
const
|
|
7457
|
+
const node = this.selectEntry?.node;
|
|
7458
|
+
if (node?.type === VIDEO_TYPE && !node.role)
|
|
7459
|
+
return this.videoBadge(node);
|
|
7460
|
+
const role = node?.role;
|
|
7183
7461
|
if (!role)
|
|
7184
7462
|
return null;
|
|
7185
7463
|
const prefix = role.split('.')[0];
|
|
@@ -7189,16 +7467,24 @@ class OverlayComponent {
|
|
|
7189
7467
|
const row = this.overlayService.selectedRow;
|
|
7190
7468
|
return row ? `${badge} · ${this.builderLabels(FROM_ROW_KEY, BUILDER_CHROME_EN[FROM_ROW_KEY]).replace('{row}', String(row))}` : badge;
|
|
7191
7469
|
}
|
|
7470
|
+
videoBadge(node) {
|
|
7471
|
+
const plan = planVideo(node, null);
|
|
7472
|
+
const where = plan.source === VideoSource.File
|
|
7473
|
+
? this.builderLabels(VIDEO_FILE_KEY, BUILDER_CHROME_EN[VIDEO_FILE_KEY])
|
|
7474
|
+
: VIDEO_PROVIDER_LABEL[plan.source];
|
|
7475
|
+
return `${this.builderLabels(VIDEO_KIND_KEY, BUILDER_CHROME_EN[VIDEO_KIND_KEY])} · ${where} · ${this.builderLabels(VIDEO_POSTER_ONLY_KEY, BUILDER_CHROME_EN[VIDEO_POSTER_ONLY_KEY])}`;
|
|
7476
|
+
}
|
|
7192
7477
|
/** The selection toolbar's name: the part's LAYER name (board 3 — "Header", "Accordion"), else its type. */
|
|
7193
7478
|
get toolbarName() {
|
|
7194
7479
|
return this.selectEntry?.node?.label || this.selectEntry?.componentName || '';
|
|
7195
7480
|
}
|
|
7196
|
-
/** An accordion part: the toolbar's "Add item" (CC2). */
|
|
7481
|
+
/** An accordion part: the toolbar's "Add item" (CC2); a Tabs part: "Add tab" (CC4a). */
|
|
7197
7482
|
get canAddItem() {
|
|
7198
7483
|
return ADD_ITEM_ROLES.has(this.selectEntry?.node?.role ?? '');
|
|
7199
7484
|
}
|
|
7200
7485
|
get addItemLabel() {
|
|
7201
|
-
|
|
7486
|
+
const key = isTabsPart(this.selectEntry?.node) ? ADD_TAB_KEY : ADD_ITEM_KEY;
|
|
7487
|
+
return this.builderLabels(key, BUILDER_CHROME_EN[key]);
|
|
7202
7488
|
}
|
|
7203
7489
|
onAddItem(event) {
|
|
7204
7490
|
event.stopPropagation();
|
|
@@ -7782,10 +8068,14 @@ function repeaterSubLabel(rows) {
|
|
|
7782
8068
|
/** Flatten `nodes` into rows, depth-first, honouring each node's expanded state. */
|
|
7783
8069
|
function flattenLayerTree(nodes, ctx) {
|
|
7784
8070
|
const out = [];
|
|
7785
|
-
walk(nodes, 0, out, ctx, false);
|
|
8071
|
+
walk(nodes, 0, out, ctx, false, undefined, []);
|
|
7786
8072
|
return out;
|
|
7787
8073
|
}
|
|
7788
|
-
|
|
8074
|
+
/** A Repeater that is a composite's item source From content (R3): an Accordion's root, a Tabs' list or panels. */
|
|
8075
|
+
function isCompositeFromContent(node) {
|
|
8076
|
+
return isAccordionRoot(node) || isExclusiveList(node) || isExclusivePanels(node);
|
|
8077
|
+
}
|
|
8078
|
+
function walk(nodes, depth, out, ctx, inTemplate, templateOf, ancestors) {
|
|
7789
8079
|
for (let i = 0; i < nodes.length; i++) {
|
|
7790
8080
|
const node = nodes[i];
|
|
7791
8081
|
const hasChildren = !!node.children?.length;
|
|
@@ -7795,7 +8085,7 @@ function walk(nodes, depth, out, ctx, inTemplate, templateOf) {
|
|
|
7795
8085
|
const bound = inTemplate && !!node.bindings?.some(b => b.prop !== REPEATER_SOURCE_PROP && b.source);
|
|
7796
8086
|
const label = node.label ?? node.type;
|
|
7797
8087
|
// An accordion item shows no text of its own: it names its question (board 3 "Item · Do you take pro…").
|
|
7798
|
-
const descriptor = rows ? null : (nodeDescriptor(node, label) ?? compositePartSub(node));
|
|
8088
|
+
const descriptor = rows ? null : (nodeDescriptor(node, label) ?? compositePartSub(node, ancestors));
|
|
7799
8089
|
out.push({
|
|
7800
8090
|
node,
|
|
7801
8091
|
icon: bound ? BOUND_ICON : (compositePartIcon(node.role) || ctx.iconOf(node.type) || DEFAULT_ICON),
|
|
@@ -7808,7 +8098,7 @@ function walk(nodes, depth, out, ctx, inTemplate, templateOf) {
|
|
|
7808
8098
|
indexInParent: i,
|
|
7809
8099
|
locked: isLockedPart(node),
|
|
7810
8100
|
template: !!templateOf?.alias,
|
|
7811
|
-
sub: rows ? (
|
|
8101
|
+
sub: rows ? (isCompositeFromContent(node) && ctx.fromContentLabel ? ctx.fromContentLabel(rows) : repeaterSubLabel(rows)) : shortDescriptor(descriptor),
|
|
7812
8102
|
descriptor,
|
|
7813
8103
|
bound,
|
|
7814
8104
|
roleChip: node.role ? roleChipLabel(node.role) : null,
|
|
@@ -7816,13 +8106,14 @@ function walk(nodes, depth, out, ctx, inTemplate, templateOf) {
|
|
|
7816
8106
|
});
|
|
7817
8107
|
if (!hasChildren || !expanded)
|
|
7818
8108
|
continue;
|
|
8109
|
+
const below = [...ancestors, node];
|
|
7819
8110
|
if (isRepeater && rows) {
|
|
7820
|
-
walk(node.children, depth + 1, out, ctx, true, rows);
|
|
8111
|
+
walk(node.children, depth + 1, out, ctx, true, rows, below);
|
|
7821
8112
|
if (rows.alias && rows.count > 1)
|
|
7822
|
-
out[out.length - 1].moreRows = { count: rows.count - 1, alias: rows.alias, depth: depth + 1, composite:
|
|
8113
|
+
out[out.length - 1].moreRows = { count: rows.count - 1, alias: rows.alias, depth: depth + 1, composite: isCompositeFromContent(node) };
|
|
7823
8114
|
}
|
|
7824
8115
|
else {
|
|
7825
|
-
walk(node.children, depth + 1, out, ctx, inTemplate);
|
|
8116
|
+
walk(node.children, depth + 1, out, ctx, inTemplate, undefined, below);
|
|
7826
8117
|
}
|
|
7827
8118
|
}
|
|
7828
8119
|
}
|
|
@@ -8071,10 +8362,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
8071
8362
|
|
|
8072
8363
|
/** Label of the always-present root crumb. The page surface itself, not a layout node. */
|
|
8073
8364
|
const BODY_CRUMB_LABEL = 'Body';
|
|
8074
|
-
/** A node's crumb: its layer name, and an accordion item's question after it ("Item · How long does…")
|
|
8075
|
-
|
|
8365
|
+
/** A node's crumb: its layer name, and an accordion item's question after it ("Item · How long does…") — a tab's and a
|
|
8366
|
+
* panel's words likewise ("Panel · Kitchens"). `ancestors` = the crumbs before it. */
|
|
8367
|
+
function crumbLabel(node, ancestors = []) {
|
|
8076
8368
|
const name = node.label || node.type;
|
|
8077
|
-
const sub = compositePartSub(node);
|
|
8369
|
+
const sub = compositePartSub(node, ancestors);
|
|
8078
8370
|
return sub ? `${name} · ${sub}` : name;
|
|
8079
8371
|
}
|
|
8080
8372
|
/**
|
|
@@ -8112,8 +8404,8 @@ class BreadcrumbBarComponent {
|
|
|
8112
8404
|
{ node: null, label: BODY_CRUMB_LABEL, icon: null, isCurrent: nodes.length === 0, key: BODY_CRUMB_LABEL },
|
|
8113
8405
|
...nodes.map((node, i) => ({
|
|
8114
8406
|
node,
|
|
8115
|
-
label: crumbLabel(node),
|
|
8116
|
-
icon:
|
|
8407
|
+
label: crumbLabel(node, nodes.slice(0, i)),
|
|
8408
|
+
icon: isAnyCompositeRoot(node) ? `ph-thin ${compositeKindGlyph(node.role)}` : null,
|
|
8117
8409
|
isCurrent: i === last,
|
|
8118
8410
|
key: node,
|
|
8119
8411
|
})),
|
|
@@ -8332,11 +8624,12 @@ class BuilderComponent {
|
|
|
8332
8624
|
this.auditRunning = false;
|
|
8333
8625
|
this.cdr.detectChanges();
|
|
8334
8626
|
}
|
|
8335
|
-
/** An Accordion from content whose source came back with no rows
|
|
8627
|
+
/** An Accordion — or Tabs (CC4a: its list) — from content whose source came back with no rows: what the audit names. */
|
|
8336
8628
|
emptySourceOf(node) {
|
|
8337
|
-
|
|
8629
|
+
const holder = isAccordionRoot(node) ? node : isExclusiveRoot(node) ? tabsListOf(node) : undefined;
|
|
8630
|
+
if (!holder)
|
|
8338
8631
|
return undefined;
|
|
8339
|
-
const instance = this.overlayService.getNodeRef(
|
|
8632
|
+
const instance = this.overlayService.getNodeRef(holder)?.componentRef?.instance;
|
|
8340
8633
|
if (!(instance instanceof BuilderRepeaterComponent) || !instance.source || instance.isLoading || instance.items.length)
|
|
8341
8634
|
return undefined;
|
|
8342
8635
|
const path = nodeAncestorPathInEither(this.layout, this.globalElements, node) ?? [];
|
|
@@ -8714,6 +9007,8 @@ class BuilderComponent {
|
|
|
8714
9007
|
this.scrollbarTranslate = 0; // counteracts Lenis wrapper scrollTop shifting position:absolute children
|
|
8715
9008
|
this.activeSidebar = 'components';
|
|
8716
9009
|
this.showSaveBlockDialog = false;
|
|
9010
|
+
/** A tab or a panel asked to be deleted alone (CC4a): "Delete tab and its panel" is offered for it. */
|
|
9011
|
+
this.pendingPairRemoval = null;
|
|
8717
9012
|
this.saveBlockName = '';
|
|
8718
9013
|
this.pendingBlockNode = null;
|
|
8719
9014
|
this.isPanning = false;
|
|
@@ -8769,6 +9064,7 @@ class BuilderComponent {
|
|
|
8769
9064
|
this.isDragging = v;
|
|
8770
9065
|
this.cdr.markForCheck();
|
|
8771
9066
|
}));
|
|
9067
|
+
this.sub.add(this.dragEngine.pairRemoval$.subscribe((node) => this.offerPairRemoval(node)));
|
|
8772
9068
|
this.sub.add(this.dragEngine.dropComplete$.subscribe((node) => {
|
|
8773
9069
|
this._selectAfterDrop(node);
|
|
8774
9070
|
this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
|
|
@@ -9314,6 +9610,8 @@ class BuilderComponent {
|
|
|
9314
9610
|
this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
|
|
9315
9611
|
}
|
|
9316
9612
|
else if (action === NodeAction.Duplicate) {
|
|
9613
|
+
if (this.duplicateTabPairOf(node))
|
|
9614
|
+
return;
|
|
9317
9615
|
const clone = this.deepCloneWithNewIds(node);
|
|
9318
9616
|
result.parent.splice(result.index + 1, 0, clone);
|
|
9319
9617
|
this.cdr.detectChanges();
|
|
@@ -9353,6 +9651,8 @@ class BuilderComponent {
|
|
|
9353
9651
|
break;
|
|
9354
9652
|
}
|
|
9355
9653
|
case ToolbarAction.Duplicate: {
|
|
9654
|
+
if (this.duplicateTabPairOf(node))
|
|
9655
|
+
break;
|
|
9356
9656
|
const result = this.findNodeInTree(node, this.layout) ?? this.findNodeInTree(node, this.globalElements);
|
|
9357
9657
|
if (result && !this.dragEngine.refuseInsert(node, this.siblingAncestors(node))) {
|
|
9358
9658
|
const clone = this.deepCloneWithNewIds(node);
|
|
@@ -9533,7 +9833,8 @@ class BuilderComponent {
|
|
|
9533
9833
|
break;
|
|
9534
9834
|
}
|
|
9535
9835
|
case ToolbarAction.AddItem: {
|
|
9536
|
-
this.
|
|
9836
|
+
if (!this.addTabPairFrom(node))
|
|
9837
|
+
this.addAccordionItemFrom(node);
|
|
9537
9838
|
break;
|
|
9538
9839
|
}
|
|
9539
9840
|
case ToolbarAction.AddSlide: {
|
|
@@ -9568,6 +9869,62 @@ class BuilderComponent {
|
|
|
9568
9869
|
if (trigger)
|
|
9569
9870
|
this.panelEventService.emit(PanelEventTypes.SELECT_NODE, { node: trigger });
|
|
9570
9871
|
}
|
|
9872
|
+
/** Toolbar ▸ Add tab on a Tabs part (CC4a): a fresh tab AND its panel after the selection's own tab (or at the end
|
|
9873
|
+
* for the root, the list, the holder) — ONE undo step, then the new tab is selected. False when `node` is no Tabs part. */
|
|
9874
|
+
addTabPairFrom(node) {
|
|
9875
|
+
const path = nodeAncestorPathInEither(this.layout, this.globalElements, node) ?? [node];
|
|
9876
|
+
const root = exclusiveRootOf(node, path);
|
|
9877
|
+
if (!root)
|
|
9878
|
+
return false;
|
|
9879
|
+
if (isTabsFromContent(root))
|
|
9880
|
+
return true;
|
|
9881
|
+
const anchor = path.find(n => isExclusiveItemPart(n)) ?? null;
|
|
9882
|
+
const factory = builderCompositeFactory(t => this.registry.createConfig(t), t => this.registry.baselineStyleProps(t));
|
|
9883
|
+
const pair = addTabPair(root, factory, anchor);
|
|
9884
|
+
if (!pair)
|
|
9885
|
+
return true;
|
|
9886
|
+
this.panelEventService.emit(PanelEventTypes.NODE_STRUCTURE_CHANGED, { node: root });
|
|
9887
|
+
this.panelEventService.emit(PanelEventTypes.SELECT_NODE, { node: pair.tab });
|
|
9888
|
+
return true;
|
|
9889
|
+
}
|
|
9890
|
+
/** Duplicate on a tab or a panel (toolbar, layer tree): the PAIR is duplicated under one new key. True when handled. */
|
|
9891
|
+
duplicateTabPairOf(node) {
|
|
9892
|
+
const root = tabsRootForPart(this.layout, this.globalElements, node);
|
|
9893
|
+
if (!root)
|
|
9894
|
+
return false;
|
|
9895
|
+
const copy = duplicateTabPair(root, node, n => this.deepCloneWithNewIds(n));
|
|
9896
|
+
if (!copy)
|
|
9897
|
+
return false;
|
|
9898
|
+
this.panelEventService.emit(PanelEventTypes.NODE_STRUCTURE_CHANGED, { node: root });
|
|
9899
|
+
this.panelEventService.emit(PanelEventTypes.SELECT_NODE, { node: copy });
|
|
9900
|
+
return true;
|
|
9901
|
+
}
|
|
9902
|
+
/** A tab or a panel asked to be deleted ALONE (every delete path asks the one guard): offer the pair. */
|
|
9903
|
+
offerPairRemoval(node) {
|
|
9904
|
+
const root = tabsRootForPart(this.layout, this.globalElements, node);
|
|
9905
|
+
if (!root)
|
|
9906
|
+
return;
|
|
9907
|
+
this.pendingPairRemoval = { node, root, label: tabPairLabel(root, node) };
|
|
9908
|
+
this.cdr.markForCheck();
|
|
9909
|
+
}
|
|
9910
|
+
/** "Delete tab and its panel": both go, ONE undo step. */
|
|
9911
|
+
confirmPairRemoval() {
|
|
9912
|
+
const pending = this.pendingPairRemoval;
|
|
9913
|
+
this.pendingPairRemoval = null;
|
|
9914
|
+
if (!pending || !deleteTabPair(pending.root, pending.node))
|
|
9915
|
+
return;
|
|
9916
|
+
this.selectedItem = null;
|
|
9917
|
+
this.overlayService.clearSelect();
|
|
9918
|
+
this.panelEventService.emit(PanelEventTypes.ELEMENT_DESELECT, null);
|
|
9919
|
+
this.panelEventService.emit(PanelEventTypes.NODE_STRUCTURE_CHANGED, { node: pending.root });
|
|
9920
|
+
}
|
|
9921
|
+
cancelPairRemoval() {
|
|
9922
|
+
this.pendingPairRemoval = null;
|
|
9923
|
+
}
|
|
9924
|
+
/** The pair-removal confirm's words, in the host's language. */
|
|
9925
|
+
pairRemovalText(key) {
|
|
9926
|
+
return this.labels(key, BUILDER_CHROME_EN[key]).replace('{tab}', this.pendingPairRemoval?.label ?? '');
|
|
9927
|
+
}
|
|
9571
9928
|
confirmSaveBlock() {
|
|
9572
9929
|
if (!this.saveBlockName.trim() || !this.pendingBlockNode)
|
|
9573
9930
|
return;
|
|
@@ -9808,11 +10165,11 @@ class BuilderComponent {
|
|
|
9808
10165
|
}
|
|
9809
10166
|
}
|
|
9810
10167
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: PanelEventService }, { token: DragEngineService }, { token: DataSourceRegistryService }, { token: ViewportService }, { token: FormStateService }, { token: CanvasDesignStateService }, { token: InteractionEngineService }, { token: InteractionPresetRegistryService }, { token: BuilderClipboardService }, { token: SymbolRegistryService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }, { token: MoveableCanvasService }, { token: SelectoCanvasService }, { token: StyleRegistryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9811
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements", pagePath: "pagePath", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }, { propertyName: "globalsBeforeRef", first: true, predicate: ["globalsBefore"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n @if (activeSidebar === 'pages') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'components') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tokens') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tree') {\n <app-layer-tree\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n }\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length || structureFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\"\n (dragstart)=\"onNativeDragStart($event)\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height is the frame a pinned global sits in (globalOverlayHeight: the canvas\n slot, or the page on a short page), bound in px so position:fixed + inset:0\n fills it (CSS calc(100%) would give the wrong containing-block height). -->\n @if (globalElements.length) {\n <div\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"globalOverlayHeight\"\n [style.transform]=\"viewportTransform\">\n <!-- BEFORE globals stay here, in the frame (a Dialog inside a header must fill the frame,\n not scroll away with the page). Their wrapper's height is what the in-flow ones take,\n and the page reserves it at its top (canvasHeaderReserve, iox-ui#19). -->\n @if (globalElementsBefore.length) {\n <div #globalsBefore class=\"iox-canvas-globals-before\">\n @for (item of globalElementsBefore; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n <!-- The PINNED after globals (a fixed bottom bar, a Dialog). An in-flow AFTER global (a\n footer) is drawn in the page, after its content (iox-ui#12). -->\n @for (item of globalElementsPinnedAfter; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [attr.data-bar-reserve]=\"canvasBarReserve\"\n [style.padding-block-start.px]=\"canvasHeaderReserve || null\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n @for (item of layout; track item) {\n <ng-container\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n\n <!-- In-flow AFTER globals (a footer) follow the page's content, inside the page's\n ground, as on the live site (iox-ui#12). Not part of the layout: the drag\n engine skips this wrapper (GLOBALS_AFTER_CLASS). -->\n @if (globalElementsInPage.length) {\n <div [class]=\"globalsAfterClass\">\n @for (item of globalElementsInPage; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;direction:ltr;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder #canvas-preview[data-bar-reserve=iox-bar-reserve]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}@container iox-canvas (max-width: 1200px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-laptop]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 991px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-tablet]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 767px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-mobile]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 479px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-small]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size;direction:ltr}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .global-elements-overlay .iox-canvas-globals-before{display:flow-root;pointer-events:none}.builder .global-elements-overlay .iox-canvas-globals-before ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"], dependencies: [{ kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i17.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i17.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i17.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i18.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i1$1.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId", "ioxPhrasing"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "rootSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
10168
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements", pagePath: "pagePath", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }, { propertyName: "globalsBeforeRef", first: true, predicate: ["globalsBefore"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n @if (activeSidebar === 'pages') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'components') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tokens') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tree') {\n <app-layer-tree\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n }\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length || structureFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\"\n (dragstart)=\"onNativeDragStart($event)\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height is the frame a pinned global sits in (globalOverlayHeight: the canvas\n slot, or the page on a short page), bound in px so position:fixed + inset:0\n fills it (CSS calc(100%) would give the wrong containing-block height). -->\n @if (globalElements.length) {\n <div\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"globalOverlayHeight\"\n [style.transform]=\"viewportTransform\">\n <!-- BEFORE globals stay here, in the frame (a Dialog inside a header must fill the frame,\n not scroll away with the page). Their wrapper's height is what the in-flow ones take,\n and the page reserves it at its top (canvasHeaderReserve, iox-ui#19). -->\n @if (globalElementsBefore.length) {\n <div #globalsBefore class=\"iox-canvas-globals-before\">\n @for (item of globalElementsBefore; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n <!-- The PINNED after globals (a fixed bottom bar, a Dialog). An in-flow AFTER global (a\n footer) is drawn in the page, after its content (iox-ui#12). -->\n @for (item of globalElementsPinnedAfter; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [attr.data-bar-reserve]=\"canvasBarReserve\"\n [style.padding-block-start.px]=\"canvasHeaderReserve || null\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n @for (item of layout; track item) {\n <ng-container\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n\n <!-- In-flow AFTER globals (a footer) follow the page's content, inside the page's\n ground, as on the live site (iox-ui#12). Not part of the layout: the drag\n engine skips this wrapper (GLOBALS_AFTER_CLASS). -->\n @if (globalElementsInPage.length) {\n <div [class]=\"globalsAfterClass\">\n @for (item of globalElementsInPage; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n <!-- A tab or a panel deleted alone (CC4a \u2014 composite T18): the pair goes together, so the delete is offered for\n both. Every delete path asks the one guard; this is its answer. -->\n <p-dialog [visible]=\"!!pendingPairRemoval\" (visibleChange)=\"$event || cancelPairRemoval()\" [modal]=\"true\" [closable]=\"true\"\n [draggable]=\"false\" [resizable]=\"false\" [header]=\"pairRemovalText('builder.chrome.deletePairTitle')\" styleClass=\"iox-pair-removal-dialog\"\n appendTo=\"body\" [style]=\"{ width: '400px' }\">\n <p class=\"pair-removal-body\">{{ pairRemovalText('builder.chrome.deletePairBody') }}</p>\n <ng-template pTemplate=\"footer\">\n <p-button size=\"small\" severity=\"secondary\" [label]=\"pairRemovalText('builder.chrome.cancel')\" (onClick)=\"cancelPairRemoval()\"></p-button>\n <p-button size=\"small\" severity=\"danger\" [label]=\"pairRemovalText('builder.chrome.deletePair')\" (onClick)=\"confirmPairRemoval()\"></p-button>\n </ng-template>\n </p-dialog>\n\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;direction:ltr;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder #canvas-preview[data-bar-reserve=iox-bar-reserve]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}@container iox-canvas (max-width: 1200px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-laptop]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 991px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-tablet]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 767px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-mobile]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 479px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-small]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size;direction:ltr}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .global-elements-overlay .iox-canvas-globals-before{display:flow-root;pointer-events:none}.builder .global-elements-overlay .iox-canvas-globals-before ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"], dependencies: [{ kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i17.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i17.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i17.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i18.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i19.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i20.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "component", type: i1$1.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId", "ioxPhrasing"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "rootSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
9812
10169
|
}
|
|
9813
10170
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, decorators: [{
|
|
9814
10171
|
type: Component,
|
|
9815
|
-
args: [{ selector: 'app-builder', standalone: false, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n @if (activeSidebar === 'pages') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'components') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tokens') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tree') {\n <app-layer-tree\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n }\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length || structureFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\"\n (dragstart)=\"onNativeDragStart($event)\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height is the frame a pinned global sits in (globalOverlayHeight: the canvas\n slot, or the page on a short page), bound in px so position:fixed + inset:0\n fills it (CSS calc(100%) would give the wrong containing-block height). -->\n @if (globalElements.length) {\n <div\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"globalOverlayHeight\"\n [style.transform]=\"viewportTransform\">\n <!-- BEFORE globals stay here, in the frame (a Dialog inside a header must fill the frame,\n not scroll away with the page). Their wrapper's height is what the in-flow ones take,\n and the page reserves it at its top (canvasHeaderReserve, iox-ui#19). -->\n @if (globalElementsBefore.length) {\n <div #globalsBefore class=\"iox-canvas-globals-before\">\n @for (item of globalElementsBefore; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n <!-- The PINNED after globals (a fixed bottom bar, a Dialog). An in-flow AFTER global (a\n footer) is drawn in the page, after its content (iox-ui#12). -->\n @for (item of globalElementsPinnedAfter; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [attr.data-bar-reserve]=\"canvasBarReserve\"\n [style.padding-block-start.px]=\"canvasHeaderReserve || null\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n @for (item of layout; track item) {\n <ng-container\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n\n <!-- In-flow AFTER globals (a footer) follow the page's content, inside the page's\n ground, as on the live site (iox-ui#12). Not part of the layout: the drag\n engine skips this wrapper (GLOBALS_AFTER_CLASS). -->\n @if (globalElementsInPage.length) {\n <div [class]=\"globalsAfterClass\">\n @for (item of globalElementsInPage; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;direction:ltr;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder #canvas-preview[data-bar-reserve=iox-bar-reserve]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}@container iox-canvas (max-width: 1200px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-laptop]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 991px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-tablet]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 767px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-mobile]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 479px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-small]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size;direction:ltr}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .global-elements-overlay .iox-canvas-globals-before{display:flow-root;pointer-events:none}.builder .global-elements-overlay .iox-canvas-globals-before ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
10172
|
+
args: [{ selector: 'app-builder', standalone: false, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n @if (activeSidebar === 'pages') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'components') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tokens') {\n <div class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n }\n @if (activeSidebar === 'tree') {\n <app-layer-tree\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n }\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length || structureFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\"\n (dragstart)=\"onNativeDragStart($event)\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height is the frame a pinned global sits in (globalOverlayHeight: the canvas\n slot, or the page on a short page), bound in px so position:fixed + inset:0\n fills it (CSS calc(100%) would give the wrong containing-block height). -->\n @if (globalElements.length) {\n <div\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"globalOverlayHeight\"\n [style.transform]=\"viewportTransform\">\n <!-- BEFORE globals stay here, in the frame (a Dialog inside a header must fill the frame,\n not scroll away with the page). Their wrapper's height is what the in-flow ones take,\n and the page reserves it at its top (canvasHeaderReserve, iox-ui#19). -->\n @if (globalElementsBefore.length) {\n <div #globalsBefore class=\"iox-canvas-globals-before\">\n @for (item of globalElementsBefore; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n <!-- The PINNED after globals (a fixed bottom bar, a Dialog). An in-flow AFTER global (a\n footer) is drawn in the page, after its content (iox-ui#12). -->\n @for (item of globalElementsPinnedAfter; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [attr.data-bar-reserve]=\"canvasBarReserve\"\n [style.padding-block-start.px]=\"canvasHeaderReserve || null\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n @for (item of layout; track item) {\n <ng-container\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n\n <!-- In-flow AFTER globals (a footer) follow the page's content, inside the page's\n ground, as on the live site (iox-ui#12). Not part of the layout: the drag\n engine skips this wrapper (GLOBALS_AFTER_CLASS). -->\n @if (globalElementsInPage.length) {\n <div [class]=\"globalsAfterClass\">\n @for (item of globalElementsInPage; track item) {\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n }\n </div>\n }\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n <!-- A tab or a panel deleted alone (CC4a \u2014 composite T18): the pair goes together, so the delete is offered for\n both. Every delete path asks the one guard; this is its answer. -->\n <p-dialog [visible]=\"!!pendingPairRemoval\" (visibleChange)=\"$event || cancelPairRemoval()\" [modal]=\"true\" [closable]=\"true\"\n [draggable]=\"false\" [resizable]=\"false\" [header]=\"pairRemovalText('builder.chrome.deletePairTitle')\" styleClass=\"iox-pair-removal-dialog\"\n appendTo=\"body\" [style]=\"{ width: '400px' }\">\n <p class=\"pair-removal-body\">{{ pairRemovalText('builder.chrome.deletePairBody') }}</p>\n <ng-template pTemplate=\"footer\">\n <p-button size=\"small\" severity=\"secondary\" [label]=\"pairRemovalText('builder.chrome.cancel')\" (onClick)=\"cancelPairRemoval()\"></p-button>\n <p-button size=\"small\" severity=\"danger\" [label]=\"pairRemovalText('builder.chrome.deletePair')\" (onClick)=\"confirmPairRemoval()\"></p-button>\n </ng-template>\n </p-dialog>\n\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;direction:ltr;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder #canvas-preview[data-bar-reserve=iox-bar-reserve]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}@container iox-canvas (max-width: 1200px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-laptop]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 991px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-tablet]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 767px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-mobile]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}@container iox-canvas (max-width: 479px){.builder #canvas-preview[data-bar-reserve=iox-bar-reserve-small]{padding-block-end:calc(var(--iox-tabbar-height, 64px) + var(--iox-border-width, 1px) + var(--iox-tabbar-inset, 0px) * 2)}}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size;direction:ltr}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .global-elements-overlay .iox-canvas-globals-before{display:flow-root;pointer-events:none}.builder .global-elements-overlay .iox-canvas-globals-before ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
9816
10173
|
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: FormStateService }, { type: CanvasDesignStateService }, { type: InteractionEngineService }, { type: InteractionPresetRegistryService }, { type: BuilderClipboardService }, { type: SymbolRegistryService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: MoveableCanvasService }, { type: SelectoCanvasService }, { type: StyleRegistryService }], propDecorators: { layout: [{
|
|
9817
10174
|
type: Input
|
|
9818
10175
|
}], dataSources: [{
|
|
@@ -10654,7 +11011,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
10654
11011
|
type: Output
|
|
10655
11012
|
}] } });
|
|
10656
11013
|
|
|
11014
|
+
/** Swiper's event → the interaction trigger it replays (componentEvents). */
|
|
11015
|
+
const SWIPER_TRIGGER = {
|
|
11016
|
+
slideChange: 'swiperSlideChange', reachBeginning: 'swiperReachBeginning', reachEnd: 'swiperReachEnd',
|
|
11017
|
+
autoplayStart: 'swiperAutoplayStart', autoplayStop: 'swiperAutoplayStop',
|
|
11018
|
+
};
|
|
10657
11019
|
class SliderContainerComponent {
|
|
11020
|
+
/** Every child — the slides AND the control parts (CC3). Split once per change, never in a getter. */
|
|
11021
|
+
set children(value) {
|
|
11022
|
+
this._children = value ?? [];
|
|
11023
|
+
const { lead, slides, trail } = splitSliderChildren(this._children);
|
|
11024
|
+
this.leadParts = lead;
|
|
11025
|
+
this.slides = slides;
|
|
11026
|
+
this.trailParts = trail;
|
|
11027
|
+
this.controlParts = hasSliderParts({ children: this._children });
|
|
11028
|
+
}
|
|
11029
|
+
get children() { return this._children; }
|
|
11030
|
+
/** A saved slider whose controls are not parts yet keeps Swiper-drawn ones. */
|
|
11031
|
+
get legacyControls() { return !this.controlParts; }
|
|
10658
11032
|
set style(v) {
|
|
10659
11033
|
if (v === this._style)
|
|
10660
11034
|
return;
|
|
@@ -10701,7 +11075,14 @@ class SliderContainerComponent {
|
|
|
10701
11075
|
this.dataSourceService = dataSourceService;
|
|
10702
11076
|
this.dsRegistry = dsRegistry;
|
|
10703
11077
|
this.panelEventService = panelEventService;
|
|
10704
|
-
this.
|
|
11078
|
+
this._children = [];
|
|
11079
|
+
/** The slides (into `.swiper-wrapper`); the template slide of a bound slider is `slides[0]`. */
|
|
11080
|
+
this.slides = [];
|
|
11081
|
+
/** Control parts before the first slide / after it — rendered beside the wrapper, inside `.swiper`. */
|
|
11082
|
+
this.leadParts = [];
|
|
11083
|
+
this.trailParts = [];
|
|
11084
|
+
/** The controls are parts: Swiper draws none of its own. */
|
|
11085
|
+
this.controlParts = false;
|
|
10705
11086
|
this._style = {};
|
|
10706
11087
|
this._previewStyle = {};
|
|
10707
11088
|
this._editShellStyle = { position: 'relative' };
|
|
@@ -10852,7 +11233,7 @@ class SliderContainerComponent {
|
|
|
10852
11233
|
});
|
|
10853
11234
|
}
|
|
10854
11235
|
refreshPreviews() {
|
|
10855
|
-
if (!this.items.length || !this.
|
|
11236
|
+
if (!this.items.length || !this.slides.length) {
|
|
10856
11237
|
this.previewSlides = [];
|
|
10857
11238
|
this.cdr.markForCheck();
|
|
10858
11239
|
this._scheduleReinit();
|
|
@@ -10860,16 +11241,16 @@ class SliderContainerComponent {
|
|
|
10860
11241
|
}
|
|
10861
11242
|
this.applyFirstItemToTemplate();
|
|
10862
11243
|
// Each preview slide = one cloned SliderSlide node with item data resolved
|
|
10863
|
-
this.previewSlides = this.items.slice(1).map(item => cloneNodesForItem([this.
|
|
11244
|
+
this.previewSlides = this.items.slice(1).map(item => cloneNodesForItem([this.slides[0]], item)[0]);
|
|
10864
11245
|
this.cdr.markForCheck();
|
|
10865
11246
|
this._scheduleReinit();
|
|
10866
11247
|
}
|
|
10867
11248
|
applyFirstItemToTemplate() {
|
|
10868
11249
|
const item = this.items[0];
|
|
10869
|
-
if (!item || !this.
|
|
11250
|
+
if (!item || !this.slides.length || !this.overlayService)
|
|
10870
11251
|
return;
|
|
10871
11252
|
// Bindings live on leaf nodes inside the template slide, so recurse into the tree
|
|
10872
|
-
this.applyItemToNodes([this.
|
|
11253
|
+
this.applyItemToNodes([this.slides[0]], item);
|
|
10873
11254
|
}
|
|
10874
11255
|
applyItemToNodes(nodes, item) {
|
|
10875
11256
|
for (const node of nodes) {
|
|
@@ -10895,7 +11276,7 @@ class SliderContainerComponent {
|
|
|
10895
11276
|
_isInTemplate(target) {
|
|
10896
11277
|
const search = (nodes) => nodes.some(c => c === target || c.id === target.id ||
|
|
10897
11278
|
(c.children?.length ? search(c.children) : false));
|
|
10898
|
-
return this.
|
|
11279
|
+
return this.slides.length > 0 && search([this.slides[0]]);
|
|
10899
11280
|
}
|
|
10900
11281
|
_scheduleReinit() {
|
|
10901
11282
|
this._destroySwiper();
|
|
@@ -10908,123 +11289,31 @@ class SliderContainerComponent {
|
|
|
10908
11289
|
const el = this.isPreview ? this.swiperEl?.nativeElement : this.editSwiperEl?.nativeElement;
|
|
10909
11290
|
if (!el)
|
|
10910
11291
|
return;
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
if (this.enableScrollbar)
|
|
10922
|
-
modules.push(Scrollbar);
|
|
10923
|
-
const config = {
|
|
10924
|
-
modules,
|
|
10925
|
-
direction: this.direction,
|
|
10926
|
-
slidesPerView: this.slidesPerView,
|
|
10927
|
-
slidesPerGroup: this.slidesPerGroup > 1 ? this.slidesPerGroup : 1,
|
|
10928
|
-
spaceBetween: this.spaceBetween,
|
|
10929
|
-
speed: this.isPreview ? this.speed : 300,
|
|
10930
|
-
// Always slide effect in edit mode — fade/cube etc. hide all but active slide
|
|
10931
|
-
effect: this.isPreview ? this.effect : 'slide',
|
|
10932
|
-
centeredSlides: this.centeredSlides,
|
|
10933
|
-
initialSlide: this.initialSlide,
|
|
10934
|
-
autoHeight: this.autoHeight,
|
|
10935
|
-
};
|
|
10936
|
-
if (this.isPreview) {
|
|
10937
|
-
// Full interactive config for preview
|
|
10938
|
-
config['grabCursor'] = this.grabCursor;
|
|
10939
|
-
config['slideToClickedSlide'] = this.slideToClickedSlide;
|
|
10940
|
-
config['loop'] = this.loop === 'loop';
|
|
10941
|
-
config['rewind'] = this.loop === 'rewind';
|
|
10942
|
-
if (this.freeMode) {
|
|
10943
|
-
modules.push(FreeMode);
|
|
10944
|
-
config['freeMode'] = true;
|
|
10945
|
-
}
|
|
10946
|
-
if (this.enableAutoplay) {
|
|
10947
|
-
modules.push(Autoplay);
|
|
10948
|
-
}
|
|
10949
|
-
if (this.enableKeyboard) {
|
|
10950
|
-
modules.push(Keyboard);
|
|
10951
|
-
}
|
|
10952
|
-
if (this.enableMousewheel) {
|
|
10953
|
-
modules.push(Mousewheel);
|
|
10954
|
-
}
|
|
10955
|
-
if (this.enableZoom) {
|
|
10956
|
-
modules.push(Zoom);
|
|
10957
|
-
}
|
|
10958
|
-
if (this.enableGrid) {
|
|
10959
|
-
modules.push(Grid);
|
|
10960
|
-
}
|
|
10961
|
-
if (this.enableParallax) {
|
|
10962
|
-
modules.push(Parallax);
|
|
10963
|
-
}
|
|
10964
|
-
const effectMap = {
|
|
10965
|
-
fade: EffectFade, cube: EffectCube, flip: EffectFlip,
|
|
10966
|
-
coverflow: EffectCoverflow, cards: EffectCards, creative: EffectCreative,
|
|
10967
|
-
};
|
|
10968
|
-
if (this.effect !== 'slide' && effectMap[this.effect]) {
|
|
10969
|
-
modules.push(effectMap[this.effect]);
|
|
10970
|
-
}
|
|
10971
|
-
if (this.enableNavigation) {
|
|
10972
|
-
config['navigation'] = { prevEl: '.swiper-button-prev', nextEl: '.swiper-button-next' };
|
|
10973
|
-
}
|
|
10974
|
-
if (this.enablePagination) {
|
|
10975
|
-
config['pagination'] = {
|
|
10976
|
-
el: '.swiper-pagination',
|
|
10977
|
-
type: this.paginationType,
|
|
10978
|
-
clickable: this.paginationClickable,
|
|
10979
|
-
dynamicBullets: this.paginationDynamicBullets,
|
|
10980
|
-
};
|
|
10981
|
-
}
|
|
10982
|
-
if (this.enableScrollbar)
|
|
10983
|
-
config['scrollbar'] = { el: '.swiper-scrollbar', draggable: true };
|
|
10984
|
-
if (this.enableAutoplay) {
|
|
10985
|
-
config['autoplay'] = {
|
|
10986
|
-
delay: this.autoplayDelay,
|
|
10987
|
-
pauseOnMouseEnter: this.autoplayPauseOnMouseEnter,
|
|
10988
|
-
disableOnInteraction: this.autoplayDisableOnInteraction,
|
|
10989
|
-
};
|
|
10990
|
-
}
|
|
10991
|
-
if (this.enableGrid && this.gridRows > 1)
|
|
10992
|
-
config['grid'] = { rows: this.gridRows };
|
|
10993
|
-
if (this.breakpoints) {
|
|
10994
|
-
try {
|
|
10995
|
-
config['breakpoints'] = JSON.parse(this.breakpoints);
|
|
10996
|
-
}
|
|
10997
|
-
catch { /* ignore */ }
|
|
10998
|
-
}
|
|
10999
|
-
config['on'] = {
|
|
11000
|
-
slideChange: (swiper) => this._onSwiperEvent('swiperSlideChange', swiper),
|
|
11001
|
-
reachBeginning: (swiper) => this._onSwiperEvent('swiperReachBeginning', swiper),
|
|
11002
|
-
reachEnd: (swiper) => this._onSwiperEvent('swiperReachEnd', swiper),
|
|
11003
|
-
autoplayStart: (swiper) => this._onSwiperEvent('swiperAutoplayStart', swiper),
|
|
11004
|
-
autoplayStop: (swiper) => this._onSwiperEvent('swiperAutoplayStop', swiper),
|
|
11005
|
-
};
|
|
11006
|
-
}
|
|
11007
|
-
else {
|
|
11008
|
-
// Edit mode: disable touch/swipe so builder click handlers work normally
|
|
11009
|
-
config['allowTouchMove'] = false;
|
|
11010
|
-
config['simulateTouch'] = false;
|
|
11011
|
-
if (this.enableNavigation) {
|
|
11012
|
-
config['navigation'] = { prevEl: '.swiper-button-prev', nextEl: '.swiper-button-next' };
|
|
11013
|
-
}
|
|
11014
|
-
if (this.enablePagination) {
|
|
11015
|
-
config['pagination'] = {
|
|
11016
|
-
el: '.swiper-pagination',
|
|
11017
|
-
type: this.paginationType,
|
|
11018
|
-
clickable: this.paginationClickable,
|
|
11019
|
-
dynamicBullets: this.paginationDynamicBullets,
|
|
11020
|
-
};
|
|
11021
|
-
}
|
|
11022
|
-
if (this.enableScrollbar)
|
|
11023
|
-
config['scrollbar'] = { el: '.swiper-scrollbar', draggable: false };
|
|
11024
|
-
}
|
|
11025
|
-
this._swiper = new Swiper(el, config);
|
|
11292
|
+
// The ONE Swiper decision (iox-ui `bootSlider` → `planSwiperOptions`): the EDIT canvas is tamed (no touch,
|
|
11293
|
+
// loop or autoplay; the parts still navigate); Preview is the live site's — the A11y module, the pause
|
|
11294
|
+
// control, the live region (CC3; Preview used to build its own config and skipped A11y).
|
|
11295
|
+
const swiper = await bootSlider(el, this.swiperInputs(), {
|
|
11296
|
+
edit: !this.isPreview,
|
|
11297
|
+
onEvent: this.isPreview ? (event, sw) => this._onSwiperEvent(SWIPER_TRIGGER[event] ?? event, sw) : undefined,
|
|
11298
|
+
});
|
|
11299
|
+
if (!swiper)
|
|
11300
|
+
return;
|
|
11301
|
+
this._swiper = swiper;
|
|
11026
11302
|
this.instanceRegistry.register(this.nodeId, this._swiper);
|
|
11027
11303
|
}
|
|
11304
|
+
swiperInputs() {
|
|
11305
|
+
return {
|
|
11306
|
+
dir: this.dir, direction: this.direction, loop: this.loop, slidesPerView: this.slidesPerView, slidesPerGroup: this.slidesPerGroup,
|
|
11307
|
+
spaceBetween: this.spaceBetween, speed: this.speed, effect: this.effect, grabCursor: this.grabCursor, centeredSlides: this.centeredSlides,
|
|
11308
|
+
initialSlide: this.initialSlide, autoHeight: this.autoHeight, slideToClickedSlide: this.slideToClickedSlide, freeMode: this.freeMode,
|
|
11309
|
+
enableNavigation: this.enableNavigation, enablePagination: this.enablePagination, enableScrollbar: this.enableScrollbar,
|
|
11310
|
+
enableAutoplay: this.enableAutoplay, enableKeyboard: this.enableKeyboard, enableMousewheel: this.enableMousewheel,
|
|
11311
|
+
enableZoom: this.enableZoom, enableGrid: this.enableGrid, enableParallax: this.enableParallax, autoplayDelay: this.autoplayDelay,
|
|
11312
|
+
autoplayPauseOnMouseEnter: this.autoplayPauseOnMouseEnter, autoplayDisableOnInteraction: this.autoplayDisableOnInteraction,
|
|
11313
|
+
paginationType: this.paginationType, paginationClickable: this.paginationClickable, paginationDynamicBullets: this.paginationDynamicBullets,
|
|
11314
|
+
gridRows: this.gridRows, breakpoints: this.breakpoints,
|
|
11315
|
+
};
|
|
11316
|
+
}
|
|
11028
11317
|
_destroySwiper() {
|
|
11029
11318
|
if (this._swiper) {
|
|
11030
11319
|
this._swiper.destroy(true, true);
|
|
@@ -11037,7 +11326,7 @@ class SliderContainerComponent {
|
|
|
11037
11326
|
if (eventName === 'swiperSlideChange') {
|
|
11038
11327
|
const prevIdx = swiper.previousIndex;
|
|
11039
11328
|
const activeIdx = swiper.activeIndex;
|
|
11040
|
-
const allSlides = this._source ? [this.
|
|
11329
|
+
const allSlides = this._source ? [this.slides[0], ...this.previewSlides] : this.slides;
|
|
11041
11330
|
if (prevIdx !== undefined && allSlides[prevIdx]) {
|
|
11042
11331
|
this.interactionEngine.replayTrigger(allSlides[prevIdx].id, 'swiperSlideInactive');
|
|
11043
11332
|
}
|
|
@@ -11067,11 +11356,17 @@ class SliderContainerComponent {
|
|
|
11067
11356
|
</div>
|
|
11068
11357
|
|
|
11069
11358
|
<div #editSwiperEl class="swiper swiper-edit" [attr.dir]="dir" [style.direction]="dir">
|
|
11359
|
+
<!-- CC3: the control PARTS (Pause, Previous, Next, Pagination) render inside .swiper, beside the
|
|
11360
|
+
wrapper (never in it — slider.md); selectable and styled on their own (R2). -->
|
|
11361
|
+
<ng-container *ngFor="let part of leadParts; trackBy: trackByNodeId">
|
|
11362
|
+
<ng-container [ioxRender]="part" (onClick)="childClick.emit($event)"
|
|
11363
|
+
(onMouseEnter)="childMouseEnter.emit($event)" (onMouseLeave)="childMouseLeave.emit()"></ng-container>
|
|
11364
|
+
</ng-container>
|
|
11070
11365
|
<div class="swiper-wrapper">
|
|
11071
11366
|
|
|
11072
|
-
<!-- NORMAL MODE: all
|
|
11367
|
+
<!-- NORMAL MODE: all slides draggable -->
|
|
11073
11368
|
<ng-container *ngIf="!source">
|
|
11074
|
-
<div *ngFor="let child of
|
|
11369
|
+
<div *ngFor="let child of slides; trackBy: trackByNodeId"
|
|
11075
11370
|
class="swiper-slide"
|
|
11076
11371
|
[ngClass]="['iox-outer-' + (child.styleId ?? child.id)]"
|
|
11077
11372
|
ioxDraggable
|
|
@@ -11089,22 +11384,24 @@ class SliderContainerComponent {
|
|
|
11089
11384
|
<!-- REPEAT MODE: template slide (item 0) + dimmed preview slides (items 1..N) -->
|
|
11090
11385
|
<ng-container *ngIf="source">
|
|
11091
11386
|
<!-- Template slide — draggable/editable, receives item[0] data -->
|
|
11092
|
-
<div *ngIf="
|
|
11387
|
+
<div *ngIf="slides.length"
|
|
11093
11388
|
class="swiper-slide"
|
|
11094
|
-
[ngClass]="['iox-outer-' + (
|
|
11389
|
+
[ngClass]="['iox-outer-' + (slides[0].styleId ?? slides[0].id)]"
|
|
11390
|
+
[attr.data-iox-row-idx]="0"
|
|
11095
11391
|
ioxDraggable
|
|
11096
|
-
[ioxDragData]="
|
|
11392
|
+
[ioxDragData]="slides[0]"
|
|
11097
11393
|
[ioxDragSourceId]="dropListId">
|
|
11098
11394
|
<ng-container
|
|
11099
|
-
[ioxRender]="
|
|
11395
|
+
[ioxRender]="slides[0]"
|
|
11100
11396
|
(onClick)="childClick.emit($event)"
|
|
11101
11397
|
(onMouseEnter)="childMouseEnter.emit($event)"
|
|
11102
11398
|
(onMouseLeave)="childMouseLeave.emit()">
|
|
11103
11399
|
</ng-container>
|
|
11104
11400
|
</div>
|
|
11105
11401
|
<!-- Preview slides — dimmed, non-interactive, data resolved from items 1..N -->
|
|
11106
|
-
<div *ngFor="let slide of previewSlides; trackBy: trackByNodeId"
|
|
11402
|
+
<div *ngFor="let slide of previewSlides; let i = index; trackBy: trackByNodeId"
|
|
11107
11403
|
class="swiper-slide slider-preview-slide"
|
|
11404
|
+
[attr.data-iox-row-idx]="i + 1"
|
|
11108
11405
|
[ngClass]="['iox-outer-' + (slide.styleId ?? slide.id)]">
|
|
11109
11406
|
<ng-container
|
|
11110
11407
|
[ioxRender]="slide"
|
|
@@ -11116,23 +11413,28 @@ class SliderContainerComponent {
|
|
|
11116
11413
|
</ng-container>
|
|
11117
11414
|
|
|
11118
11415
|
</div>
|
|
11416
|
+
<ng-container *ngFor="let part of trailParts; trackBy: trackByNodeId">
|
|
11417
|
+
<ng-container [ioxRender]="part" (onClick)="childClick.emit($event)"
|
|
11418
|
+
(onMouseEnter)="childMouseEnter.emit($event)" (onMouseLeave)="childMouseLeave.emit()"></ng-container>
|
|
11419
|
+
</ng-container>
|
|
11119
11420
|
|
|
11120
|
-
<div *ngIf="!source && !
|
|
11421
|
+
<div *ngIf="!source && !slides.length" class="slider-edit-placeholder" data-iox-chrome>
|
|
11121
11422
|
<i class="ph-thin ph-sliders-horizontal"></i>
|
|
11122
11423
|
<span>Click + in toolbar to add a slide</span>
|
|
11123
11424
|
</div>
|
|
11124
11425
|
|
|
11125
|
-
<div *ngIf="enablePagination" class="swiper-pagination"></div>
|
|
11426
|
+
<div *ngIf="legacyControls && enablePagination" class="swiper-pagination"></div>
|
|
11126
11427
|
<div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
|
|
11127
11428
|
</div>
|
|
11128
11429
|
|
|
11129
|
-
<!--
|
|
11130
|
-
and top:50%+transform always centers on the real slide height
|
|
11131
|
-
|
|
11430
|
+
<!-- Legacy (Swiper-drawn) nav buttons live on the shell, not inside .swiper, so overflow:hidden never
|
|
11431
|
+
clips them and top:50%+transform always centers on the real slide height. A slider whose controls
|
|
11432
|
+
are PARTS (CC3) draws none: the parts are the controls. -->
|
|
11433
|
+
<button *ngIf="legacyControls && enableNavigation" class="slider-edit-nav slider-edit-nav--prev" data-iox-chrome
|
|
11132
11434
|
(click)="_swiper?.slidePrev()" type="button" title="Previous slide">
|
|
11133
11435
|
<i class="ph-thin ph-caret-left"></i>
|
|
11134
11436
|
</button>
|
|
11135
|
-
<button *ngIf="enableNavigation" class="slider-edit-nav slider-edit-nav--next" data-iox-chrome
|
|
11437
|
+
<button *ngIf="legacyControls && enableNavigation" class="slider-edit-nav slider-edit-nav--next" data-iox-chrome
|
|
11136
11438
|
(click)="_swiper?.slideNext()" type="button" title="Next slide">
|
|
11137
11439
|
<i class="ph-thin ph-caret-right"></i>
|
|
11138
11440
|
</button>
|
|
@@ -11149,12 +11451,16 @@ class SliderContainerComponent {
|
|
|
11149
11451
|
[style.--swiper-navigation-color]="navigationColor || null"
|
|
11150
11452
|
[style.--swiper-navigation-size.px]="navigationSize || null"
|
|
11151
11453
|
[style.--swiper-pagination-color]="paginationColor || null">
|
|
11454
|
+
<ng-container *ngFor="let part of leadParts; trackBy: trackByNodeId">
|
|
11455
|
+
<ng-container [ioxRender]="part" (onClick)="childClick.emit($event)"
|
|
11456
|
+
(onMouseEnter)="childMouseEnter.emit($event)" (onMouseLeave)="childMouseLeave.emit()"></ng-container>
|
|
11457
|
+
</ng-container>
|
|
11152
11458
|
<div class="swiper-wrapper">
|
|
11153
11459
|
<!-- Swiper requires .swiper-slide as a DIRECT child of .swiper-wrapper.
|
|
11154
11460
|
Wrap each rendered slide in one (same structure as edit mode) so the
|
|
11155
11461
|
app-slider-slide host sits INSIDE the slide, not between it and the wrapper. -->
|
|
11156
11462
|
<ng-container *ngIf="!source">
|
|
11157
|
-
<div *ngFor="let child of
|
|
11463
|
+
<div *ngFor="let child of slides; trackBy: trackByNodeId"
|
|
11158
11464
|
class="swiper-slide"
|
|
11159
11465
|
[ngClass]="['iox-outer-' + (child.styleId ?? child.id)]">
|
|
11160
11466
|
<ng-container
|
|
@@ -11165,20 +11471,22 @@ class SliderContainerComponent {
|
|
|
11165
11471
|
</ng-container>
|
|
11166
11472
|
</div>
|
|
11167
11473
|
</ng-container>
|
|
11168
|
-
<!-- REPEAT: template slide + preview slides -->
|
|
11474
|
+
<!-- REPEAT: template slide + preview slides; each is a row (a lightbox opens at the clicked one) -->
|
|
11169
11475
|
<ng-container *ngIf="source">
|
|
11170
|
-
<div *ngIf="
|
|
11476
|
+
<div *ngIf="slides.length"
|
|
11171
11477
|
class="swiper-slide"
|
|
11172
|
-
[
|
|
11478
|
+
[attr.data-iox-row-idx]="0"
|
|
11479
|
+
[ngClass]="['iox-outer-' + (slides[0].styleId ?? slides[0].id)]">
|
|
11173
11480
|
<ng-container
|
|
11174
|
-
[ioxRender]="
|
|
11481
|
+
[ioxRender]="slides[0]"
|
|
11175
11482
|
(onClick)="childClick.emit($event)"
|
|
11176
11483
|
(onMouseEnter)="childMouseEnter.emit($event)"
|
|
11177
11484
|
(onMouseLeave)="childMouseLeave.emit()">
|
|
11178
11485
|
</ng-container>
|
|
11179
11486
|
</div>
|
|
11180
|
-
<div *ngFor="let slide of previewSlides; trackBy: trackByNodeId"
|
|
11487
|
+
<div *ngFor="let slide of previewSlides; let i = index; trackBy: trackByNodeId"
|
|
11181
11488
|
class="swiper-slide"
|
|
11489
|
+
[attr.data-iox-row-idx]="i + 1"
|
|
11182
11490
|
[ngClass]="['iox-outer-' + (slide.styleId ?? slide.id)]">
|
|
11183
11491
|
<ng-container
|
|
11184
11492
|
[ioxRender]="slide"
|
|
@@ -11189,9 +11497,13 @@ class SliderContainerComponent {
|
|
|
11189
11497
|
</div>
|
|
11190
11498
|
</ng-container>
|
|
11191
11499
|
</div>
|
|
11192
|
-
<
|
|
11193
|
-
|
|
11194
|
-
|
|
11500
|
+
<ng-container *ngFor="let part of trailParts; trackBy: trackByNodeId">
|
|
11501
|
+
<ng-container [ioxRender]="part" (onClick)="childClick.emit($event)"
|
|
11502
|
+
(onMouseEnter)="childMouseEnter.emit($event)" (onMouseLeave)="childMouseLeave.emit()"></ng-container>
|
|
11503
|
+
</ng-container>
|
|
11504
|
+
<div *ngIf="legacyControls && enableNavigation" class="swiper-button-prev"></div>
|
|
11505
|
+
<div *ngIf="legacyControls && enableNavigation" class="swiper-button-next"></div>
|
|
11506
|
+
<div *ngIf="legacyControls && enablePagination" class="swiper-pagination"></div>
|
|
11195
11507
|
<div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
|
|
11196
11508
|
</div>
|
|
11197
11509
|
`, isInline: true, styles: [":host{display:block}.swiper-wrapper{height:auto}.slider-edit-shell{position:relative;display:block}.swiper-edit{width:100%}.swiper-edit .swiper-slide{position:relative}.slider-edit-nav{position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,.5);background:#00000038;color:#fff;font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;transition:background .15s}.slider-edit-nav:hover{background:#00000073}.slider-edit-nav--prev{left:8px}.slider-edit-nav--next{right:8px}.slider-edit-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.slider-edit-placeholder i{font-size:22px}.slider-repeat-badge{position:absolute;top:0;left:0;z-index:10;display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px solid rgba(203,144,144,.5);border-radius:0 0 4px;opacity:0;transition:opacity .15s;-webkit-user-select:none;user-select:none;pointer-events:none}.slider-edit-shell:hover .slider-repeat-badge{opacity:1}.slider-repeat-badge-count{font-weight:600}.slider-badge-spin{animation:slider-badge-spin 1s linear infinite}@keyframes slider-badge-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.slider-preview-slide{opacity:.55;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId", "ioxPhrasing"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId", "ioxDragDisabled"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
|
|
@@ -11218,11 +11530,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
11218
11530
|
</div>
|
|
11219
11531
|
|
|
11220
11532
|
<div #editSwiperEl class="swiper swiper-edit" [attr.dir]="dir" [style.direction]="dir">
|
|
11533
|
+
<!-- CC3: the control PARTS (Pause, Previous, Next, Pagination) render inside .swiper, beside the
|
|
11534
|
+
wrapper (never in it — slider.md); selectable and styled on their own (R2). -->
|
|
11535
|
+
<ng-container *ngFor="let part of leadParts; trackBy: trackByNodeId">
|
|
11536
|
+
<ng-container [ioxRender]="part" (onClick)="childClick.emit($event)"
|
|
11537
|
+
(onMouseEnter)="childMouseEnter.emit($event)" (onMouseLeave)="childMouseLeave.emit()"></ng-container>
|
|
11538
|
+
</ng-container>
|
|
11221
11539
|
<div class="swiper-wrapper">
|
|
11222
11540
|
|
|
11223
|
-
<!-- NORMAL MODE: all
|
|
11541
|
+
<!-- NORMAL MODE: all slides draggable -->
|
|
11224
11542
|
<ng-container *ngIf="!source">
|
|
11225
|
-
<div *ngFor="let child of
|
|
11543
|
+
<div *ngFor="let child of slides; trackBy: trackByNodeId"
|
|
11226
11544
|
class="swiper-slide"
|
|
11227
11545
|
[ngClass]="['iox-outer-' + (child.styleId ?? child.id)]"
|
|
11228
11546
|
ioxDraggable
|
|
@@ -11240,22 +11558,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
11240
11558
|
<!-- REPEAT MODE: template slide (item 0) + dimmed preview slides (items 1..N) -->
|
|
11241
11559
|
<ng-container *ngIf="source">
|
|
11242
11560
|
<!-- Template slide — draggable/editable, receives item[0] data -->
|
|
11243
|
-
<div *ngIf="
|
|
11561
|
+
<div *ngIf="slides.length"
|
|
11244
11562
|
class="swiper-slide"
|
|
11245
|
-
[ngClass]="['iox-outer-' + (
|
|
11563
|
+
[ngClass]="['iox-outer-' + (slides[0].styleId ?? slides[0].id)]"
|
|
11564
|
+
[attr.data-iox-row-idx]="0"
|
|
11246
11565
|
ioxDraggable
|
|
11247
|
-
[ioxDragData]="
|
|
11566
|
+
[ioxDragData]="slides[0]"
|
|
11248
11567
|
[ioxDragSourceId]="dropListId">
|
|
11249
11568
|
<ng-container
|
|
11250
|
-
[ioxRender]="
|
|
11569
|
+
[ioxRender]="slides[0]"
|
|
11251
11570
|
(onClick)="childClick.emit($event)"
|
|
11252
11571
|
(onMouseEnter)="childMouseEnter.emit($event)"
|
|
11253
11572
|
(onMouseLeave)="childMouseLeave.emit()">
|
|
11254
11573
|
</ng-container>
|
|
11255
11574
|
</div>
|
|
11256
11575
|
<!-- Preview slides — dimmed, non-interactive, data resolved from items 1..N -->
|
|
11257
|
-
<div *ngFor="let slide of previewSlides; trackBy: trackByNodeId"
|
|
11576
|
+
<div *ngFor="let slide of previewSlides; let i = index; trackBy: trackByNodeId"
|
|
11258
11577
|
class="swiper-slide slider-preview-slide"
|
|
11578
|
+
[attr.data-iox-row-idx]="i + 1"
|
|
11259
11579
|
[ngClass]="['iox-outer-' + (slide.styleId ?? slide.id)]">
|
|
11260
11580
|
<ng-container
|
|
11261
11581
|
[ioxRender]="slide"
|
|
@@ -11267,23 +11587,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
11267
11587
|
</ng-container>
|
|
11268
11588
|
|
|
11269
11589
|
</div>
|
|
11590
|
+
<ng-container *ngFor="let part of trailParts; trackBy: trackByNodeId">
|
|
11591
|
+
<ng-container [ioxRender]="part" (onClick)="childClick.emit($event)"
|
|
11592
|
+
(onMouseEnter)="childMouseEnter.emit($event)" (onMouseLeave)="childMouseLeave.emit()"></ng-container>
|
|
11593
|
+
</ng-container>
|
|
11270
11594
|
|
|
11271
|
-
<div *ngIf="!source && !
|
|
11595
|
+
<div *ngIf="!source && !slides.length" class="slider-edit-placeholder" data-iox-chrome>
|
|
11272
11596
|
<i class="ph-thin ph-sliders-horizontal"></i>
|
|
11273
11597
|
<span>Click + in toolbar to add a slide</span>
|
|
11274
11598
|
</div>
|
|
11275
11599
|
|
|
11276
|
-
<div *ngIf="enablePagination" class="swiper-pagination"></div>
|
|
11600
|
+
<div *ngIf="legacyControls && enablePagination" class="swiper-pagination"></div>
|
|
11277
11601
|
<div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
|
|
11278
11602
|
</div>
|
|
11279
11603
|
|
|
11280
|
-
<!--
|
|
11281
|
-
and top:50%+transform always centers on the real slide height
|
|
11282
|
-
|
|
11604
|
+
<!-- Legacy (Swiper-drawn) nav buttons live on the shell, not inside .swiper, so overflow:hidden never
|
|
11605
|
+
clips them and top:50%+transform always centers on the real slide height. A slider whose controls
|
|
11606
|
+
are PARTS (CC3) draws none: the parts are the controls. -->
|
|
11607
|
+
<button *ngIf="legacyControls && enableNavigation" class="slider-edit-nav slider-edit-nav--prev" data-iox-chrome
|
|
11283
11608
|
(click)="_swiper?.slidePrev()" type="button" title="Previous slide">
|
|
11284
11609
|
<i class="ph-thin ph-caret-left"></i>
|
|
11285
11610
|
</button>
|
|
11286
|
-
<button *ngIf="enableNavigation" class="slider-edit-nav slider-edit-nav--next" data-iox-chrome
|
|
11611
|
+
<button *ngIf="legacyControls && enableNavigation" class="slider-edit-nav slider-edit-nav--next" data-iox-chrome
|
|
11287
11612
|
(click)="_swiper?.slideNext()" type="button" title="Next slide">
|
|
11288
11613
|
<i class="ph-thin ph-caret-right"></i>
|
|
11289
11614
|
</button>
|
|
@@ -11300,12 +11625,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
11300
11625
|
[style.--swiper-navigation-color]="navigationColor || null"
|
|
11301
11626
|
[style.--swiper-navigation-size.px]="navigationSize || null"
|
|
11302
11627
|
[style.--swiper-pagination-color]="paginationColor || null">
|
|
11628
|
+
<ng-container *ngFor="let part of leadParts; trackBy: trackByNodeId">
|
|
11629
|
+
<ng-container [ioxRender]="part" (onClick)="childClick.emit($event)"
|
|
11630
|
+
(onMouseEnter)="childMouseEnter.emit($event)" (onMouseLeave)="childMouseLeave.emit()"></ng-container>
|
|
11631
|
+
</ng-container>
|
|
11303
11632
|
<div class="swiper-wrapper">
|
|
11304
11633
|
<!-- Swiper requires .swiper-slide as a DIRECT child of .swiper-wrapper.
|
|
11305
11634
|
Wrap each rendered slide in one (same structure as edit mode) so the
|
|
11306
11635
|
app-slider-slide host sits INSIDE the slide, not between it and the wrapper. -->
|
|
11307
11636
|
<ng-container *ngIf="!source">
|
|
11308
|
-
<div *ngFor="let child of
|
|
11637
|
+
<div *ngFor="let child of slides; trackBy: trackByNodeId"
|
|
11309
11638
|
class="swiper-slide"
|
|
11310
11639
|
[ngClass]="['iox-outer-' + (child.styleId ?? child.id)]">
|
|
11311
11640
|
<ng-container
|
|
@@ -11316,20 +11645,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
11316
11645
|
</ng-container>
|
|
11317
11646
|
</div>
|
|
11318
11647
|
</ng-container>
|
|
11319
|
-
<!-- REPEAT: template slide + preview slides -->
|
|
11648
|
+
<!-- REPEAT: template slide + preview slides; each is a row (a lightbox opens at the clicked one) -->
|
|
11320
11649
|
<ng-container *ngIf="source">
|
|
11321
|
-
<div *ngIf="
|
|
11650
|
+
<div *ngIf="slides.length"
|
|
11322
11651
|
class="swiper-slide"
|
|
11323
|
-
[
|
|
11652
|
+
[attr.data-iox-row-idx]="0"
|
|
11653
|
+
[ngClass]="['iox-outer-' + (slides[0].styleId ?? slides[0].id)]">
|
|
11324
11654
|
<ng-container
|
|
11325
|
-
[ioxRender]="
|
|
11655
|
+
[ioxRender]="slides[0]"
|
|
11326
11656
|
(onClick)="childClick.emit($event)"
|
|
11327
11657
|
(onMouseEnter)="childMouseEnter.emit($event)"
|
|
11328
11658
|
(onMouseLeave)="childMouseLeave.emit()">
|
|
11329
11659
|
</ng-container>
|
|
11330
11660
|
</div>
|
|
11331
|
-
<div *ngFor="let slide of previewSlides; trackBy: trackByNodeId"
|
|
11661
|
+
<div *ngFor="let slide of previewSlides; let i = index; trackBy: trackByNodeId"
|
|
11332
11662
|
class="swiper-slide"
|
|
11663
|
+
[attr.data-iox-row-idx]="i + 1"
|
|
11333
11664
|
[ngClass]="['iox-outer-' + (slide.styleId ?? slide.id)]">
|
|
11334
11665
|
<ng-container
|
|
11335
11666
|
[ioxRender]="slide"
|
|
@@ -11340,9 +11671,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
11340
11671
|
</div>
|
|
11341
11672
|
</ng-container>
|
|
11342
11673
|
</div>
|
|
11343
|
-
<
|
|
11344
|
-
|
|
11345
|
-
|
|
11674
|
+
<ng-container *ngFor="let part of trailParts; trackBy: trackByNodeId">
|
|
11675
|
+
<ng-container [ioxRender]="part" (onClick)="childClick.emit($event)"
|
|
11676
|
+
(onMouseEnter)="childMouseEnter.emit($event)" (onMouseLeave)="childMouseLeave.emit()"></ng-container>
|
|
11677
|
+
</ng-container>
|
|
11678
|
+
<div *ngIf="legacyControls && enableNavigation" class="swiper-button-prev"></div>
|
|
11679
|
+
<div *ngIf="legacyControls && enableNavigation" class="swiper-button-next"></div>
|
|
11680
|
+
<div *ngIf="legacyControls && enablePagination" class="swiper-pagination"></div>
|
|
11346
11681
|
<div *ngIf="enableScrollbar" class="swiper-scrollbar"></div>
|
|
11347
11682
|
</div>
|
|
11348
11683
|
`, standalone: false, styles: [":host{display:block}.swiper-wrapper{height:auto}.slider-edit-shell{position:relative;display:block}.swiper-edit{width:100%}.swiper-edit .swiper-slide{position:relative}.slider-edit-nav{position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,.5);background:#00000038;color:#fff;font-size:16px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:10;transition:background .15s}.slider-edit-nav:hover{background:#00000073}.slider-edit-nav--prev{left:8px}.slider-edit-nav--next{right:8px}.slider-edit-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none}.slider-edit-placeholder i{font-size:22px}.slider-repeat-badge{position:absolute;top:0;left:0;z-index:10;display:flex;align-items:center;gap:.4rem;padding:4px 8px;font-size:11px;color:#cb9090;background:#cb909014;border:1px solid rgba(203,144,144,.5);border-radius:0 0 4px;opacity:0;transition:opacity .15s;-webkit-user-select:none;user-select:none;pointer-events:none}.slider-edit-shell:hover .slider-repeat-badge{opacity:1}.slider-repeat-badge-count{font-weight:600}.slider-badge-spin{animation:slider-badge-spin 1s linear infinite}@keyframes slider-badge-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.slider-preview-slide{opacity:.55;pointer-events:none}\n"] }]
|
|
@@ -12501,6 +12836,7 @@ class DialogComponentConfig extends ComponentConfig {
|
|
|
12501
12836
|
showCloseButton: 'checkbox',
|
|
12502
12837
|
consentRole: 'checkbox',
|
|
12503
12838
|
closeAbove: 'select',
|
|
12839
|
+
colorScheme: 'select',
|
|
12504
12840
|
ariaLabel: 'text',
|
|
12505
12841
|
};
|
|
12506
12842
|
this.traits = [
|
|
@@ -12513,6 +12849,8 @@ class DialogComponentConfig extends ComponentConfig {
|
|
|
12513
12849
|
new TraitConfig('consentRole', 'Consent banner role', TraitInputType.Checkbox, undefined, false),
|
|
12514
12850
|
...buildDialogCloseAboveTraits(),
|
|
12515
12851
|
...buildAccessibleNameTraits(),
|
|
12852
|
+
// CC3 (board 6): a dialog may wear a section colour scheme — the Lightbox is Inverse.
|
|
12853
|
+
buildColorSchemeTrait(),
|
|
12516
12854
|
];
|
|
12517
12855
|
this.styleTraits = buildFullStyleTraits();
|
|
12518
12856
|
this.applyStyleDefaults({
|
|
@@ -12707,6 +13045,64 @@ class GalleryConfig extends ComponentConfig {
|
|
|
12707
13045
|
}
|
|
12708
13046
|
}
|
|
12709
13047
|
|
|
13048
|
+
/**
|
|
13049
|
+
* Video (CC3 — complex-components plan §7b, board 5): a LEAF. The RENDER component is the shared `iox-video`
|
|
13050
|
+
* (`IoxVideoComponent` in @vectoriox/iox-ui) — the canvas shows its poster only; Preview and the live site run the
|
|
13051
|
+
* media-core runtime (the YouTube / Vimeo facade behind consent, the background video's pause button).
|
|
13052
|
+
*
|
|
13053
|
+
* Bindings ▸ Media · Behaviour · Accessibility · Privacy (boards 5 and 9). The address and the poster are bindable (a
|
|
13054
|
+
* project's video field) and work inside a Repeater. Background mode and autoplay act on an uploaded file; a YouTube
|
|
13055
|
+
* or Vimeo video is always the facade (D5) and plays on a click, after consent — the panel says so.
|
|
13056
|
+
*/
|
|
13057
|
+
class VideoConfig extends ComponentConfig {
|
|
13058
|
+
constructor() {
|
|
13059
|
+
super('Video', 'iox-video', 'ph-thin ph-film-strip', 'Media');
|
|
13060
|
+
const embed = { trait: VideoTrait.Source, values: [VideoSource.YouTube, VideoSource.Vimeo] };
|
|
13061
|
+
const file = { trait: VideoTrait.Source, values: VideoSource.File };
|
|
13062
|
+
this.traits = [
|
|
13063
|
+
// ── Media ─────────────────────────────────────────────────────
|
|
13064
|
+
new TraitConfig('__mediaLabel', 'Media', TraitInputType.SectionLabel),
|
|
13065
|
+
new TraitConfig(VideoTrait.Source, 'Source', TraitInputType.Select, [
|
|
13066
|
+
{ label: 'YouTube', value: VideoSource.YouTube },
|
|
13067
|
+
{ label: 'Vimeo', value: VideoSource.Vimeo },
|
|
13068
|
+
{ label: 'Uploaded file', value: VideoSource.File },
|
|
13069
|
+
], VideoSource.YouTube),
|
|
13070
|
+
new TraitConfig(VideoTrait.Src, 'Video address', TraitInputType.Media, { mode: 'compact' }, ''),
|
|
13071
|
+
new TraitConfig(VideoTrait.Poster, 'Poster', TraitInputType.Media, { mode: 'compact' }, ''),
|
|
13072
|
+
// ── Behaviour ─────────────────────────────────────────────────
|
|
13073
|
+
new TraitConfig('__behaviourLabel', 'Behaviour', TraitInputType.SectionLabel),
|
|
13074
|
+
new TraitConfig(VideoTrait.Mode, 'Mode', TraitInputType.Select, [
|
|
13075
|
+
{ label: 'Inline · with controls', value: VideoMode.Inline },
|
|
13076
|
+
{ label: 'Background · muted loop', value: VideoMode.Background },
|
|
13077
|
+
], VideoMode.Inline),
|
|
13078
|
+
new TraitConfig(VideoTrait.Autoplay, 'Autoplay (muted)', TraitInputType.Checkbox, undefined, false),
|
|
13079
|
+
// ── Accessibility ─────────────────────────────────────────────
|
|
13080
|
+
new TraitConfig('__accessibilityLabel', 'Accessibility', TraitInputType.SectionLabel),
|
|
13081
|
+
new TraitConfig(VideoTrait.Title, 'Title (names the play button)', TraitInputType.Text, undefined, ''),
|
|
13082
|
+
new TraitConfig(VideoTrait.Captions, 'Captions (.vtt)', TraitInputType.Media, { mode: 'compact' }, '', undefined, file),
|
|
13083
|
+
new TraitConfig(VideoTrait.CaptionsLang, 'Captions language', TraitInputType.Text, undefined, 'en', undefined, file),
|
|
13084
|
+
// ── Privacy ───────────────────────────────────────────────────
|
|
13085
|
+
new TraitConfig('__privacyLabel', 'Privacy', TraitInputType.SectionLabel),
|
|
13086
|
+
new TraitConfig(VideoTrait.Consent, 'Consent', TraitInputType.Select, [
|
|
13087
|
+
{ label: 'Marketing', value: ConsentCategory.Marketing },
|
|
13088
|
+
], ConsentCategory.Marketing, undefined, embed),
|
|
13089
|
+
];
|
|
13090
|
+
// A media frame: size, spacing, border (radius), effects (the `media` style's transition and hover scale).
|
|
13091
|
+
this.styleTraits = [
|
|
13092
|
+
new PositionGroupStyleConfig(),
|
|
13093
|
+
new SizeGroupStyleConfig(),
|
|
13094
|
+
new SpacingGroupStyleConfig(),
|
|
13095
|
+
new BorderGroupStyleConfig(),
|
|
13096
|
+
new EffectsGroupStyleConfig(),
|
|
13097
|
+
];
|
|
13098
|
+
// A 16 / 9 frame that fills its column — the size the poster and the player take.
|
|
13099
|
+
this.applyStyleDefaults({
|
|
13100
|
+
width: '100%',
|
|
13101
|
+
aspectRatio: '16 / 9',
|
|
13102
|
+
});
|
|
13103
|
+
}
|
|
13104
|
+
}
|
|
13105
|
+
|
|
12710
13106
|
/**
|
|
12711
13107
|
* Field's styleable sub-elements (board 7 — "the failure design"), for the Style panel's picker.
|
|
12712
13108
|
* Class names come from the SHARED `PART_CLASS_NAMES` (`@vectoriox/iox-ui`) — the same map the
|
|
@@ -13329,6 +13725,11 @@ class SliderContainerConfig extends ComponentConfig {
|
|
|
13329
13725
|
// ── Responsive ────────────────────────────────────────────────────
|
|
13330
13726
|
new TraitConfig('__responsiveLabel', 'Responsive', TraitInputType.SectionLabel),
|
|
13331
13727
|
new TraitConfig('breakpoints', 'Breakpoints (JSON)', TraitInputType.Textarea, undefined, ''),
|
|
13728
|
+
// ── Accessibility (CC3, board 6) ──────────────────────────────────
|
|
13729
|
+
// The carousel's name: a named carousel is a `region` landmark ("What clients say"). The pause control
|
|
13730
|
+
// is a part added while Autoplay is on (WCAG 2.2.2) — the panel says so under this group.
|
|
13731
|
+
new TraitConfig('__accessibilityLabel', 'Accessibility', TraitInputType.SectionLabel),
|
|
13732
|
+
new TraitConfig(A11yTrait.AccessibleName, 'Carousel name', TraitInputType.Text, undefined, ''),
|
|
13332
13733
|
];
|
|
13333
13734
|
this.styleTraits = [
|
|
13334
13735
|
new PositionGroupStyleConfig(),
|
|
@@ -13362,5 +13763,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
|
|
|
13362
13763
|
* Generated bundle index. Do not edit.
|
|
13363
13764
|
*/
|
|
13364
13765
|
|
|
13365
|
-
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BOUND_ICON, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_FONT_LINK_ATTR, BUILDER_TOKEN_SCOPE_CLASS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDialogComponent, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CANVAS_EMPTY_IMAGE_LABEL_EN, CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_ROOT_RESET_CSS, COMPOSITE_PRESETS, CanvasDesignStateService, CanvasShowState, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, CompositeCanvasService, CompositePaletteGroup, CompositePresetId, DEFAULT_ESSENTIAL_TRAITS, DESCRIPTOR_MAX_LENGTH, DRAG_HANDLE_ATTR, DROP_REFUSED_CLASS, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, EDITOR_CHROME_ATTRIBUTE, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_STATE_BLOCK_TYPES, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, 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, PALETTE_PRESET_DEFS, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, REPEATER_SOURCE_PROP, REPEATER_TYPE, ROOT_ESCAPE_PX, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, RootZone, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SelectionReveal, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StructureFix, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SubmitButtonComponentConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VALIDATION_STATES, VALIDATION_STATE_CLASS, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, auditNodes, bindingLabel, bindingLabels, buildCompositePreset, buildConsentPreset, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, builderCompositeFactory, canFluidise, canvasEmptyImageLabel, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropChildren, dropTargetLabel, escapesRoot, essentialTraitsFor, findNodeLocation, findParentOf, flattenLayerTree, flattenSchemaFields, fluidiseProps, generateNodeId, globalCanvasLayer, insertRefusal, insertRefusalLabel, isHiddenAt, isHiddenAtRest, isPinnedGlobal, isSpecimen, markSpecimen, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, nodeDescriptor, nodeStyleMap, overlayGlobals, pathToNode, refreshSubtree, regenerateNodeIds, removalRefusal, reorderProps, repeaterSourceAlias, repeaterSubLabel, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, retypeNode, rootEscapeBand, rootZone, selectionRevealOf, setStyleTrait, setTrait, shortDescriptor, sourceContentType, sourceTypeLabel, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, syncNodeInputs, textNode, upsertNodeStyles, visibilityProps, walkNodes, wrapNode };
|
|
13766
|
+
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BOUND_ICON, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_FONT_LINK_ATTR, BUILDER_TOKEN_SCOPE_CLASS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDialogComponent, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CANVAS_EMPTY_IMAGE_LABEL_EN, CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_ROOT_RESET_CSS, COMPOSITE_PRESETS, CanvasDesignStateService, CanvasShowState, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, CompositeCanvasService, CompositePaletteGroup, CompositePresetId, DEFAULT_ESSENTIAL_TRAITS, DESCRIPTOR_MAX_LENGTH, DRAG_HANDLE_ATTR, DROP_REFUSED_CLASS, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, EDITOR_CHROME_ATTRIBUTE, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_STATE_BLOCK_TYPES, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, 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, PALETTE_PRESET_DEFS, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, 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, SILENT_REFUSALS, STRUCTURAL_STATES, SUPPORTED_STATES, 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, arrayItemSchemaFields, arraySchemaFields, auditNodes, bindingLabel, bindingLabels, buildCompositePreset, buildConsentPreset, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, builderCompositeFactory, canFluidise, canvasEmptyImageLabel, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, deleteTabPair, dropChildren, dropTargetLabel, duplicateTabPair, escapesRoot, essentialTraitsFor, findNodeLocation, findParentOf, flattenLayerTree, flattenSchemaFields, fluidiseProps, generateNodeId, globalCanvasLayer, insertRefusal, insertRefusalLabel, isHiddenAt, isHiddenAtRest, isPinnedGlobal, isSpecimen, markSpecimen, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, nodeDescriptor, nodeStyleMap, overlayGlobals, pathToNode, refreshSubtree, regenerateNodeIds, removalRefusal, reorderProps, repeaterSourceAlias, repeaterSubLabel, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, retypeNode, rootEscapeBand, rootZone, selectionRevealOf, setStyleTrait, setTrait, shortDescriptor, sourceContentType, sourceTypeLabel, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, syncNodeInputs, tabPairLabel, tabsRootForPart, textNode, upsertNodeStyles, visibilityProps, walkNodes, wrapNode };
|
|
13366
13767
|
//# sourceMappingURL=vectoriox-iox-builder.mjs.map
|