@useinsider/guido 3.8.1 → 3.8.2-beta.189e91c

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.
Files changed (86) hide show
  1. package/dist/@types/config/schemas.js +70 -66
  2. package/dist/components/organisms/email-preview/desktop-preview/DesktopPreview.vue.js +8 -8
  3. package/dist/components/organisms/email-preview/desktop-preview/DesktopPreview.vue2.js +15 -12
  4. package/dist/components/organisms/email-preview/mobile-preview/ContentView.vue.js +5 -5
  5. package/dist/components/organisms/email-preview/mobile-preview/ContentView.vue2.js +16 -15
  6. package/dist/components/organisms/unsubscribe/UnsubscribePageSelection.vue.js +5 -5
  7. package/dist/components/organisms/unsubscribe/UnsubscribePageSelection.vue2.js +42 -37
  8. package/dist/components/organisms/unsubscribe/UnsubscribeTypeSelection.vue.js +3 -3
  9. package/dist/components/organisms/unsubscribe/UnsubscribeTypeSelection.vue2.js +40 -37
  10. package/dist/composables/useHtmlCompiler.js +13 -10
  11. package/dist/composables/useHtmlValidator.js +180 -133
  12. package/dist/composables/usePreviewInteractionGuard.js +17 -0
  13. package/dist/composables/usePreviewMode.js +15 -14
  14. package/dist/composables/useRecommendationPreview.js +111 -0
  15. package/dist/composables/useSave.js +14 -14
  16. package/dist/composables/validators/useUnsubscribeBlockValidator.js +26 -17
  17. package/dist/config/compiler/htmlCompilerRules.js +38 -10
  18. package/dist/config/compiler/recommendationCompilerRules.js +84 -109
  19. package/dist/config/compiler/utils/recommendationCompilerUtils.js +98 -89
  20. package/dist/config/compiler/utils/recommendationIgnoreUtils.js +15 -0
  21. package/dist/config/migrator/recommendation/extractors.js +44 -22
  22. package/dist/config/migrator/recommendation/htmlBuilder.js +170 -163
  23. package/dist/config/migrator/recommendation/settingsMapper.js +38 -33
  24. package/dist/config/migrator/recommendationMigrator.js +30 -31
  25. package/dist/extensions/Blocks/Recommendation/block.js +60 -41
  26. package/dist/extensions/Blocks/Recommendation/canvasPreview.css.js +16 -0
  27. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +41 -32
  28. package/dist/extensions/Blocks/Recommendation/constants/selectors.js +26 -20
  29. package/dist/extensions/Blocks/Recommendation/controls/cardComposition/index.js +359 -264
  30. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +96 -83
  31. package/dist/extensions/Blocks/Recommendation/controls/main/layoutOrientation.js +43 -31
  32. package/dist/extensions/Blocks/Recommendation/controls/main/pricePlacement.js +133 -0
  33. package/dist/extensions/Blocks/Recommendation/controls/main/productCount.js +3 -2
  34. package/dist/extensions/Blocks/Recommendation/controls/main/productLayout.js +55 -45
  35. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +175 -166
  36. package/dist/extensions/Blocks/Recommendation/controls/syncInfoMessage.js +7 -6
  37. package/dist/extensions/Blocks/Recommendation/extension.js +31 -29
  38. package/dist/extensions/Blocks/Recommendation/iconsRegistry.js +21 -7
  39. package/dist/extensions/Blocks/Recommendation/recommendation.css.js +64 -4
  40. package/dist/extensions/Blocks/Recommendation/settingsPanel.js +3 -2
  41. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +6 -3
  42. package/dist/extensions/Blocks/Recommendation/templates/grid/elementRenderer.js +130 -96
  43. package/dist/extensions/Blocks/Recommendation/templates/grid/template.js +43 -53
  44. package/dist/extensions/Blocks/Recommendation/templates/index.js +20 -16
  45. package/dist/extensions/Blocks/Recommendation/templates/list/elementRenderer.js +86 -80
  46. package/dist/extensions/Blocks/Recommendation/templates/list/template.js +31 -40
  47. package/dist/extensions/Blocks/Recommendation/templates/utils.js +155 -102
  48. package/dist/extensions/Blocks/Unsubscribe/block.js +40 -37
  49. package/dist/extensions/Blocks/Unsubscribe/control.js +19 -16
  50. package/dist/guido.css +1 -1
  51. package/dist/src/@types/config/schemas.d.ts +16 -0
  52. package/dist/src/composables/useConfig.d.ts +4 -0
  53. package/dist/src/composables/useHtmlValidator.d.ts +27 -0
  54. package/dist/src/composables/useHtmlValidator.test.d.ts +1 -0
  55. package/dist/src/composables/usePreviewInteractionGuard.d.ts +3 -0
  56. package/dist/src/composables/useRecommendationPreview.d.ts +10 -0
  57. package/dist/src/composables/validators/useUnsubscribeBlockValidator.d.ts +1 -0
  58. package/dist/src/config/compiler/utils/recommendationIgnoreUtils.d.ts +17 -0
  59. package/dist/src/config/compiler/utils/recommendationIgnoreUtils.test.d.ts +1 -0
  60. package/dist/src/config/migrator/recommendation/extractors.d.ts +15 -0
  61. package/dist/src/config/migrator/recommendation/htmlBuilder.d.ts +8 -0
  62. package/dist/src/config/migrator/recommendation/settingsMapper.d.ts +1 -1
  63. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +10 -0
  64. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +1 -1
  65. package/dist/src/extensions/Blocks/Recommendation/constants/selectors.d.ts +21 -0
  66. package/dist/src/extensions/Blocks/Recommendation/controls/cardComposition/index.d.ts +36 -3
  67. package/dist/src/extensions/Blocks/Recommendation/controls/index.d.ts +1 -1
  68. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +3 -1
  69. package/dist/src/extensions/Blocks/Recommendation/controls/main/layoutOrientation.d.ts +5 -0
  70. package/dist/src/extensions/Blocks/Recommendation/controls/main/pricePlacement.d.ts +59 -0
  71. package/dist/src/extensions/Blocks/Recommendation/controls/main/productLayout.d.ts +6 -0
  72. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +9 -0
  73. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +2 -0
  74. package/dist/src/extensions/Blocks/Recommendation/templates/grid/elementRenderer.d.ts +13 -1
  75. package/dist/src/extensions/Blocks/Recommendation/templates/grid/template.d.ts +4 -4
  76. package/dist/src/extensions/Blocks/Recommendation/templates/index.d.ts +1 -1
  77. package/dist/src/extensions/Blocks/Recommendation/templates/list/elementRenderer.d.ts +10 -1
  78. package/dist/src/extensions/Blocks/Recommendation/templates/list/template.d.ts +3 -3
  79. package/dist/src/extensions/Blocks/Recommendation/templates/utils.d.ts +51 -1
  80. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +15 -0
  81. package/dist/src/stores/config.d.ts +36 -0
  82. package/dist/src/stores/unsubscribe.d.ts +11 -1
  83. package/dist/src/utils/ampErrorFilter.d.ts +1 -0
  84. package/dist/stores/unsubscribe.js +8 -7
  85. package/dist/utils/ampErrorFilter.js +6 -5
  86. package/package.json +1 -1
@@ -139,6 +139,10 @@ export declare const LegacyRecommendationConfigSchema: v.LooseObjectSchema<{
139
139
  readonly size: v.OptionalSchema<v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>;
140
140
  /** Vertical responsiveness flag (legacy size=1 variants) */
141
141
  readonly verticalResponsiveness: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
142
+ /** Legacy "Move to next line" price placement toggle (cardPricePlacement.js) */
143
+ readonly isPriceMovedToNextLine: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
144
+ /** Legacy "Hide if same as discounted" / delete-price-for-zero-sale toggle */
145
+ readonly isPriceDeletedForZeroSale: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
142
146
  }, undefined>;
143
147
  /**
144
148
  * Migration-only inputs scoped to this template.
@@ -195,6 +199,10 @@ export declare const TemplateMigrationSchema: v.ObjectSchema<{
195
199
  readonly size: v.OptionalSchema<v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>;
196
200
  /** Vertical responsiveness flag (legacy size=1 variants) */
197
201
  readonly verticalResponsiveness: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
202
+ /** Legacy "Move to next line" price placement toggle (cardPricePlacement.js) */
203
+ readonly isPriceMovedToNextLine: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
204
+ /** Legacy "Hide if same as discounted" / delete-price-for-zero-sale toggle */
205
+ readonly isPriceDeletedForZeroSale: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
198
206
  }, undefined>, undefined>, {}>;
199
207
  }, undefined>;
200
208
  /**
@@ -274,6 +282,10 @@ export declare const TemplateSchema: v.ObjectSchema<{
274
282
  readonly size: v.OptionalSchema<v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>;
275
283
  /** Vertical responsiveness flag (legacy size=1 variants) */
276
284
  readonly verticalResponsiveness: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
285
+ /** Legacy "Move to next line" price placement toggle (cardPricePlacement.js) */
286
+ readonly isPriceMovedToNextLine: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
287
+ /** Legacy "Hide if same as discounted" / delete-price-for-zero-sale toggle */
288
+ readonly isPriceDeletedForZeroSale: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
277
289
  }, undefined>, undefined>, {}>;
278
290
  }, undefined>, {}>;
279
291
  }, undefined>;
@@ -665,6 +677,10 @@ export declare const GuidoConfigSchema: v.ObjectSchema<{
665
677
  readonly size: v.OptionalSchema<v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>;
666
678
  /** Vertical responsiveness flag (legacy size=1 variants) */
667
679
  readonly verticalResponsiveness: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
680
+ /** Legacy "Move to next line" price placement toggle (cardPricePlacement.js) */
681
+ readonly isPriceMovedToNextLine: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
682
+ /** Legacy "Hide if same as discounted" / delete-price-for-zero-sale toggle */
683
+ readonly isPriceDeletedForZeroSale: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
668
684
  }, undefined>, undefined>, {}>;
669
685
  }, undefined>, {}>;
670
686
  }, undefined>, {}>;
@@ -60,6 +60,8 @@ export declare const useConfig: () => {
60
60
  blockType?: string | undefined;
61
61
  size?: string | number | undefined;
62
62
  verticalResponsiveness?: boolean | undefined;
63
+ isPriceMovedToNextLine?: boolean | undefined;
64
+ isPriceDeletedForZeroSale?: boolean | undefined;
63
65
  } & {
64
66
  [key: string]: unknown;
65
67
  };
@@ -186,6 +188,8 @@ export declare const useConfig: () => {
186
188
  blockType?: string | undefined;
187
189
  size?: string | number | undefined;
188
190
  verticalResponsiveness?: boolean | undefined;
191
+ isPriceMovedToNextLine?: boolean | undefined;
192
+ isPriceDeletedForZeroSale?: boolean | undefined;
189
193
  } & {
190
194
  [key: string]: unknown;
191
195
  };
@@ -1,4 +1,31 @@
1
1
  import type { DynamicContent } from '@@/Types/generic';
2
+ import type { FiltersResponse } from '@@/Types/recommendation';
3
+ /**
4
+ * Parses a `{{recoId_index_attributeName}}` variable (already stripped of the
5
+ * braces/whitespace) into its recommendation id and attribute name. Returns
6
+ * null for anything that isn't a well-formed recommendation variable.
7
+ */
8
+ export declare function parseRecommendationVariable(field: string): {
9
+ recoId: string;
10
+ attribute: string;
11
+ } | null;
12
+ /**
13
+ * Builds the set of attribute forms valid inside partner-authored
14
+ * `{{recoId_index_attribute}}` variables: the built-in compiler attributes plus
15
+ * each account attribute in its canonical token form — bare for
16
+ * `defaultAttribute`, `product_attribute.<name>` for `productAttribute`. Using
17
+ * `resolveProductAttrValue` (the same producer the block builder uses) keeps the
18
+ * validator's notion of "valid form" identical to what is actually emitted.
19
+ */
20
+ export declare function buildPartnerAttributeNameSet(filterList: FiltersResponse): Set<string>;
21
+ /**
22
+ * Returns the `{{...}}` custom fields in the HTML that are invalid. Partner
23
+ * attributes in `ins-skip-compile` blocks (recoId ∈ ignoredIds) are checked
24
+ * against `ignoredAttributeNames`; Guido-generated reco variables and known merge
25
+ * tags pass. An unrecognized dotted token is ignored: it was never captured
26
+ * before, so flagging it would false-positive on merge tags like `{{user.name}}`.
27
+ */
28
+ export declare function findInvalidCustomFields(html: string, validCustomFields: string[], recommendationBlockIds: string[], ignoredIds: Set<string>, ignoredAttributeNames: Set<string> | null): string[];
2
29
  export declare const useHtmlValidator: () => {
3
30
  validateHtml: (html: string, customFields: DynamicContent[], isOnSaveValidation?: boolean) => Promise<boolean>;
4
31
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare const usePreviewInteractionGuard: () => {
2
+ blockPreviewInteractions: (iframe: HTMLIFrameElement) => void;
3
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Substitutes recommendation product variables in compiled preview HTML with the
3
+ * blocks' fetched sample products, then simulates the email-service hide rule by
4
+ * emptying the original-price cell (DOM kept) for slots with no real discount.
5
+ * Returns the input unchanged when there are no reco blocks or no products.
6
+ */
7
+ export declare function substituteRecommendationPreview(html: string): string;
8
+ export declare const useRecommendationPreview: () => {
9
+ substituteRecommendationPreview: typeof substituteRecommendationPreview;
10
+ };
@@ -1,3 +1,4 @@
1
1
  export declare const useUnsubscribeBlockValidator: () => {
2
2
  validateUnsubscribeBlockUniqueness: (html: string) => boolean;
3
+ validateUnsubscribeBlockHasTemplate: (html: string) => boolean;
3
4
  };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Returns true when a recommendation block has opted out of Guido's
3
+ * product-data → template-variable substitution. Partners signal opt-out by
4
+ * adding the `ins-skip-compile` class on the `.recommendation-block-v2`
5
+ * element. Only the variable substitution is skipped — campaign URL building,
6
+ * hidden-row removal, Outlook image widths, discount conditions, text-trim CSS
7
+ * and general HTML optimizations still apply. See `CSS_CLASS_SKIP_COMPILE` for
8
+ * the full scope.
9
+ */
10
+ export declare function isIgnoredRecommendationBlock(block: Element): boolean;
11
+ /**
12
+ * DOM-parses the HTML once and returns the set of `recommendation-id`
13
+ * values that belong to ignored blocks. Use this in regex/string-based
14
+ * compiler rules where the rule processes flat matches and only has the
15
+ * extracted block ID rather than a DOM Element reference.
16
+ */
17
+ export declare function getIgnoredRecommendationBlockIds(html: string): Set<string>;
@@ -5,3 +5,18 @@ export declare function extractTitleText(block: Element): string;
5
5
  * the caller treats that as "no segment background".
6
6
  */
7
7
  export declare function extractCardBgColor(block: Element): string;
8
+ /**
9
+ * Reads the legacy block's own `currency-*` attributes into a
10
+ * `currencySettings`-shaped object that `mapCurrency` understands. Used as a
11
+ * fallback when the external recommendationConfigs map has no entry for this
12
+ * block, so the migrated block keeps its real currency instead of defaulting.
13
+ * Returns undefined when the block carries no `currency` attribute.
14
+ */
15
+ export declare function extractCurrencyFromBlock(block: Element): Record<string, string> | undefined;
16
+ /**
17
+ * True when a PM has manually opted the legacy block out of content
18
+ * regeneration by adding the `ins-skip-compile` class. Detected before rebuild
19
+ * so the migrator preserves the partner's markup verbatim (keeping their
20
+ * hand-authored variables) instead of regenerating the block.
21
+ */
22
+ export declare function isPartnerManagedBlock(block: Element): boolean;
@@ -8,4 +8,12 @@ export declare function buildBlockHtml(params: {
8
8
  extraClasses?: string;
9
9
  legacyId?: string;
10
10
  legacyBgColor?: string;
11
+ /**
12
+ * When set, the block's existing inner HTML is preserved verbatim instead
13
+ * of regenerating product rows — used for partner-customized blocks so
14
+ * their hand-authored `{{...}}` variables survive migration.
15
+ */
16
+ preserveInnerHtml?: string;
17
+ /** Adds the `ins-skip-compile` opt-out class to the migrated block. */
18
+ skipCompile?: boolean;
11
19
  }): string;
@@ -3,5 +3,5 @@ import type { CurrencyConfig, RecommendationNodeConfig } from '@@/Extensions/Blo
3
3
  import type { LegacyRecommendationConfig } from '@@/Types/config';
4
4
  /** Defensive against missing/malformed legacy `currencySettings` — defaults are per-field, not whole-object. */
5
5
  export declare function mapCurrency(legacy: unknown): CurrencyConfig;
6
- export type SettingsPartial = Pick<RecommendationNodeConfig, 'recommendationId' | 'strategy' | 'productIds' | 'size' | 'shuffleProducts' | 'language' | 'currency' | 'filters' | 'layout' | 'cardsInRow' | 'mobileCardsInRow' | 'mobileLayoutEnabled' | 'previousMobileCardsInRow' | 'columnSpacing' | 'rowSpacing' | 'mobileColumnSpacing' | 'mobileRowSpacing' | 'omnibusPrice' | 'omnibusDiscount' | 'configVersion'>;
6
+ export type SettingsPartial = Pick<RecommendationNodeConfig, 'recommendationId' | 'strategy' | 'productIds' | 'size' | 'shuffleProducts' | 'language' | 'currency' | 'filters' | 'layout' | 'cardsInRow' | 'mobileCardsInRow' | 'mobileLayoutEnabled' | 'previousMobileCardsInRow' | 'columnSpacing' | 'rowSpacing' | 'mobileColumnSpacing' | 'mobileRowSpacing' | 'omnibusPrice' | 'omnibusDiscount' | 'priceMovedToNextLine' | 'priceHideIfSameAsDiscounted' | 'configVersion'>;
7
7
  export declare function mapSettings(recCfg: LegacyRecommendationConfig, parsed: LegacyProductConfig, id: number): SettingsPartial;
@@ -117,6 +117,16 @@ export declare class RecommendationBlock extends Block {
117
117
  * added classes via setAttribute.
118
118
  */
119
119
  private _assignRecommendationId;
120
+ /**
121
+ * Stamps the `hide-price` attribute (read by email-service at send time) on
122
+ * the block element from the config flag.
123
+ */
124
+ private _stampHidePrice;
125
+ /**
126
+ * Stamps `hide-price` from config only when the block lacks the attribute —
127
+ * heals templates saved before this feature without dirtying current ones.
128
+ */
129
+ private _healHidePriceAttr;
120
130
  /**
121
131
  * Gets the recommendation-id from a block node
122
132
  */
@@ -8,6 +8,6 @@
8
8
  */
9
9
  export { RecommendationBlockId } from './blockIds';
10
10
  export { RecommendationControlId } from './controlIds';
11
- export { BLOCK_ROOT_SELECTOR, CONTAINER_SELECTOR, DESKTOP_CONTAINER_SELECTOR, MOBILE_CONTAINER_SELECTOR, MOBILE_ROW_SELECTOR, CSS_CLASS_RECO_BUTTON, CURRENCY_ATTR, ATTR_PRODUCT_IMAGE, ATTR_PRODUCT_NAME, ATTR_PRODUCT_PRICE, ATTR_PRODUCT_OLD_PRICE, ATTR_PRODUCT_OMNIBUS_PRICE, ATTR_PRODUCT_OMNIBUS_DISCOUNT, ATTR_PRODUCT_BUTTON, ATTR_CUSTOM_PREFIX, ATTR_DATA_CUSTOM_ATTRIBUTES, ATTR_PRODUCT_ATTR, PRODUCT_ATTRIBUTE_PREFIX, BUILT_IN_DEFAULT_ATTRIBUTES, } from './selectors';
11
+ export { BLOCK_ROOT_SELECTOR, CONTAINER_SELECTOR, DESKTOP_CONTAINER_SELECTOR, MOBILE_CONTAINER_SELECTOR, MOBILE_ROW_SELECTOR, CSS_CLASS_RECO_BUTTON, CSS_CLASS_SKIP_COMPILE, RECOMMENDATION_VARIABLE_BUILTIN_ATTRIBUTES, CURRENCY_ATTR, ATTR_PRODUCT_IMAGE, ATTR_PRODUCT_NAME, ATTR_PRODUCT_PRICE, ATTR_PRODUCT_OLD_PRICE, ATTR_PRODUCT_OMNIBUS_PRICE, ATTR_PRODUCT_OMNIBUS_DISCOUNT, ATTR_PRODUCT_BUTTON, ATTR_CUSTOM_PREFIX, ATTR_DATA_CUSTOM_ATTRIBUTES, ATTR_PRODUCT_ATTR, PRODUCT_ATTRIBUTE_PREFIX, BUILT_IN_DEFAULT_ATTRIBUTES, } from './selectors';
12
12
  export { LAYOUT_VALUES, LAYOUT_OPTIONS, DEFAULT_PRODUCTS_PER_ROW, DEFAULT_CARDS_IN_ROW, DEFAULT_MOBILE_CARDS_IN_ROW, MAX_PRODUCT_COUNT, MIN_PRODUCT_COUNT, MAX_PRODUCTS_PER_ROW, MIN_PRODUCTS_PER_ROW, MAX_MOBILE_PRODUCTS_PER_ROW, MIN_MOBILE_PRODUCTS_PER_ROW, DEFAULT_COLUMN_SPACING, DEFAULT_ROW_SPACING, DEFAULT_MOBILE_COLUMN_SPACING, DEFAULT_MOBILE_ROW_SPACING, MIN_SPACING, MAX_SPACING, SPACING_STEP, } from './layout';
13
13
  export { DEFAULT_NODE_CONFIG, DEFAULT_CURRENCY, DEFAULT_COMPOSITION, DEFAULT_VISIBILITY, CURRENT_CONFIG_VERSION, EXCLUDED_ALGORITHM_IDS, } from './defaultConfig';
@@ -23,6 +23,18 @@ export declare const MOBILE_ROW_SELECTOR = ".ins-recommendation-mobile-row";
23
23
  * `{{cId_pIdx_url}}` template variable.
24
24
  */
25
25
  export declare const CSS_CLASS_RECO_BUTTON = "reco-v3-button";
26
+ /**
27
+ * Opt-out class for partners who want to inject their own template variables
28
+ * into a recommendation block. When present on `.recommendation-block-v2`,
29
+ * Guido skips ONLY its product-data → template-variable substitution
30
+ * (`replaceProductAttributes`) for that block — partners author their own
31
+ * `{{recoId_index_attribute}}` variables. Everything else (campaign URL
32
+ * building, hidden-row removal, Outlook image widths, text-trim CSS, discount
33
+ * conditions, store hydration, store-level config validation, and general HTML
34
+ * optimizations) still applies. The class is preserved in the final compiled
35
+ * output so partners may use it for their own styling.
36
+ */
37
+ export declare const CSS_CLASS_SKIP_COMPILE = "ins-skip-compile";
26
38
  /**
27
39
  * Currency HTML attributes set on the block element
28
40
  * These are read by the HTML compiler at runtime to format prices
@@ -63,3 +75,12 @@ export declare const PRODUCT_ATTRIBUTE_PREFIX = "product_attribute.";
63
75
  * Used to exclude these from the custom attribute dropdown to prevent duplication with toggles.
64
76
  */
65
77
  export declare const BUILT_IN_DEFAULT_ATTRIBUTES: Set<string>;
78
+ /**
79
+ * Attribute names valid inside a `{{recoId_index_attributeName}}` template
80
+ * variable regardless of the account's custom attribute list. Every built-in
81
+ * composition attribute is valid, plus `currency` and `discount` which the
82
+ * compiler emits but are not composition toggles. Custom attributes are
83
+ * validated separately against the account's fetched attribute list
84
+ * (`stripo/email-recommendation-attributes`).
85
+ */
86
+ export declare const RECOMMENDATION_VARIABLE_BUILTIN_ATTRIBUTES: Set<string>;
@@ -17,6 +17,13 @@ export declare class RecommendationCardCompositionControl extends CommonControl
17
17
  getId(): string;
18
18
  getTemplate(): string;
19
19
  onRender(): void;
20
+ /**
21
+ * Card composition restructures the block (toggles/reorders/adds attribute
22
+ * cells), which would corrupt a partner-managed (`ins-skip-compile`) block's
23
+ * preserved markup. There is no native tab-disable API, so the whole control
24
+ * container is rendered inert (greyed + non-interactive) instead.
25
+ */
26
+ _syncDisabledState(): void;
20
27
  onTemplateNodeUpdated(node: ImmutableHtmlNode): void;
21
28
  onDestroy(): void;
22
29
  private _initializeComposition;
@@ -43,6 +50,15 @@ export declare class RecommendationCardCompositionControl extends CommonControl
43
50
  */
44
51
  private _tryReorderInPlace;
45
52
  private _createBuiltInItemHtml;
53
+ /**
54
+ * Inline mode: the merged "Product Prices" group item. It moves as a unit in
55
+ * the main list (both keys stay adjacent) and contains a nested 2-item drag
56
+ * to reorder Product Price ↔ Product Original Price — which flips the inline
57
+ * render order via `resolveInlinePriceOrder`. The group keeps a single
58
+ * visibility toggle (`data-action-for="productPrice"`). Nested sub-items use a
59
+ * plain drag handle (no UE button → untouched by the reorder-icon rescue).
60
+ */
61
+ private _createPriceGroupItemHtml;
46
62
  private _createCustomItemHtml;
47
63
  /**
48
64
  * Builds select options from the store's filterList.
@@ -98,7 +114,23 @@ export declare class RecommendationCardCompositionControl extends CommonControl
98
114
  */
99
115
  private _rescueReorderIconsToStore;
100
116
  private _setupEventListeners;
117
+ /**
118
+ * Builds the composition key order from the orderable list, expanding the
119
+ * inline "Product Prices" group into its two sub-keys (in nested DOM order)
120
+ * so both price keys stay adjacent and in the user-chosen order.
121
+ */
122
+ private _extractCompositionOrder;
101
123
  private _setupDragAndDrop;
124
+ /**
125
+ * Wires drag/drop reordering for one item level on the shared list. Both the
126
+ * top-level items and the nested price sub-items use this; they never fight
127
+ * because each only sets its `dragged` ref when its own `itemSelector` matches
128
+ * at dragstart (and the top-level one additionally ignores drags originating
129
+ * inside `ignoreSelector`). Every drop re-reads the full composition via
130
+ * `_extractCompositionOrder`, so both levels commit through `_onReorder`
131
+ * identically.
132
+ */
133
+ private _registerDragHandlers;
102
134
  private _setupDeleteHandler;
103
135
  private _onAddAttribute;
104
136
  /**
@@ -155,9 +187,10 @@ export declare class RecommendationCardCompositionControl extends CommonControl
155
187
  private _extractSegmentBgFromCard;
156
188
  private _getControlContainer;
157
189
  /**
158
- * Adds/removes orderable-disabled class based on layout orientation.
159
- * List layout hides drag handles via CSS and disables draggable attribute
160
- * to prevent native browser drag-and-drop from working without the handle.
190
+ * Ensures composition reorder is enabled. Both grid and list layouts now
191
+ * support reordering top-level items and the nested price sub-items are all
192
+ * draggable. (Items render with `draggable="true"`; this just clears any stale
193
+ * disabled state.)
161
194
  */
162
195
  private _updateOrderableState;
163
196
  /**
@@ -9,7 +9,7 @@
9
9
  * // Or individual imports
10
10
  * import { NameAlignControl, ButtonColorControl } from './controls';
11
11
  */
12
- export { RecommendationBlockControl, CONTROL_BLOCK_ID, AlgorithmControl, ALGORITHM_CONTROL_ID, LocaleControl, LOCALE_CONTROL_ID, CurrencyControl, CURRENCY_CONTROL_ID, ProductCountControl, PRODUCT_COUNT_CONTROL_ID, ProductLayoutControl, PRODUCT_LAYOUT_CONTROL_ID, FiltersControl, FILTERS_CONTROL_ID, ShuffleControl, SHUFFLE_CONTROL_ID, LayoutOrientationControl, LAYOUT_ORIENTATION_CONTROL_ID, } from './main';
12
+ export { RecommendationBlockControl, CONTROL_BLOCK_ID, AlgorithmControl, ALGORITHM_CONTROL_ID, LocaleControl, LOCALE_CONTROL_ID, CurrencyControl, CURRENCY_CONTROL_ID, ProductCountControl, PRODUCT_COUNT_CONTROL_ID, ProductLayoutControl, PRODUCT_LAYOUT_CONTROL_ID, FiltersControl, FILTERS_CONTROL_ID, ShuffleControl, SHUFFLE_CONTROL_ID, LayoutOrientationControl, LAYOUT_ORIENTATION_CONTROL_ID, PricePlacementControl, PRICE_PLACEMENT_CONTROL_ID, } from './main';
13
13
  export * from './main/utils';
14
14
  export { NameControls } from './name';
15
15
  export { PriceControls } from './price';
@@ -5,11 +5,12 @@ import { CurrencyControl, CURRENCY_CONTROL_ID } from './currency';
5
5
  import { FiltersControl, FILTERS_CONTROL_ID } from './filters';
6
6
  import { LayoutOrientationControl, LAYOUT_ORIENTATION_CONTROL_ID } from './layoutOrientation';
7
7
  import { LocaleControl, LOCALE_CONTROL_ID } from './locale';
8
+ import { PricePlacementControl, PRICE_PLACEMENT_CONTROL_ID } from './pricePlacement';
8
9
  import { ProductCountControl, PRODUCT_COUNT_CONTROL_ID } from './productCount';
9
10
  import { ProductLayoutControl, PRODUCT_LAYOUT_CONTROL_ID } from './productLayout';
10
11
  import { ShuffleControl, SHUFFLE_CONTROL_ID } from './shuffle';
11
12
  export declare const CONTROL_BLOCK_ID = "ui-elements-recommendation-block";
12
- export { AlgorithmControl, ALGORITHM_CONTROL_ID, LocaleControl, LOCALE_CONTROL_ID, CurrencyControl, CURRENCY_CONTROL_ID, ProductCountControl, PRODUCT_COUNT_CONTROL_ID, ProductLayoutControl, PRODUCT_LAYOUT_CONTROL_ID, FiltersControl, FILTERS_CONTROL_ID, ShuffleControl, SHUFFLE_CONTROL_ID, LayoutOrientationControl, LAYOUT_ORIENTATION_CONTROL_ID, };
13
+ export { AlgorithmControl, ALGORITHM_CONTROL_ID, LocaleControl, LOCALE_CONTROL_ID, CurrencyControl, CURRENCY_CONTROL_ID, ProductCountControl, PRODUCT_COUNT_CONTROL_ID, ProductLayoutControl, PRODUCT_LAYOUT_CONTROL_ID, FiltersControl, FILTERS_CONTROL_ID, ShuffleControl, SHUFFLE_CONTROL_ID, LayoutOrientationControl, LAYOUT_ORIENTATION_CONTROL_ID, PricePlacementControl, PRICE_PLACEMENT_CONTROL_ID, };
13
14
  export * from './utils';
14
15
  /**
15
16
  * Main recommendation block control that composes all sub-controls
@@ -26,6 +27,7 @@ export declare class RecommendationBlockControl extends CommonControl {
26
27
  private productLayoutControl;
27
28
  private filtersControl;
28
29
  private shuffleControl;
30
+ private pricePlacementControl;
29
31
  private layoutOrientationControl;
30
32
  getId(): string;
31
33
  getTemplate(): string;
@@ -21,6 +21,11 @@ export declare class LayoutOrientationControl extends CommonControl {
21
21
  getTemplate(): string;
22
22
  onRender(): void;
23
23
  onTemplateNodeUpdated(node: ImmutableHtmlNode): void;
24
+ /**
25
+ * Layout orientation restructures the whole block, which is meaningless for
26
+ * partner-managed (`ins-skip-compile`) blocks — disable the control there.
27
+ */
28
+ _syncDisabledState(): void;
24
29
  _setFormValues(): void;
25
30
  /**
26
31
  * Handles layout change
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Price Placement Control
3
+ *
4
+ * Two block-level price settings ported from the legacy product block, shown
5
+ * above the Currency control in the main Settings tab:
6
+ *
7
+ * - "Product Price Orientation" (`priceMovedToNextLine`): a vertical/horizontal
8
+ * icon radio (mirrors the Items block control). `vertical` (default) keeps the
9
+ * price and original price on separate stacked rows (current Guido look);
10
+ * `horizontal` renders them inline on the same line and Card Composition
11
+ * collapses them into a single "Product Prices" entry.
12
+ * - "Hide if same as discounted" (`priceHideIfSameAsDiscounted`): a toggle.
13
+ * When ON, email-service blanks the redundant original price at send time when
14
+ * it equals the sale price (see the `hide-price` block attribute below).
15
+ *
16
+ * Orientation re-renders the rows (it changes the saved template layout).
17
+ * Hide-if-same NEVER touches the template — it only persists config + stamps the
18
+ * `hide-price` attribute; the visual hide is simulated in the preview and applied
19
+ * for real server-side by email-service.
20
+ */
21
+ import type { RecommendationNodeConfig } from '../../types/nodeConfig';
22
+ import type { ImmutableHtmlNode } from '@stripoinc/ui-editor-extensions';
23
+ import { CommonControl } from '../../../common-control';
24
+ export declare const PRICE_PLACEMENT_CONTROL_ID = "recommendation-price-placement-control";
25
+ type Orientation = 'vertical' | 'horizontal';
26
+ /**
27
+ * Control for the block-level price orientation radio + hide-if-same toggle.
28
+ */
29
+ export declare class PricePlacementControl extends CommonControl {
30
+ private store;
31
+ getId(): string;
32
+ getTemplate(): string;
33
+ onRender(): void;
34
+ onTemplateNodeUpdated(node: ImmutableHtmlNode): void;
35
+ _setFormValues(config: RecommendationNodeConfig): void;
36
+ _initializeHideToggle(config: RecommendationNodeConfig): void;
37
+ /**
38
+ * Disables the orientation radio when a price element is hidden via Card
39
+ * Composition — orientation is meaningless without both prices visible.
40
+ * Visibility lives on the attribute rows' `data-visibility` (set by the Card
41
+ * Composition control). Inline mode has no standalone original-price row, so
42
+ * the merged "Product Prices" entry (the price row) governs.
43
+ */
44
+ _updateOrientationDisabled(): void;
45
+ _onOrientationChange(orientation: Orientation): void;
46
+ _onHideIfSameChange(value: boolean): void;
47
+ /**
48
+ * Mirrors the hide-if-same flag onto the durable `hide-price` block attribute
49
+ * read by email-service at send time.
50
+ */
51
+ _stampHideIfSameAttr(value: boolean): void;
52
+ /**
53
+ * Rebuilds the product rows so the renderer re-reads the inline/stacked
54
+ * layout flag from the store. Used by the orientation change only.
55
+ */
56
+ _regenerate(): void;
57
+ _listenToFormUpdates(): void;
58
+ }
59
+ export {};
@@ -30,6 +30,12 @@ export declare class ProductLayoutControl extends CommonControl {
30
30
  getTemplate(): string;
31
31
  onRender(): void;
32
32
  onTemplateNodeUpdated(node: ImmutableHtmlNode): void;
33
+ /**
34
+ * Products-per-row and mobile-layout restructure the block, which is
35
+ * meaningless for partner-managed (`ins-skip-compile`) blocks — disable
36
+ * these inputs there. Disabling coexists with the visibility logic above.
37
+ */
38
+ _syncDisabledState(): void;
33
39
  onDestroy(): void;
34
40
  _setFormValues(): void;
35
41
  /**
@@ -35,6 +35,15 @@ interface DocumentModifier {
35
35
  * @returns The block element or null if not found
36
36
  */
37
37
  export declare function getBlockElement(currentNode: ImmutableHtmlNode | null | undefined): ImmutableHtmlNode | null;
38
+ /**
39
+ * True when the recommendation block has opted out of Guido's content
40
+ * regeneration via the `ins-skip-compile` class. Partner-managed blocks carry
41
+ * hand-authored `{{...}}` variables in their cells, so Guido must never
42
+ * overwrite their content with product data (regenerate / in-place update).
43
+ * @param currentNode - The current template node
44
+ * @returns True if the block opted out of content regeneration
45
+ */
46
+ export declare function isPartnerManagedBlock(currentNode: ImmutableHtmlNode | null | undefined): boolean;
38
47
  /**
39
48
  * Gets the current layout orientation from the block's data attribute
40
49
  * Supports both old (horizontal/vertical) and new (list/grid) values for backward compatibility
@@ -25,6 +25,8 @@ export interface PerBlockConfigs {
25
25
  unresponsive: boolean;
26
26
  size: string;
27
27
  customAttributes: string[];
28
+ priceMovedToNextLine: boolean;
29
+ priceHideIfSameAsDiscounted: boolean;
28
30
  }
29
31
  interface PerBlockState {
30
32
  recommendationConfigs: PerBlockConfigs;
@@ -1,4 +1,5 @@
1
- import { type ElementRenderer } from '../utils';
1
+ import type { RecommendationProduct } from '@@/Types/recommendation';
2
+ import { type ElementRenderer, type CellRenderOptions } from '../utils';
2
3
  /**
3
4
  * Grid element renderer
4
5
  * Each element returns a `<td>` containing a nested table structure:
@@ -24,4 +25,15 @@ export declare const ATTRIBUTE_CELL_CLASS = "attribute-cell";
24
25
  * card-composition control so both render identical filler markup.
25
26
  */
26
27
  export declare function buildFillerCell(columnWidth: string, padding?: string): string;
28
+ /**
29
+ * Renders the inline price cell used when "Move to next line" is OFF: the sale
30
+ * price and original price sit side by side in a single attribute cell, each
31
+ * keeping its own `esd-extension-block-id` + `product-*` class so the per-element
32
+ * style controls and the compiler keep working.
33
+ *
34
+ * `originalFirst` preserves the card-composition order (original before sale when
35
+ * `productOldPrice` precedes `productPrice` in the composition) so the side-by-side
36
+ * order matches the stacked order.
37
+ */
38
+ export declare function renderInlineGridPriceCell(product: RecommendationProduct, originalFirst?: boolean, priceOptions?: CellRenderOptions, oldPriceOptions?: CellRenderOptions, cardBackgroundColor?: string): string;
27
39
  export declare const gridElementRenderer: ElementRenderer;
@@ -1,5 +1,5 @@
1
1
  import type { FiltersResponse, RecommendationProduct } from '@@/Types/recommendation';
2
- import { type ElementRenderer, type RenderOptions } from '../utils';
2
+ import { type ElementRenderer, type PricePlacementFlags, type RenderOptions } from '../utils';
3
3
  /**
4
4
  * Generates attribute-aligned product rows for grid layout.
5
5
  * Creates rows where each row contains one attribute type from all products.
@@ -9,7 +9,7 @@ import { type ElementRenderer, type RenderOptions } from '../utils';
9
9
  * @param composition - Array defining order of card elements
10
10
  * @returns HTML string for attribute-aligned rows
11
11
  */
12
- export declare function prepareGridAttributeRows(products: RecommendationProduct[], productsPerRow: number, elementRenderer: ElementRenderer, composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions): string;
12
+ export declare function prepareGridAttributeRows(products: RecommendationProduct[], productsPerRow: number, elementRenderer: ElementRenderer, composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions & PricePlacementFlags): string;
13
13
  /**
14
14
  * Prepares grid product rows with attribute-aligned structure
15
15
  * Groups products into rows, then generates attribute-aligned HTML for each group
@@ -19,7 +19,7 @@ export declare function prepareGridAttributeRows(products: RecommendationProduct
19
19
  * @param composition - Array defining order of card elements
20
20
  * @returns HTML string for all product rows
21
21
  */
22
- export declare function prepareGridProductRows(products: RecommendationProduct[], productsPerRow: number, elementRenderer: ElementRenderer, composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions): string;
22
+ export declare function prepareGridProductRows(products: RecommendationProduct[], productsPerRow: number, elementRenderer: ElementRenderer, composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions & PricePlacementFlags): string;
23
23
  /**
24
24
  * Prepares grid product rows with attribute-aligned structure.
25
25
  * Uses row-based rendering where each attribute type forms a single row across all products.
@@ -28,6 +28,6 @@ export declare function prepareGridProductRows(products: RecommendationProduct[]
28
28
  * @param composition - Array defining element order
29
29
  * @returns HTML string for product rows
30
30
  */
31
- export declare function prepareProductRows(products: RecommendationProduct[], productsPerRow: number, composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions): string;
31
+ export declare function prepareProductRows(products: RecommendationProduct[], productsPerRow: number, composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions & PricePlacementFlags): string;
32
32
  export declare function getDefaultTemplate(recommendationId?: number): string;
33
33
  export declare function generateBlockTemplate(products: RecommendationProduct[], productsPerRow: number, title?: string, composition?: string[], mobileProductsPerRow?: number): string;
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { RecommendationProduct } from '@@/Types/recommendation';
8
8
  import { type Orientation, type PrepareProductRowsOptions } from './utils';
9
- export { DEFAULTS, DEFAULT_CARD_COMPOSITION, DEFAULT_CARD_VISIBILITY, getDefaultProducts, spacer, sanitizeImageUrl, createBlockTemplate, toDisplayableAttributeValue, type Orientation, type PrepareProductRowsOptions, type ElementRenderer, type ProductCardGetter, type AttributeStyleTemplate, type AttributeStyleTemplates, type CellBackgroundColors, type CellAlignments, type CellClasses, type OmnibusTexts, } from './utils';
9
+ export { DEFAULTS, DEFAULT_CARD_COMPOSITION, DEFAULT_CARD_VISIBILITY, getDefaultProducts, isSamePrice, spacer, sanitizeImageUrl, createBlockTemplate, toDisplayableAttributeValue, type Orientation, type PrepareProductRowsOptions, type ElementRenderer, type ProductCardGetter, type AttributeStyleTemplate, type AttributeStyleTemplates, type CellBackgroundColors, type CellAlignments, type CellClasses, type OmnibusTexts, } from './utils';
10
10
  /**
11
11
  * Unified function to prepare product rows for any layout.
12
12
  * Delegates to the appropriate layout-specific implementation.
@@ -1,8 +1,17 @@
1
- import { type ElementRenderer } from '../utils';
1
+ import type { RecommendationProduct } from '@@/Types/recommendation';
2
+ import { type ElementRenderer, type CellRenderOptions } from '../utils';
2
3
  /**
3
4
  * List element renderer
4
5
  * - Image and Button return `<td>` cells for the 3-column layout
5
6
  * - Name and Prices return `<tr><td>` rows for the info cell table
6
7
  * All elements have esd-block-* classes for Stripo selection
7
8
  */
9
+ /**
10
+ * Inline price info row used when "Move to next line" is OFF: the sale price and
11
+ * original price sit in two cells of one row. Each keeps its block id + class so
12
+ * style controls and the compiler keep working. `originalFirst` preserves the
13
+ * card-composition order (original before sale by default) so it matches the
14
+ * stacked order.
15
+ */
16
+ export declare function renderInlineListPriceRow(product: RecommendationProduct, originalFirst?: boolean, priceOptions?: CellRenderOptions, oldPriceOptions?: CellRenderOptions): string;
8
17
  export declare const listElementRenderer: ElementRenderer;
@@ -1,5 +1,5 @@
1
1
  import type { FiltersResponse, RecommendationProduct } from '@@/Types/recommendation';
2
- import { type RenderOptions } from '../utils';
2
+ import { type PricePlacementFlags, type RenderOptions } from '../utils';
3
3
  /**
4
4
  * Generates a list product card with 3-column layout
5
5
  * Uses buildElementRenderer to render Image, Info content, and Button
@@ -12,7 +12,7 @@ import { type RenderOptions } from '../utils';
12
12
  * @param composition - Array defining order of card elements
13
13
  * @returns HTML string for a single product card row
14
14
  */
15
- export declare function getListProductCard(product: RecommendationProduct, composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions): string;
15
+ export declare function getListProductCard(product: RecommendationProduct, composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions & PricePlacementFlags): string;
16
16
  /**
17
17
  * Prepares list product rows
18
18
  * Each product is a full-width row with 3-column layout
@@ -20,6 +20,6 @@ export declare function getListProductCard(product: RecommendationProduct, compo
20
20
  * @param composition - Array defining order of card elements
21
21
  * @returns HTML string for product rows
22
22
  */
23
- export declare function prepareProductRows(products: RecommendationProduct[], composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions): string;
23
+ export declare function prepareProductRows(products: RecommendationProduct[], composition?: string[], filterList?: FiltersResponse, renderOptions?: RenderOptions & PricePlacementFlags): string;
24
24
  export declare function getDefaultTemplate(): string;
25
25
  export declare function generateBlockTemplate(products: RecommendationProduct[], title?: string, composition?: string[]): string;