@vectoriox/iox-builder 11.17.0 → 11.18.1

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, Optional, Inject, inject, NgZone, EventEmitter, createComponent, Output, Input, Directive, Component, ChangeDetectionStrategy, ViewChild, effect, ContentChildren, NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
2
+ import { InjectionToken, Injectable, Optional, Inject, inject, NgZone, EventEmitter, createComponent, Output, Input, Directive, Component, ChangeDetectionStrategy, ViewChild, untracked, effect, ContentChildren, NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
3
3
  import * as i2 from '@angular/common';
4
4
  import { DOCUMENT, CommonModule } from '@angular/common';
5
5
  import * as i17 from '@angular/forms';
@@ -17,16 +17,17 @@ import { TooltipModule } from 'primeng/tooltip';
17
17
  import * as i20 from 'primeng/dialog';
18
18
  import { DialogModule } from 'primeng/dialog';
19
19
  import * as i2$1 from '@vectoriox/iox-ui';
20
- import { nodeCssId, needsGroupName, resolveBoundValue, STRUCTURAL_STATES as STRUCTURAL_STATES$1, VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, TEXT_DIRECTION_KEY, TEXT_ALIGN_KEY, TextAlign, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, LinkType, MAX_LINES_KEY, A11yTrait, BREAKPOINTS, Breakpoint, withSchemeInk, stripSuppressedProps, DeclarationContext, planPartRule, gateHoverRule, planResponsiveRules, resolveStyleProps, planStateRule, splitOuterProps, OUTER_STYLE_PROPS, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, isLastEmittedStateKey, wrapInBreakpointQuery, BreakpointQueryKind, NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, applyElementStyle, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, 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';
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, collectContextSlotKeys, withContextSlots, needsAccessibleName, resolveInputs, setInputOverride, barReserveClass, 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
@@ -9512,6 +9575,26 @@ function pickInherited(css) {
9512
9575
  */
9513
9576
  const EDITOR_CANVAS_SCROLL = { lerp: 0.1, wheelMultiplier: 1, touchMultiplier: 1, orientation: 'vertical', infinite: false };
9514
9577
  class BuilderComponent {
9578
+ applyContextSlots(slotValues) {
9579
+ if (!Object.keys(slotValues).length)
9580
+ return;
9581
+ // The globals too: a header's cart button reads `$cart.count` (commerce E1c).
9582
+ const nodes = [...(this.layout ?? []), ...(this.globalElements ?? [])];
9583
+ const slotKeys = collectContextSlotKeys(nodes);
9584
+ if (!slotKeys.size)
9585
+ return;
9586
+ this.applyContextBindings(nodes, withContextSlots({}, slotValues), slotKeys);
9587
+ }
9588
+ reapplyContextAfterRender() {
9589
+ if (this.contextRenderTimer)
9590
+ clearTimeout(this.contextRenderTimer);
9591
+ this.contextRenderTimer = setTimeout(() => {
9592
+ this.contextRenderTimer = setTimeout(() => {
9593
+ this.contextRenderTimer = null;
9594
+ this.applyContextSlots(untracked(() => this.contextStore.values()));
9595
+ });
9596
+ });
9597
+ }
9515
9598
  /** The overlay's BEFORE wrapper comes and goes with its globals; its block size is the page's top reserve. */
9516
9599
  set globalsBeforeRef(ref) {
9517
9600
  this.observeBeforeGlobals(ref?.nativeElement ?? null);
@@ -9909,14 +9992,15 @@ class BuilderComponent {
9909
9992
  * Only nodes that read a slot are touched; nothing else re-renders (trap T2).
9910
9993
  */
9911
9994
  this.contextEffect = effect(() => {
9912
- const slotValues = this.contextStore.values();
9913
- if (!Object.keys(slotValues).length)
9914
- return;
9915
- const slotKeys = collectContextSlotKeys(this.layout);
9916
- if (!slotKeys.size)
9917
- return;
9918
- this.applyContextBindings(this.layout, withContextSlots({}, slotValues), slotKeys);
9995
+ this.applyContextSlots(this.contextStore.values());
9919
9996
  }, ...(ngDevMode ? [{ debugName: "contextEffect" }] : []));
9997
+ /** A part rendered AFTER its slot was written (a Dialog's children appear only while it is open — the cart drawer's
9998
+ * subtotal) gets the slot's current value too. Two ticks: after the bindings panel's own re-resolve of the same burst. */
9999
+ this.contextRenderTimer = null;
10000
+ /** Commerce E1c: the shop parts run on the canvas — the live site's runtime over an in-memory SAMPLE bag that starts
10001
+ * with one line (so the drawer has a line to design) and is never written anywhere. */
10002
+ this.canvasShop = null;
10003
+ this.instanceRegistry = inject(ComponentInstanceRegistryService);
9920
10004
  this.layout = [];
9921
10005
  this.dataSources = [];
9922
10006
  this.orgId = 'default';
@@ -10113,6 +10197,7 @@ class BuilderComponent {
10113
10197
  this.autoFitViewport();
10114
10198
  this.overlayService.setContainer(this.previewCanvas?.nativeElement || null);
10115
10199
  this.attachCanvasFloating();
10200
+ this.attachCanvasShop();
10116
10201
  // Builder dispatches synthetic 'scroll' events on .preview-lenis-content from
10117
10202
  // the Lenis callback — point the overlay scroll listener at that element.
10118
10203
  // (Not previewScrollRef which is the Lenis eventsTarget — dispatching there
@@ -10237,6 +10322,44 @@ class BuilderComponent {
10237
10322
  this.canvasTooltips = attachTooltips(root, { topLayer: false, container: canvasFloatingContainer });
10238
10323
  this.floatingSub = this.canvasDesignState.state$.subscribe(state => this.applyFloatingDesign(state.tooltips, state.toast));
10239
10324
  }
10325
+ attachCanvasShop() {
10326
+ const root = this.previewCanvas?.nativeElement;
10327
+ if (!root || this.canvasShop)
10328
+ return;
10329
+ const transport = simulatedCartTransport(storefrontCatalog(SAMPLE_PRODUCT), SAMPLE_STUDIO, { lines: [SAMPLE_CART_LINE] });
10330
+ this.canvasShop = attachShop(root, {
10331
+ transport,
10332
+ observe: true,
10333
+ productFor: el => {
10334
+ const product = this.interactionEngine.productFor(el);
10335
+ transport.addEntries(storefrontCatalog(product));
10336
+ return product;
10337
+ },
10338
+ publish: (slot, value) => this.contextStore.set(slot, value),
10339
+ swatches: product => (product === SAMPLE_PRODUCT ? SAMPLE_SWATCHES : null),
10340
+ openDrawer: () => {
10341
+ const drawer = this.findByRole([...(this.layout ?? []), ...(this.globalElements ?? [])], ShopRole.CartDrawer);
10342
+ if (drawer?.id)
10343
+ this.instanceRegistry.get(drawer.id)?.open?.();
10344
+ },
10345
+ goToCheckout: () => window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail: { type: 'startCheckout', params: {}, target: '' } })),
10346
+ onCloneClick: part => {
10347
+ const entry = this.overlayService.findNodeByElement(part);
10348
+ if (entry)
10349
+ this.handleClick({ element: entry.ref.element, node: entry.node, componentRef: entry.ref.componentRef });
10350
+ },
10351
+ });
10352
+ }
10353
+ findByRole(nodes, role) {
10354
+ for (const node of nodes ?? []) {
10355
+ if (node.role === role)
10356
+ return node;
10357
+ const below = this.findByRole(node.children ?? [], role);
10358
+ if (below)
10359
+ return below;
10360
+ }
10361
+ return null;
10362
+ }
10240
10363
  applyFloatingDesign(tooltips, toast) {
10241
10364
  const root = this.previewCanvas?.nativeElement;
10242
10365
  const runtime = this.canvasTooltips;
@@ -10281,6 +10404,10 @@ class BuilderComponent {
10281
10404
  return null;
10282
10405
  }
10283
10406
  ngOnDestroy() {
10407
+ if (this.contextRenderTimer)
10408
+ clearTimeout(this.contextRenderTimer);
10409
+ this.canvasShop?.destroy();
10410
+ this.canvasShop = null;
10284
10411
  this.floatingSub?.unsubscribe();
10285
10412
  this.canvasTooltips?.destroy();
10286
10413
  this.canvasTooltips = null;
@@ -11285,6 +11412,10 @@ class BuilderComponent {
11285
11412
  return node.styleOverrides?.['display'] ?? 'block';
11286
11413
  }
11287
11414
  handlePanelEvents(panelEvent) {
11415
+ if (panelEvent.type === PanelEventTypes.NODE_RENDERED) {
11416
+ this.reapplyContextAfterRender();
11417
+ return;
11418
+ }
11288
11419
  // Click-to-insert from the components panel (B2) — no drag, same node and same undo step.
11289
11420
  if (panelEvent.type === PanelEventTypes.INSERT_COMPONENT) {
11290
11421
  this.insertFromPanel(panelEvent.data);
@@ -14449,12 +14580,13 @@ function button(styleId, label, createConfig, styles = {}) {
14449
14580
  return withChildren(btn, [text(label, createConfig, BUTTON_LABEL_STYLE)]);
14450
14581
  }
14451
14582
  /** 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. */
14583
+ * furniture, and a specimen has no drop zone. A style that owns its min-height (the quantity stepper) keeps it. */
14453
14584
  function box(createConfig, styleId) {
14454
14585
  const node = createConfig('Container');
14455
14586
  if (styleId)
14456
14587
  wearing(node, styleId);
14457
- return styled(node, { minHeight: '0' });
14588
+ const ownsMinHeight = !!styleId && elementStyleById(styleId)?.base['minHeight'] !== undefined;
14589
+ return ownsMinHeight ? node ?? null : styled(node, { minHeight: '0' });
14458
14590
  }
14459
14591
  function boxed(styleId, createConfig, children, styles) {
14460
14592
  return withChildren(styled(box(createConfig, styleId), styles), children);