@vectoriox/iox-builder 11.17.0 → 11.18.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.
@@ -19,14 +19,15 @@ import { DialogModule } from 'primeng/dialog';
19
19
  import * as i2$1 from '@vectoriox/iox-ui';
20
20
  import { nodeCssId, needsGroupName, resolveBoundValue, STRUCTURAL_STATES as STRUCTURAL_STATES$1, VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, TEXT_DIRECTION_KEY, TEXT_ALIGN_KEY, TextAlign, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, LinkType, MAX_LINES_KEY, A11yTrait, BREAKPOINTS, Breakpoint, withSchemeInk, stripSuppressedProps, DeclarationContext, planPartRule, gateHoverRule, planResponsiveRules, resolveStyleProps, planStateRule, splitOuterProps, OUTER_STYLE_PROPS, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, isLastEmittedStateKey, wrapInBreakpointQuery, BreakpointQueryKind, NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, applyElementStyle, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, PROGRESS_ATTRIBUTE, interactionMarkerAttributes, cascadeChain, attachScrollProgress, ContextStore, ENTRANCE_ACTION_TYPES, EXIT_ACTION_TYPES, ComponentInstanceRegistryService, syncInteractionMarkers, entranceStartsHidden, isActionEnabled, restoreCountUp, SCROLL_PROGRESS_TRIGGER, armInteractionCounts, splitPressFeedback, isHoverPointer, isContextAction, contextActionEffect, resolveContextValue, effectiveActionType, INTERACTION_HIDDEN_ATTRIBUTE, COUNT_UP_ACTION, motionAllowed, settleInteraction, settleModeFor, playInteraction, resolveInteractionEasing, entranceDelay, repeatedRowIndex, entrancePreStateStyle, buildInteractionKeyframes, showToast, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, nodeTraitValue, syncNodeA11yAttributes, nodeA11yAttributes, safeSetInput, colorSchemeOf, withStateTransitions, composeVirtualTraits, stateNamesIn, partClassName, switchElementStyle, releaseElementStyle, countOverrides, setOverride, breakpointDef, breakpointForWidth, FormRenderState, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, attachTooltips, showToastDesign, TOOLTIP_ATTRIBUTE, hidingHeadersIn, HeaderRuntime, isScrolled, SCROLLED_ATTRIBUTE, syncSubmitButtons, IS_PREVIEW, buttonBlockHref, layerFrame, rectInLayer, placeFloating, FloatingAlign, popoverSide, bootSlider, IoxPageModule, PALETTE_PRESETS, elementStyleById, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
21
21
  export { ComponentInstanceRegistryService, VIRTUAL_TRAIT_KEYS, composeVirtualTraits } from '@vectoriox/iox-ui';
22
- import { StructureEdit, isAccordionPart, isDisclosurePart, isSliderPart, isTabsPart, isSegmentedPart, structureRefusal, StructureRefusal, SegmentedRole, SEGMENTED_MAX_OPTIONS, AccordionRole, SEGMENTED_MIN_OPTIONS, isExclusiveItemPart, TabsRole, ITEM_OPEN_STATE, readStyle, isSegmentedRoot, segmentedDefaultOn, segmentedOptions, isTabsRoot, isTabsFromContent, tabsTriggers, isAccordionRoot, isAccordionFromContent, accordionItems, initialAccordionOpen, accordionBehaviour, exclusiveItemLabel, exclusiveRootOf, tabTriggerOf, tabLabel, removeTabPair, tabPanelOf, tabsListOf, tabsPanelsOf, nextTabKey, CompositeBehaviourTrait, ACCORDION_DEFAULT_ONS, TabsBehaviourTrait, TABS_ACTIVATE_ONS, TABS_ORIENTATIONS, MarqueeTrait, MarqueeSpeed, MarqueeDirection, DisclosureRole, isExclusiveTrigger, onCompositeItemState, isCompositeItemOn, runCompositeItemAction, shouldScrollToItem, rowIndexOf as rowIndexOf$1, slideIndexForRow, isMarqueeRoot, compositeScopeId, compositeRowKeys, isExclusiveRoot, ExclusiveRuntime, exclusiveDefaultOf, exclusiveLabelOf, exclusiveBehaviourOf, exclusiveKindOf, compositeKindOfRole, AccordionRuntime, isToggleKind, MarqueeRuntime, closestAccordionItem, isAnyCompositeRoot, SceneRole, isExclusiveList, buildCardRecipe, buildBadge, buildChip, buildAccordionRecipe, buildDisclosureRecipe, buildTabsRecipe, buildSegmentedRecipe, buildLightboxRecipe, buildMarqueeRecipe, buildCounterRecipe, buildReadingBarRecipe, NO_ELEMENT_STYLE, CompositeKind, SliderRole, accordionItemLabel, segmentedOptionLabel, isExclusivePanels, isLockedPart, accordionRootOf, accordionItemOf, addAccordionItem, addTabPair, segmentedAddRefusal, addSegmentedOption, isCompositeItem, accordionHeadingLevel, isCompositePanel, isCompositeTrigger, splitSliderChildren, hasSliderParts, SLIDE_CAPTION_TRAIT, SLIDER_SCROLL_PLAYS_TRAIT, SLIDER_PLACEMENT_TRAIT, SliderPlacement, SliderFlag } from '@vectoriox/iox-ui/composite-core';
22
+ import { StructureEdit, isAccordionPart, isDisclosurePart, isSliderPart, isTabsPart, isSegmentedPart, structureRefusal, StructureRefusal, SegmentedRole, SEGMENTED_MAX_OPTIONS, AccordionRole, SEGMENTED_MIN_OPTIONS, isExclusiveItemPart, TabsRole, ITEM_OPEN_STATE, readStyle, isSegmentedRoot, segmentedDefaultOn, segmentedOptions, isTabsRoot, isTabsFromContent, tabsTriggers, isAccordionRoot, isAccordionFromContent, accordionItems, initialAccordionOpen, accordionBehaviour, exclusiveItemLabel, exclusiveRootOf, tabTriggerOf, tabLabel, removeTabPair, tabPanelOf, tabsListOf, tabsPanelsOf, nextTabKey, CompositeBehaviourTrait, ACCORDION_DEFAULT_ONS, TabsBehaviourTrait, TABS_ACTIVATE_ONS, TABS_ORIENTATIONS, MarqueeTrait, MarqueeSpeed, MarqueeDirection, DisclosureRole, isExclusiveTrigger, onCompositeItemState, isCompositeItemOn, ShopActionType, runShopAction, runCompositeItemAction, shouldScrollToItem, rowIndexOf as rowIndexOf$1, slideIndexForRow, isMarqueeRoot, compositeScopeId, compositeRowKeys, isExclusiveRoot, ExclusiveRuntime, exclusiveDefaultOf, exclusiveLabelOf, exclusiveBehaviourOf, exclusiveKindOf, compositeKindOfRole, AccordionRuntime, isToggleKind, MarqueeRuntime, closestAccordionItem, isAnyCompositeRoot, SceneRole, isExclusiveList, buildCardRecipe, buildBadge, buildChip, buildAccordionRecipe, buildDisclosureRecipe, buildTabsRecipe, buildSegmentedRecipe, buildLightboxRecipe, buildMarqueeRecipe, buildCounterRecipe, buildReadingBarRecipe, SHOP_RECIPES, ShopRecipe, NO_ELEMENT_STYLE, CompositeKind, SliderRole, accordionItemLabel, segmentedOptionLabel, isExclusivePanels, isLockedPart, attachShop, ShopRole, accordionRootOf, accordionItemOf, addAccordionItem, addTabPair, segmentedAddRefusal, addSegmentedOption, isCompositeItem, accordionHeadingLevel, isCompositePanel, isCompositeTrigger, splitSliderChildren, hasSliderParts, SLIDE_CAPTION_TRAIT, SLIDER_SCROLL_PLAYS_TRAIT, SLIDER_PLACEMENT_TRAIT, SliderPlacement, SliderFlag } from '@vectoriox/iox-ui/composite-core';
23
23
  import { formContentInHost, isInteractiveHost, findFormContentInHosts, FORM_CHANNEL_ROLE, channelHref, channelOpensNewTab, FORM_ACTIONS_ROLE, SUBMIT_BUTTON_NODE_TYPE, FORM_AFTER_SEND_TRAIT, FormAfterSend } from '@vectoriox/iox-ui/forms-core';
24
24
  import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
25
25
  import interact from 'interactjs';
26
26
  import { HeaderTrait, HeaderBehaviour, HeaderEdge, pageOpeningScheme } from '@vectoriox/iox-ui/design-core';
27
27
  import { map, catchError } from 'rxjs/operators';
28
28
  import * as i1 from '@angular/common/http';
29
- import { POPOVER_ACTION_TYPE_SET, TOAST_ACTION_TYPES, itemActionOf, BEHAVIOUR_ACTION_TYPES, SERVER_ELEMENT_ACTION_TYPES, CONSENT_ACTION_TYPES, opensAtClickedItem, openedDialogId, DIALOG_CLOSING_ATTRIBUTE, cancelExit, runExitMotion, popoverOpenersSelector, popoverDomId } from '@vectoriox/iox-ui/actions-core';
29
+ import { SAMPLE_PRODUCT, productInScope, simulatedCartTransport, storefrontCatalog, SAMPLE_STUDIO, SAMPLE_CART_LINE, SAMPLE_SWATCHES } from '@vectoriox/iox-ui/commerce-core';
30
+ import { POPOVER_ACTION_TYPE_SET, TOAST_ACTION_TYPES, itemActionOf, BEHAVIOUR_ACTION_TYPES, SHOP_ACTION_TYPES, SERVER_ELEMENT_ACTION_TYPES, CONSENT_ACTION_TYPES, opensAtClickedItem, openedDialogId, DIALOG_CLOSING_ATTRIBUTE, cancelExit, runExitMotion, popoverOpenersSelector, popoverDomId } from '@vectoriox/iox-ui/actions-core';
30
31
  import { NavRole, navShapeBreakpoint, entranceInteraction, entranceAction, sectionEntrances, EntrancePart } from '@vectoriox/iox-ui/generation-core';
31
32
  import { ScrollDriver, syncSceneAttributes, buildSceneRecipe } from '@vectoriox/iox-ui/scene-core';
32
33
  import Lenis from 'lenis';
@@ -5071,6 +5072,12 @@ class InteractionEngineService {
5071
5072
  this.executeBehaviourAction(node, action, selfEl);
5072
5073
  continue;
5073
5074
  }
5075
+ // Shop actions (commerce E1c). Add to cart SIMULATES: the canvas's shop runtime adds to an in-memory sample bag
5076
+ // (never a cart) and opens the drawer; Buy now is inert. The toast says what the live site does.
5077
+ if (SHOP_ACTION_TYPES.has(action.type)) {
5078
+ this.executeShopAction(action, selfEl);
5079
+ continue;
5080
+ }
5074
5081
  // Tier-B server actions (A2). ALWAYS a simulation here: the canvas is the ADMIN's
5075
5082
  // browser and a server action is a public-site write (an email, a webhook, a content
5076
5083
  // row) — the toast says what the LIVE site would do.
@@ -5120,6 +5127,17 @@ class InteractionEngineService {
5120
5127
  });
5121
5128
  this.contextStore.set(effect.slot, value);
5122
5129
  }
5130
+ executeShopAction(action, selfEl) {
5131
+ if (action.type === ShopActionType.AddToCart && selfEl)
5132
+ void runShopAction(selfEl, action.type);
5133
+ const detail = { type: action.type, params: action.params ?? {}, target: action.target };
5134
+ window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
5135
+ }
5136
+ /** The product a shop part on the canvas belongs to: its row's, else the route item's, else the page's first —
5137
+ * else the sample (board 10's vase), so an unbound part is still designable. */
5138
+ productFor(el) {
5139
+ return productInScope(this.rowItemFor(el), this.contextData) ?? SAMPLE_PRODUCT;
5140
+ }
5123
5141
  /** The repeater item this element belongs to — the nearest marked row ancestor. */
5124
5142
  rowItemFor(el) {
5125
5143
  const row = el?.closest?.('[data-iox-row-idx]');
@@ -6717,6 +6735,13 @@ var CompositePresetId;
6717
6735
  CompositePresetId["Marquee"] = "marquee";
6718
6736
  CompositePresetId["Counter"] = "counter";
6719
6737
  CompositePresetId["ReadingBar"] = "reading-bar";
6738
+ CompositePresetId["Price"] = "shop-price";
6739
+ CompositePresetId["Variants"] = "shop-variants";
6740
+ CompositePresetId["Quantity"] = "shop-quantity";
6741
+ CompositePresetId["AddToCart"] = "shop-add";
6742
+ CompositePresetId["BuyNow"] = "shop-buy-now";
6743
+ CompositePresetId["CartButton"] = "shop-cart-button";
6744
+ CompositePresetId["CartDrawer"] = "shop-cart-drawer";
6720
6745
  })(CompositePresetId || (CompositePresetId = {}));
6721
6746
  /** Where a composite sits in the Components panel (composite plan §6d): content blocks, or the interactive
6722
6747
  * composites (the Accordion — CC2, the Disclosure — CC2.1, Tabs — CC4a, the Segmented control — CC4b; the floating
@@ -6725,6 +6750,8 @@ var CompositePaletteGroup;
6725
6750
  (function (CompositePaletteGroup) {
6726
6751
  CompositePaletteGroup["ContentBlocks"] = "content-blocks";
6727
6752
  CompositePaletteGroup["Interactive"] = "interactive";
6753
+ /** Commerce E1c (C-D16, commerce board 10): the parts a product page and the bag are built from. */
6754
+ CompositePaletteGroup["Shop"] = "shop";
6728
6755
  })(CompositePaletteGroup || (CompositePaletteGroup = {}));
6729
6756
  const COMPOSITE_PRESETS = Object.freeze([
6730
6757
  {
@@ -6788,6 +6815,42 @@ const COMPOSITE_PRESETS = Object.freeze([
6788
6815
  description: 'A thin bar that fills as the page is read — or its section: change it in Interactions.',
6789
6816
  build: create => buildReadingBarRecipe(create),
6790
6817
  },
6818
+ // Commerce E1c — the shop parts (commerce board 10). Each fills from the product in scope and the bag at runtime.
6819
+ {
6820
+ id: CompositePresetId.Price, group: CompositePaletteGroup.Shop, name: 'Price', icon: 'ph-thin ph-coins',
6821
+ description: 'The chosen option\'s price in your studio\'s currency, and its earlier price struck through when there is one.',
6822
+ build: create => SHOP_RECIPES[ShopRecipe.Price](create),
6823
+ },
6824
+ {
6825
+ id: CompositePresetId.Variants, group: CompositePaletteGroup.Shop, name: 'Options', icon: 'ph-thin ph-swatches',
6826
+ description: 'One row per option — colours as swatches, a few words as chips, a long list as a menu. A sold-out option stays visible and says so.',
6827
+ build: create => SHOP_RECIPES[ShopRecipe.Variants](create),
6828
+ },
6829
+ {
6830
+ id: CompositePresetId.Quantity, group: CompositePaletteGroup.Shop, name: 'Quantity', icon: 'ph-thin ph-plus-minus',
6831
+ description: 'How many to add — never below one, never above what is in stock.',
6832
+ build: create => SHOP_RECIPES[ShopRecipe.Quantity](create),
6833
+ },
6834
+ {
6835
+ id: CompositePresetId.AddToCart, group: CompositePaletteGroup.Shop, name: 'Add to cart', icon: 'ph-thin ph-shopping-cart-simple',
6836
+ description: 'Adds the chosen option and quantity to the bag, reads "Added ✓" for a moment and opens the bag. On the canvas it adds a sample.',
6837
+ build: create => SHOP_RECIPES[ShopRecipe.Add](create),
6838
+ },
6839
+ {
6840
+ id: CompositePresetId.BuyNow, group: CompositePaletteGroup.Shop, name: 'Buy now', icon: 'ph-thin ph-lightning',
6841
+ description: 'Straight to checkout with the chosen option. Does nothing on the canvas.',
6842
+ build: create => SHOP_RECIPES[ShopRecipe.BuyNow](create),
6843
+ },
6844
+ {
6845
+ id: CompositePresetId.CartButton, group: CompositePaletteGroup.Shop, name: 'Cart button', icon: 'ph-thin ph-handbag',
6846
+ description: 'The bag with its live count — put it in your header. It opens the cart drawer.',
6847
+ build: create => SHOP_RECIPES[ShopRecipe.CartButton](create),
6848
+ },
6849
+ {
6850
+ id: CompositePresetId.CartDrawer, group: CompositePaletteGroup.Shop, name: 'Cart drawer', icon: 'ph-thin ph-tote',
6851
+ description: 'The bag: its lines, the subtotal with VAT included, Check out and Keep shopping. Slides in from the edge; a sheet on a phone.',
6852
+ build: create => SHOP_RECIPES[ShopRecipe.CartDrawer](create),
6853
+ },
6791
6854
  ]);
6792
6855
  /**
6793
6856
  * The builder's node factory for composite-core recipes: a NEW node as the palette creates one
@@ -9912,11 +9975,17 @@ class BuilderComponent {
9912
9975
  const slotValues = this.contextStore.values();
9913
9976
  if (!Object.keys(slotValues).length)
9914
9977
  return;
9915
- const slotKeys = collectContextSlotKeys(this.layout);
9978
+ // The globals too: a header's cart button reads `$cart.count` (commerce E1c).
9979
+ const nodes = [...(this.layout ?? []), ...(this.globalElements ?? [])];
9980
+ const slotKeys = collectContextSlotKeys(nodes);
9916
9981
  if (!slotKeys.size)
9917
9982
  return;
9918
- this.applyContextBindings(this.layout, withContextSlots({}, slotValues), slotKeys);
9983
+ this.applyContextBindings(nodes, withContextSlots({}, slotValues), slotKeys);
9919
9984
  }, ...(ngDevMode ? [{ debugName: "contextEffect" }] : []));
9985
+ /** Commerce E1c: the shop parts run on the canvas — the live site's runtime over an in-memory SAMPLE bag that starts
9986
+ * with one line (so the drawer has a line to design) and is never written anywhere. */
9987
+ this.canvasShop = null;
9988
+ this.instanceRegistry = inject(ComponentInstanceRegistryService);
9920
9989
  this.layout = [];
9921
9990
  this.dataSources = [];
9922
9991
  this.orgId = 'default';
@@ -10113,6 +10182,7 @@ class BuilderComponent {
10113
10182
  this.autoFitViewport();
10114
10183
  this.overlayService.setContainer(this.previewCanvas?.nativeElement || null);
10115
10184
  this.attachCanvasFloating();
10185
+ this.attachCanvasShop();
10116
10186
  // Builder dispatches synthetic 'scroll' events on .preview-lenis-content from
10117
10187
  // the Lenis callback — point the overlay scroll listener at that element.
10118
10188
  // (Not previewScrollRef which is the Lenis eventsTarget — dispatching there
@@ -10237,6 +10307,44 @@ class BuilderComponent {
10237
10307
  this.canvasTooltips = attachTooltips(root, { topLayer: false, container: canvasFloatingContainer });
10238
10308
  this.floatingSub = this.canvasDesignState.state$.subscribe(state => this.applyFloatingDesign(state.tooltips, state.toast));
10239
10309
  }
10310
+ attachCanvasShop() {
10311
+ const root = this.previewCanvas?.nativeElement;
10312
+ if (!root || this.canvasShop)
10313
+ return;
10314
+ const transport = simulatedCartTransport(storefrontCatalog(SAMPLE_PRODUCT), SAMPLE_STUDIO, { lines: [SAMPLE_CART_LINE] });
10315
+ this.canvasShop = attachShop(root, {
10316
+ transport,
10317
+ observe: true,
10318
+ productFor: el => {
10319
+ const product = this.interactionEngine.productFor(el);
10320
+ transport.addEntries(storefrontCatalog(product));
10321
+ return product;
10322
+ },
10323
+ publish: (slot, value) => this.contextStore.set(slot, value),
10324
+ swatches: product => (product === SAMPLE_PRODUCT ? SAMPLE_SWATCHES : null),
10325
+ openDrawer: () => {
10326
+ const drawer = this.findByRole([...(this.layout ?? []), ...(this.globalElements ?? [])], ShopRole.CartDrawer);
10327
+ if (drawer?.id)
10328
+ this.instanceRegistry.get(drawer.id)?.open?.();
10329
+ },
10330
+ goToCheckout: () => window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail: { type: 'startCheckout', params: {}, target: '' } })),
10331
+ onCloneClick: part => {
10332
+ const entry = this.overlayService.findNodeByElement(part);
10333
+ if (entry)
10334
+ this.handleClick({ element: entry.ref.element, node: entry.node, componentRef: entry.ref.componentRef });
10335
+ },
10336
+ });
10337
+ }
10338
+ findByRole(nodes, role) {
10339
+ for (const node of nodes ?? []) {
10340
+ if (node.role === role)
10341
+ return node;
10342
+ const below = this.findByRole(node.children ?? [], role);
10343
+ if (below)
10344
+ return below;
10345
+ }
10346
+ return null;
10347
+ }
10240
10348
  applyFloatingDesign(tooltips, toast) {
10241
10349
  const root = this.previewCanvas?.nativeElement;
10242
10350
  const runtime = this.canvasTooltips;
@@ -10281,6 +10389,8 @@ class BuilderComponent {
10281
10389
  return null;
10282
10390
  }
10283
10391
  ngOnDestroy() {
10392
+ this.canvasShop?.destroy();
10393
+ this.canvasShop = null;
10284
10394
  this.floatingSub?.unsubscribe();
10285
10395
  this.canvasTooltips?.destroy();
10286
10396
  this.canvasTooltips = null;
@@ -14449,12 +14559,13 @@ function button(styleId, label, createConfig, styles = {}) {
14449
14559
  return withChildren(btn, [text(label, createConfig, BUTTON_LABEL_STYLE)]);
14450
14560
  }
14451
14561
  /** A Container sized by its content: the palette Container's 200px drop-zone min-height is canvas
14452
- * furniture, and a specimen has no drop zone. */
14562
+ * furniture, and a specimen has no drop zone. A style that owns its min-height (the quantity stepper) keeps it. */
14453
14563
  function box(createConfig, styleId) {
14454
14564
  const node = createConfig('Container');
14455
14565
  if (styleId)
14456
14566
  wearing(node, styleId);
14457
- return styled(node, { minHeight: '0' });
14567
+ const ownsMinHeight = !!styleId && elementStyleById(styleId)?.base['minHeight'] !== undefined;
14568
+ return ownsMinHeight ? node ?? null : styled(node, { minHeight: '0' });
14458
14569
  }
14459
14570
  function boxed(styleId, createConfig, children, styles) {
14460
14571
  return withChildren(styled(box(createConfig, styleId), styles), children);