@designcrowd/library.brand-page 6.2.11 → 6.2.12

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 (576) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DDcraD5S.js → CreatedOnBrandCrowd-de-de-BtVKllNQ.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DtOeaisf.js → CreatedOnBrandCrowd-en-us-CX5V0v8u.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BZpmJPew.js → CreatedOnBrandCrowd-es-es-CbA02PCL.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CqRwC4e7.js → CreatedOnBrandCrowd-fr-ca-BQAKruBF.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CqRwC4e7.js → CreatedOnBrandCrowd-fr-fr-BQAKruBF.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-DId9Dfjh.js → CreatedOnBrandCrowd-pt-br-Yk_yzB3s.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-DId9Dfjh.js → CreatedOnBrandCrowd-pt-pt-Yk_yzB3s.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-BTDfmOcm.js → CreatedOnDcom-de-de-DJZQbPSD.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-CeAu9Ayx.js → CreatedOnDcom-en-us-BJvNAa4l.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-WCv0K41H.js → CreatedOnDcom-es-es-CUwB1FRY.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-BlfevQDY.js → CreatedOnDcom-fr-ca-CK54U4oh.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-Cvt7MHPZ.js → CreatedOnDcom-fr-fr-DuUNBGrM.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-DipZuylv.js → CreatedOnDcom-pt-br-BfODpFNV.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-BCw4lcKN.js → CreatedOnDcom-pt-pt-Cf6u2GCg.js} +1 -1
  15. package/lib/src/brand-page/__fixtures__/production-text-style-resolver.fixture.d.ts +16 -0
  16. package/lib/src/brand-page/__fixtures__/production-text-style-resolver.fixture.js +35 -0
  17. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +3 -0
  18. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +19 -13
  19. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.d.ts +3 -1
  20. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +17 -2
  21. package/lib/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.mixin.d.ts +11 -3
  22. package/lib/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.mixin.js +35 -1
  23. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +8 -2
  24. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +27 -3
  25. package/lib/src/brand-page/components/page/Page.mixin.d.ts +5 -2
  26. package/lib/src/brand-page/components/page/Page.mixin.js +33 -27
  27. package/lib/src/brand-page/components/page-footer/PageFooter.mixin.d.ts +7 -2
  28. package/lib/src/brand-page/components/page-footer/PageFooter.mixin.js +21 -1
  29. package/lib/src/brand-page/components/payment/Payment.mixin.d.ts +5 -2
  30. package/lib/src/brand-page/components/payment/Payment.mixin.js +22 -2
  31. package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.d.ts +6 -2
  32. package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.js +32 -1
  33. package/lib/src/brand-page/components/social-links/SocialLinks.stories.js +0 -3
  34. package/lib/src/brand-page/components/testimonial-section/__fixtures__/data.js +0 -3
  35. package/lib/src/brand-page/components/text/Text.stories.js +0 -17
  36. package/lib/src/brand-page/constants/font.constants.d.ts +62 -0
  37. package/lib/src/brand-page/constants/font.constants.js +35 -0
  38. package/lib/src/brand-page/models/index.d.ts +23 -24
  39. package/lib/src/brand-page/models/index.js +20 -7
  40. package/lib/src/brand-page/models/navigation-styles.model.d.ts +1 -1
  41. package/lib/src/brand-page/models/navigation-styles.model.js +17 -8
  42. package/lib/src/brand-page/models/payment.model.d.ts +11 -6
  43. package/lib/src/brand-page/models/styles.model.d.ts +53 -7
  44. package/lib/src/brand-page/models/styles.model.js +115 -27
  45. package/lib/src/brand-page/utils/color-palette.util.d.ts +43 -23
  46. package/lib/src/brand-page/utils/color-palette.util.js +65 -27
  47. package/lib/src/brand-page/utils/color.util.d.ts +13 -9
  48. package/lib/src/brand-page/utils/color.util.js +56 -15
  49. package/lib/src/brand-page/utils/container-text-color.util.d.ts +46 -13
  50. package/lib/src/brand-page/utils/container-text-color.util.js +321 -41
  51. package/lib/src/brand-page/utils/index.d.ts +1 -0
  52. package/lib/src/brand-page/utils/index.js +1 -0
  53. package/lib/src/brand-page/utils/migration/migration-20.js +6 -2
  54. package/lib/src/brand-page/utils/migration/migration-21.js +34 -6
  55. package/lib/src/brand-page/utils/migration/migration-22.d.ts +99 -0
  56. package/lib/src/brand-page/utils/migration/migration-22.js +909 -0
  57. package/lib/src/brand-page/utils/migration.util.js +17 -0
  58. package/lib/src/brand-page/utils/model.util.d.ts +4 -0
  59. package/lib/src/brand-page/utils/model.util.js +4 -0
  60. package/lib/src/brand-page/utils/personalisation.util.js +9 -1
  61. package/lib/src/brand-page/utils/rte-role-lift.util.d.ts +72 -0
  62. package/lib/src/brand-page/utils/rte-role-lift.util.js +275 -0
  63. package/lib/src/brand-page/utils/style.util.d.ts +5 -1
  64. package/lib/src/brand-page/utils/style.util.js +34 -11
  65. package/lib/src/brand-page/utils/text-role-css.util.d.ts +19 -0
  66. package/lib/src/brand-page/utils/text-role-css.util.js +186 -0
  67. package/lib/src/brand-page/utils/text-role-reconnect.util.d.ts +90 -0
  68. package/lib/src/brand-page/utils/text-role-reconnect.util.js +434 -0
  69. package/lib/src/brand-page/utils/text-style.util.d.ts +53 -23
  70. package/lib/src/brand-page/utils/text-style.util.js +122 -22
  71. package/lib/src/brand-page/utils/view-model.util.d.ts +9 -1
  72. package/lib/src/brand-page/utils/view-model.util.js +124 -111
  73. package/lib/src/brand-page/view-models.d.ts +1 -0
  74. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +4 -1
  75. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +13 -2
  76. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +4 -4
  77. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +4 -1
  78. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.js +0 -10
  79. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/content-settings-items.fixture.js +0 -1
  80. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +23 -138
  81. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.js +0 -28
  82. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +4 -1
  83. package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.d.ts +1 -1
  84. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +5 -2
  85. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +4 -1
  86. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +4 -1
  87. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +4 -1
  88. package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -0
  89. package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.js +1 -0
  90. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +1 -0
  91. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/RoleTypographyFill.d.ts +16 -0
  92. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/RoleTypographyFill.js +61 -0
  93. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/RteClipboard.d.ts +11 -0
  94. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/RteClipboard.js +236 -0
  95. package/lib/src/brand-page-maker/composables/rich-text-editor/font.d.ts +2 -1
  96. package/lib/src/brand-page-maker/composables/rich-text-editor/font.js +25 -6
  97. package/lib/src/brand-page-maker/composables/rich-text-editor/formatting.d.ts +17 -4
  98. package/lib/src/brand-page-maker/composables/rich-text-editor/formatting.js +204 -17
  99. package/lib/src/brand-page-maker/composables/rich-text-editor/hyperlink.d.ts +1 -1
  100. package/lib/src/brand-page-maker/composables/rich-text-editor/hyperlink.js +29 -31
  101. package/lib/src/brand-page-maker/composables/rich-text-editor/keepMarksOnEmpty.d.ts +20 -0
  102. package/lib/src/brand-page-maker/composables/rich-text-editor/keepMarksOnEmpty.js +60 -0
  103. package/lib/src/brand-page-maker/composables/rich-text-editor/spacing.d.ts +2 -1
  104. package/lib/src/brand-page-maker/composables/rich-text-editor/spacing.js +22 -6
  105. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +10 -4
  106. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +319 -275
  107. package/lib/src/brand-page-maker/constants/config-settings-constant.js +1 -5
  108. package/lib/src/brand-page-maker/convertors/text-css-style-options.convertor.js +6 -2
  109. package/lib/src/brand-page-maker/convertors/text-size-value.convertor.d.ts +1 -1
  110. package/lib/src/brand-page-maker/convertors/text-size-value.convertor.js +3 -1
  111. package/lib/src/brand-page-maker/default-getters/payment-text-and-border-color.default-getter.js +7 -14
  112. package/lib/src/brand-page-maker/default-getters/text-color.default-getter.js +6 -13
  113. package/lib/src/brand-page-maker/models/content-settings.d.ts +8 -0
  114. package/lib/src/brand-page-maker/models/content-settings.js +112 -81
  115. package/lib/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store-utils.d.ts +2 -2
  116. package/lib/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store-utils.js +5 -2
  117. package/lib/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.js +4 -3
  118. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +1257 -117
  119. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +6 -1
  120. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +17 -9
  121. package/lib/src/brand-page-maker/stores/history/history-store.d.ts +59 -5
  122. package/lib/src/brand-page-maker/stores/history/history-store.js +22 -2
  123. package/lib/src/brand-page-maker/utils/apply-color-strategy.js +26 -8
  124. package/lib/src/brand-page-maker/utils/index.d.ts +2 -1
  125. package/lib/src/brand-page-maker/utils/index.js +2 -1
  126. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +117 -15
  127. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +435 -281
  128. package/lib/src/brand-page-maker/utils/rte-authoring.util.d.ts +4 -0
  129. package/lib/src/brand-page-maker/utils/rte-authoring.util.js +101 -0
  130. package/lib/src/brand-page-maker/utils/rte-clipboard.util.d.ts +126 -0
  131. package/lib/src/brand-page-maker/utils/rte-clipboard.util.js +307 -0
  132. package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +13 -3
  133. package/lib/src/{bundled-translations-zfiA5Tsw.js → bundled-translations-yGrvPE5i.js} +17 -17
  134. package/lib/src/{bundled-translations.de-DE-ChsdSyAP.js → bundled-translations.de-DE-L4Znf8Hs.js} +17 -17
  135. package/lib/src/{bundled-translations.es-ES-DflsWQK0.js → bundled-translations.es-ES-D6iticrK.js} +15 -15
  136. package/lib/src/{bundled-translations.fr-CA-fpDRuZ52.js → bundled-translations.fr-CA-BgAVE-9k.js} +25 -25
  137. package/lib/src/{bundled-translations.fr-FR-Bub73Zt-.js → bundled-translations.fr-FR-B8GbyqxQ.js} +17 -17
  138. package/lib/src/{bundled-translations.pt-BR-BlnWE9TB.js → bundled-translations.pt-BR-DjPU3Db7.js} +25 -25
  139. package/lib/src/{bundled-translations.pt-PT-DmzXlnaS.js → bundled-translations.pt-PT-BaepaDpr.js} +23 -23
  140. package/lib/src/bundles/bundled-translations.de-DE.json +21 -2
  141. package/lib/src/bundles/bundled-translations.es-ES.json +21 -2
  142. package/lib/src/bundles/bundled-translations.fr-CA.json +21 -2
  143. package/lib/src/bundles/bundled-translations.fr-FR.json +21 -2
  144. package/lib/src/bundles/bundled-translations.json +21 -2
  145. package/lib/src/bundles/bundled-translations.pt-BR.json +21 -2
  146. package/lib/src/bundles/bundled-translations.pt-PT.json +21 -2
  147. package/lib/src/clients/ai-assistant-client.d.ts +1 -0
  148. package/lib/src/composables/useTranslate.d.ts +1 -1
  149. package/lib/src/composables/useTranslate.js +1 -1
  150. package/lib/src/css/library.brand-page-brandCrowd.min.css +303 -207
  151. package/lib/src/css/library.brand-page-brandPage.min.css +303 -207
  152. package/lib/src/css/library.brand-page-designCom.min.css +303 -207
  153. package/lib/src/css/library.brand-page-preview.min.css +303 -207
  154. package/lib/src/index.d.ts +2 -1
  155. package/lib/src/index.mjs +195 -191
  156. package/lib/src/{lib-1AqtEmvQ.js → lib-kGRlD3_P.js} +36855 -33893
  157. package/lib/src/lib.js +2 -1
  158. package/lib/src/metadata-builder/builders/footer-builder.js +4 -5
  159. package/lib/src/metadata-builder/builders/global-role-propagation.d.ts +58 -0
  160. package/lib/src/metadata-builder/builders/global-role-propagation.js +246 -0
  161. package/lib/src/metadata-builder/builders/global-styles-builder.d.ts +1 -0
  162. package/lib/src/metadata-builder/builders/global-styles-builder.js +116 -45
  163. package/lib/src/metadata-builder/builders/header-builder.js +13 -4
  164. package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +3 -3
  165. package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +3 -3
  166. package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +3 -3
  167. package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +4 -4
  168. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +3 -3
  169. package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +2 -2
  170. package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +2 -2
  171. package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +4 -4
  172. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +3 -3
  173. package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +8 -2
  174. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +2 -2
  175. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +60 -29
  176. package/lib/src/metadata-builder/builders/section-field-maps.js +1 -0
  177. package/lib/src/metadata-builder/builders/section-field-utils.d.ts +48 -19
  178. package/lib/src/metadata-builder/builders/section-field-utils.js +128 -52
  179. package/lib/src/metadata-builder/builders/section-rules/global-styles.rules.d.ts +4 -2
  180. package/lib/src/metadata-builder/builders/section-rules/global-styles.rules.js +28 -12
  181. package/lib/src/metadata-builder/builders/section-rules/social-links.rules.js +5 -1
  182. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +1 -1
  183. package/lib/src/metadata-builder/helpers/rte.helpers.js +97 -40
  184. package/lib/src/metadata-builder/metadata-executor.js +23 -1
  185. package/lib/src/metadata-builder/models.d.ts +2 -0
  186. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +18 -19
  187. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +18 -1
  188. package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +1 -1
  189. package/lib/src/metadata-builder/sanitizers/sanitizer-rules.d.ts +0 -1
  190. package/lib/src/metadata-builder/sanitizers/sanitizer-rules.js +5 -8
  191. package/lib/src/metadata-builder/sanitizers/section-args-validator.d.ts +7 -16
  192. package/lib/src/metadata-builder/sanitizers/section-args-validator.js +95 -22
  193. package/lib/src/shared/color-palette/section-color-matches.js +9 -25
  194. package/lib/src/shared/color-strategies/color-strategies.d.ts +15 -0
  195. package/lib/src/shared/color-strategies/color-strategies.js +31 -0
  196. package/lib/src/shared/color-strategies/rte-color.d.ts +8 -0
  197. package/lib/src/shared/color-strategies/rte-color.js +16 -0
  198. package/lib/src/shared/color-strategies/rte-field-pairs.d.ts +23 -0
  199. package/lib/src/shared/color-strategies/rte-field-pairs.js +28 -0
  200. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +9 -135
  201. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +1 -27
  202. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +12 -2
  203. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +7 -0
  204. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +75 -6
  205. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +18 -7
  206. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +6 -1
  207. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.js +0 -7
  208. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +1 -0
  209. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +8 -4
  210. package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.d.ts +1255 -26
  211. package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.js +203 -59
  212. package/lib/src/shared/content-settings/store/content-settings-store.d.ts +6 -0
  213. package/lib/src/shared/content-settings/store/content-settings-store.js +105 -8
  214. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +5 -1
  215. package/lib/src/shared/navigation-style-settings/store/index.d.ts +7 -3
  216. package/lib/src/shared/navigation-style-settings/store/index.js +40 -15
  217. package/lib/src/shared/page-settings/components/delete-page-modal/DeletePageModal.mixin.d.ts +1 -1
  218. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +1 -0
  219. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +17 -5
  220. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +70 -13
  221. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +10 -2
  222. package/lib/src/shared/page-settings/store/page-settings-store.d.ts +2 -1
  223. package/lib/src/shared/page-settings/store/page-settings-store.js +17 -0
  224. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +4 -1
  225. package/lib/src/shared/rte-extensions/AutoLink.d.ts +17 -0
  226. package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/AutoLink.js +67 -51
  227. package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/MaxLength.js +4 -1
  228. package/lib/src/shared/rte-extensions/TextEffects.d.ts +28 -0
  229. package/lib/src/shared/rte-extensions/TextEffects.js +96 -0
  230. package/lib/src/shared/rte-extensions/ViewDomParser.d.ts +2 -0
  231. package/lib/src/shared/rte-extensions/ViewDomParser.js +21 -0
  232. package/lib/src/shared/rte-extensions.util.d.ts +25 -0
  233. package/lib/src/{brand-page-maker/composables/rich-text-editor/getRteExtensions.js → shared/rte-extensions.util.js} +62 -7
  234. package/lib/src/shared/rte-link.util.d.ts +1 -0
  235. package/lib/src/shared/rte-link.util.js +1 -0
  236. package/lib/src/shared/rte-render.util.d.ts +112 -0
  237. package/lib/src/shared/rte-render.util.js +255 -0
  238. package/lib/src/shared/rte-shape.util.d.ts +6 -0
  239. package/lib/src/shared/rte-shape.util.js +18 -0
  240. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +2 -1
  241. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +1 -0
  242. package/lib/src/shared/side-bar/store/side-bar-store.d.ts +2 -0
  243. package/lib/src/shared/side-bar/store/side-bar-store.js +9 -0
  244. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +155 -15
  245. package/lib/src/shared/style-settings/components/StyleSettings.mixin.js +7 -1
  246. package/lib/src/shared/style-settings/store/style-settings-store.d.ts +1 -9
  247. package/lib/src/shared/style-settings/store/style-settings-store.js +3 -33
  248. package/lib/src/shared/style-settings/text-style-size-order.d.ts +2 -0
  249. package/lib/src/shared/style-settings/text-style-size-order.js +13 -0
  250. package/lib/src/shared/text-role-typography.util.d.ts +64 -0
  251. package/lib/src/shared/text-role-typography.util.js +183 -0
  252. package/lib/src/utils/minify-brand-page-metadata.d.ts +16 -1
  253. package/lib/src/utils/minify-brand-page-metadata.js +31 -9
  254. package/package.json +1 -1
  255. package/src/brand-page/__fixtures__/migration-22-v21/edited-brandsite-sections.v21.json +982 -0
  256. package/src/brand-page/__fixtures__/migration-22-v21/new-brandcontact-every-section.v21.json +1231 -0
  257. package/src/brand-page/__fixtures__/migration-22-v21/new-brandpage-every-section.v21.json +1231 -0
  258. package/src/brand-page/__fixtures__/migration-22-v21/new-brandsite-every-section.v21.json +1243 -0
  259. package/src/brand-page/__fixtures__/migration-22-v21/real-brandcontact.v21.json +755 -0
  260. package/src/brand-page/__fixtures__/migration-22-v21/real-brandpage-general-link.v21.json +527 -0
  261. package/src/brand-page/__fixtures__/migration-22-v21/real-brandpage-links.v21.json +755 -0
  262. package/src/brand-page/__fixtures__/migration-22-v21/real-brandpage-social-links.v21.json +514 -0
  263. package/src/brand-page/__fixtures__/migration-22-v21/real-brandsite-about.v21.json +459 -0
  264. package/src/brand-page/__fixtures__/migration-22-v21/real-brandsite-footer.v21.json +469 -0
  265. package/src/brand-page/__fixtures__/migration-22-v21/real-brandsite-header-banner.v21.json +481 -0
  266. package/src/brand-page/__fixtures__/migration-22-v21/real-brandsite-sections.v21.json +800 -0
  267. package/src/brand-page/__fixtures__/production-text-style-resolver.fixture.ts +52 -0
  268. package/src/brand-page/components/about-section/AboutSection.vue.test.ts +0 -10
  269. package/src/brand-page/components/contact-form/ContactForm.mixin.test.ts +127 -0
  270. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +31 -14
  271. package/src/brand-page/components/contact-form/ContactForm.vue +26 -12
  272. package/src/brand-page/components/contact-form/ContactForm.vue.test.ts +60 -0
  273. package/src/brand-page/components/mailing-list/MailingList.mixin.test.ts +92 -0
  274. package/src/brand-page/components/mailing-list/MailingList.mixin.ts +28 -3
  275. package/src/brand-page/components/mailing-list/MailingList.vue +9 -4
  276. package/src/brand-page/components/mailing-list/MailingList.vue.test.ts +63 -0
  277. package/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.mixin.ts +51 -1
  278. package/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.test.ts +114 -0
  279. package/src/brand-page/components/navigation/button-carousel/ButtonCarouselNavigation.vue +8 -2
  280. package/src/brand-page/components/navigation/navigation-carousel/NavigationCarousel.test.ts +182 -0
  281. package/src/brand-page/components/navigation/navigation-carousel/NavigationCarousel.vue +59 -12
  282. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +40 -3
  283. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +15 -7
  284. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue.test.ts +133 -3
  285. package/src/brand-page/components/page/Page.mixin.ts +41 -34
  286. package/src/brand-page/components/page/Page.vue +17 -6
  287. package/src/brand-page/components/page/Page.vue.test.ts +91 -1
  288. package/src/brand-page/components/page-footer/PageFooter.mixin.ts +32 -2
  289. package/src/brand-page/components/page-footer/PageFooter.test.ts +79 -0
  290. package/src/brand-page/components/page-footer/PageFooter.vue +12 -4
  291. package/src/brand-page/components/payment/Payment.mixin.ts +34 -2
  292. package/src/brand-page/components/payment/Payment.test.ts +188 -0
  293. package/src/brand-page/components/payment/Payment.vue +12 -5
  294. package/src/brand-page/components/payment/RequestPaymentStripe.vue +1 -0
  295. package/src/brand-page/components/payment/support-me/SupportMe.mixin.ts +49 -2
  296. package/src/brand-page/components/payment/support-me/SupportMe.test.ts +100 -0
  297. package/src/brand-page/components/payment/support-me/SupportMe.vue +7 -1
  298. package/src/brand-page/components/payment/support-me/SupportMe.vue.test.ts +66 -0
  299. package/src/brand-page/components/save-contact/SaveContactWidget.test.ts +100 -0
  300. package/src/brand-page/components/save-contact/SaveContactWidget.vue +6 -3
  301. package/src/brand-page/components/shop/PaymentModal.vue +2 -6
  302. package/src/brand-page/components/shop/PaymentShippingAddress.vue +6 -0
  303. package/src/brand-page/components/shop/Shop.vue +15 -2
  304. package/src/brand-page/components/shop/Shop.vue.test.ts +74 -2
  305. package/src/brand-page/components/social-links/SocialLinks.stories.ts +0 -3
  306. package/src/brand-page/components/social-links/SocialLinks.vue.test.ts +0 -3
  307. package/src/brand-page/components/testimonial-section/__fixtures__/data.ts +0 -3
  308. package/src/brand-page/components/text/Text.stories.ts +0 -17
  309. package/src/brand-page/components/text-wrapper/TextWrapper.parity.test.ts +136 -0
  310. package/src/brand-page/components/text-wrapper/TextWrapper.test.ts +358 -0
  311. package/src/brand-page/components/text-wrapper/TextWrapper.vue +82 -22
  312. package/src/brand-page/constants/font.constants.ts +37 -0
  313. package/src/brand-page/models/footer-icon-color.test.ts +22 -0
  314. package/src/brand-page/models/index.ts +50 -28
  315. package/src/brand-page/models/navigation-styles.model.test.ts +77 -1
  316. package/src/brand-page/models/navigation-styles.model.ts +26 -9
  317. package/src/brand-page/models/payment.model.ts +15 -6
  318. package/src/brand-page/models/styles.model.test.ts +104 -0
  319. package/src/brand-page/models/styles.model.ts +181 -47
  320. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +619 -115
  321. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +150 -9
  322. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +300 -18
  323. package/src/brand-page/utils/color-palette.util.test.ts +126 -1
  324. package/src/brand-page/utils/color-palette.util.ts +92 -26
  325. package/src/brand-page/utils/color.util.test.ts +62 -29
  326. package/src/brand-page/utils/color.util.ts +77 -14
  327. package/src/brand-page/utils/container-text-color.util.test.ts +672 -26
  328. package/src/brand-page/utils/container-text-color.util.ts +472 -46
  329. package/src/brand-page/utils/index.ts +1 -0
  330. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/edited-brandsite-sections.v22.json +1191 -0
  331. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandcontact-every-section.v22.json +2026 -0
  332. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandpage-every-section.v22.json +2017 -0
  333. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandsite-every-section.v22.json +2031 -0
  334. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandcontact.v22.json +1145 -0
  335. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-general-link.v22.json +645 -0
  336. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-links.v22.json +1136 -0
  337. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-social-links.v22.json +662 -0
  338. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandsite-about.v22.json +638 -0
  339. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandsite-footer.v22.json +610 -0
  340. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandsite-header-banner.v22.json +660 -0
  341. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandsite-sections.v22.json +1132 -0
  342. package/src/brand-page/utils/migration/migration-01.ts +293 -237
  343. package/src/brand-page/utils/migration/migration-02.ts +8 -1
  344. package/src/brand-page/utils/migration/migration-20.ts +6 -3
  345. package/src/brand-page/utils/migration/migration-20_5.test.ts +5 -2
  346. package/src/brand-page/utils/migration/migration-21.test.ts +34 -0
  347. package/src/brand-page/utils/migration/migration-21.ts +46 -10
  348. package/src/brand-page/utils/migration/migration-22.fixed-input.test.ts +43 -0
  349. package/src/brand-page/utils/migration/migration-22.test.ts +4103 -0
  350. package/src/brand-page/utils/migration/migration-22.text-font-family.test.ts +72 -0
  351. package/src/brand-page/utils/migration/migration-22.ts +1336 -0
  352. package/src/brand-page/utils/migration.util.test.ts +146 -32
  353. package/src/brand-page/utils/migration.util.ts +18 -0
  354. package/src/brand-page/utils/model.util.ts +4 -0
  355. package/src/brand-page/utils/personalisation.util.test.ts +148 -3
  356. package/src/brand-page/utils/personalisation.util.ts +9 -1
  357. package/src/brand-page/utils/rte-role-lift.util.ts +409 -0
  358. package/src/brand-page/utils/style.util.test.ts +35 -2
  359. package/src/brand-page/utils/style.util.ts +45 -11
  360. package/src/brand-page/utils/text-role-css.util.test.ts +525 -0
  361. package/src/brand-page/utils/text-role-css.util.ts +321 -0
  362. package/src/brand-page/utils/text-role-reconnect.util.test.ts +604 -0
  363. package/src/brand-page/utils/text-role-reconnect.util.ts +727 -0
  364. package/src/brand-page/utils/text-style.util.test.ts +305 -0
  365. package/src/brand-page/utils/text-style.util.ts +198 -24
  366. package/src/brand-page/utils/view-model.page-data.test.ts +232 -0
  367. package/src/brand-page/utils/view-model.util.test.ts +954 -133
  368. package/src/brand-page/utils/view-model.util.ts +187 -139
  369. package/src/brand-page/view-models.ts +4 -0
  370. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.blank-site.test.ts +57 -0
  371. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +9 -5
  372. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +0 -10
  373. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/content-settings-items.fixture.ts +0 -1
  374. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.ts +0 -44
  375. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +253 -200
  376. package/src/brand-page-maker/components/shop/AddOrEditProductContainer.vue +1 -0
  377. package/src/brand-page-maker/components/template-preview/TemplatePreview.vue +1 -0
  378. package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +2 -0
  379. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +1 -0
  380. package/src/brand-page-maker/composables/rich-text-editor/block-typography.test.ts +519 -0
  381. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +1 -1
  382. package/src/brand-page-maker/composables/rich-text-editor/extensions/MaxLength.test.ts +26 -1
  383. package/src/brand-page-maker/composables/rich-text-editor/extensions/RoleTypographyFill.test.ts +110 -0
  384. package/src/brand-page-maker/composables/rich-text-editor/extensions/RoleTypographyFill.ts +99 -0
  385. package/src/brand-page-maker/composables/rich-text-editor/extensions/RteClipboard.ts +397 -0
  386. package/src/brand-page-maker/composables/rich-text-editor/extensions/ViewDomParser.test.ts +139 -0
  387. package/src/brand-page-maker/composables/rich-text-editor/font.ts +36 -5
  388. package/src/brand-page-maker/composables/rich-text-editor/formatting.test.ts +375 -1
  389. package/src/brand-page-maker/composables/rich-text-editor/formatting.ts +301 -17
  390. package/src/brand-page-maker/composables/rich-text-editor/hyperlink.test.ts +82 -1
  391. package/src/brand-page-maker/composables/rich-text-editor/hyperlink.ts +28 -34
  392. package/src/brand-page-maker/composables/rich-text-editor/keepMarksOnEmpty.test.ts +80 -0
  393. package/src/brand-page-maker/composables/rich-text-editor/keepMarksOnEmpty.ts +75 -0
  394. package/src/brand-page-maker/composables/rich-text-editor/spacing.ts +31 -6
  395. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.test.ts +2485 -0
  396. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +435 -337
  397. package/src/brand-page-maker/constants/config-settings-constant.ts +0 -6
  398. package/src/brand-page-maker/convertors/text-css-style-options.convertor.test.ts +43 -0
  399. package/src/brand-page-maker/convertors/text-css-style-options.convertor.ts +6 -2
  400. package/src/brand-page-maker/convertors/text-size-value.convertor.test.ts +24 -0
  401. package/src/brand-page-maker/convertors/text-size-value.convertor.ts +17 -13
  402. package/src/brand-page-maker/default-getters/payment-text-and-border-color.default-getter.ts +7 -22
  403. package/src/brand-page-maker/default-getters/text-color.default-getter.ts +5 -21
  404. package/src/brand-page-maker/models/content-settings.test.ts +708 -0
  405. package/src/brand-page-maker/models/content-settings.ts +133 -82
  406. package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store-utils.test.ts +63 -1
  407. package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store-utils.ts +16 -3
  408. package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.test.ts +34 -0
  409. package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.ts +4 -2
  410. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +185 -1
  411. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +22 -34
  412. package/src/brand-page-maker/stores/history/history-store.ts +35 -2
  413. package/src/brand-page-maker/utils/__snapshots__/apply-color-strategy.golden.test.ts.snap +528 -0
  414. package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +12 -0
  415. package/src/brand-page-maker/utils/apply-color-strategy.golden.test.ts +2 -0
  416. package/src/brand-page-maker/utils/apply-color-strategy.test.ts +181 -3
  417. package/src/brand-page-maker/utils/apply-color-strategy.ts +36 -4
  418. package/src/brand-page-maker/utils/index.ts +2 -1
  419. package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +447 -410
  420. package/src/brand-page-maker/utils/rich-text-editor.util.ts +610 -506
  421. package/src/brand-page-maker/utils/rte-authoring.util.test.ts +244 -0
  422. package/src/brand-page-maker/utils/rte-authoring.util.ts +175 -0
  423. package/src/brand-page-maker/utils/rte-clipboard.util.test.ts +558 -0
  424. package/src/brand-page-maker/utils/rte-clipboard.util.ts +473 -0
  425. package/src/brand-page-maker/utils/rte-features.test.ts +99 -3
  426. package/src/brand-page-maker/utils/rte-render-parity.test.ts +1 -1
  427. package/src/brand-page-maker/utils/rte-render.util.test.ts +230 -0
  428. package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +1 -1
  429. package/src/brand-page-maker/utils/rte-sanitize.util.ts +14 -3
  430. package/src/brand-page-maker/utils/rte-schema-version.test.ts +102 -0
  431. package/src/bundles/bundled-translations.de-DE.json +21 -2
  432. package/src/bundles/bundled-translations.es-ES.json +21 -2
  433. package/src/bundles/bundled-translations.fr-CA.json +21 -2
  434. package/src/bundles/bundled-translations.fr-FR.json +21 -2
  435. package/src/bundles/bundled-translations.json +21 -2
  436. package/src/bundles/bundled-translations.pt-BR.json +21 -2
  437. package/src/bundles/bundled-translations.pt-PT.json +21 -2
  438. package/src/clients/ai-assistant-client.ts +1 -0
  439. package/src/composables/useTranslate.ts +2 -1
  440. package/src/lib.ts +2 -2
  441. package/src/metadata-builder/builders/footer-builder.test.ts +70 -45
  442. package/src/metadata-builder/builders/footer-builder.ts +4 -8
  443. package/src/metadata-builder/builders/global-role-propagation.test.ts +907 -0
  444. package/src/metadata-builder/builders/global-role-propagation.ts +430 -0
  445. package/src/metadata-builder/builders/global-styles-builder.test.ts +1020 -0
  446. package/src/metadata-builder/builders/global-styles-builder.ts +194 -48
  447. package/src/metadata-builder/builders/header-builder.test.ts +112 -40
  448. package/src/metadata-builder/builders/header-builder.ts +18 -8
  449. package/src/metadata-builder/builders/section-builders/about-section-builder.ts +5 -3
  450. package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +5 -3
  451. package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +5 -3
  452. package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +7 -4
  453. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +5 -3
  454. package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +3 -2
  455. package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +3 -2
  456. package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +7 -7
  457. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +5 -3
  458. package/src/metadata-builder/builders/section-builders/social-links-builder.test.ts +2 -2
  459. package/src/metadata-builder/builders/section-builders/social-links-builder.ts +21 -10
  460. package/src/metadata-builder/builders/section-builders/support-me-builder.test.ts +10 -0
  461. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +3 -2
  462. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +111 -61
  463. package/src/metadata-builder/builders/section-field-maps.ts +1 -0
  464. package/src/metadata-builder/builders/section-field-utils.test.ts +545 -2
  465. package/src/metadata-builder/builders/section-field-utils.ts +202 -69
  466. package/src/metadata-builder/builders/section-rules/global-styles.rules.ts +38 -13
  467. package/src/metadata-builder/builders/section-rules/social-links.rules.ts +5 -0
  468. package/src/metadata-builder/helpers/rte.helpers.test.ts +101 -0
  469. package/src/metadata-builder/helpers/rte.helpers.ts +129 -43
  470. package/src/metadata-builder/metadata-executor.test.ts +167 -0
  471. package/src/metadata-builder/metadata-executor.ts +34 -1
  472. package/src/metadata-builder/models.ts +2 -0
  473. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +23 -7
  474. package/src/metadata-builder/parsers/section-tool-arguments.ts +21 -1
  475. package/src/metadata-builder/sanitizers/__tests__/section-args-validator.test.ts +215 -0
  476. package/src/metadata-builder/sanitizers/rte-schema-check.ts +1 -1
  477. package/src/metadata-builder/sanitizers/sanitizer-rules.ts +4 -8
  478. package/src/metadata-builder/sanitizers/section-args-validator.ts +136 -53
  479. package/src/shared/color-palette/section-color-matches.test.ts +15 -11
  480. package/src/shared/color-palette/section-color-matches.ts +9 -28
  481. package/src/shared/color-strategies/color-strategies.ts +35 -0
  482. package/src/shared/color-strategies/rte-color.test.ts +1 -1
  483. package/src/shared/color-strategies/rte-color.ts +19 -0
  484. package/src/shared/color-strategies/rte-field-pairs.test.ts +26 -5
  485. package/src/shared/color-strategies/rte-field-pairs.ts +33 -0
  486. package/src/shared/content-settings/components/color-matches-panel/ColorMatchesPanel.test.ts +43 -66
  487. package/src/shared/content-settings/components/color-matches-panel/ColorMatchesPanel.vue +7 -1
  488. package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +1 -48
  489. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +33 -7
  490. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.test.ts +128 -1
  491. package/src/shared/content-settings/components/draggable-item-list/DraggableItemList.vue +1 -0
  492. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.ts +0 -6
  493. package/src/shared/content-settings/components/logo-selection-field/LogoSelection.vue +3 -0
  494. package/src/shared/content-settings/components/logo-size-field/LogoSizeSlider.vue +3 -0
  495. package/src/shared/content-settings/components/logo-size-field/LogoSizeSliderField.vue +1 -0
  496. package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +1 -0
  497. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.test.ts +56 -3
  498. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +11 -4
  499. package/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.test.ts +335 -25
  500. package/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.ts +248 -70
  501. package/src/shared/content-settings/components/text-style-field/TextStyleField.vue +113 -25
  502. package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.de-DE.json +17 -10
  503. package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.es-ES.json +17 -10
  504. package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.fr-CA.json +17 -10
  505. package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.fr-FR.json +17 -10
  506. package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.json +8 -1
  507. package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.pt-BR.json +17 -10
  508. package/src/shared/content-settings/components/text-style-field/i18n/text-style-field.pt-PT.json +17 -10
  509. package/src/shared/content-settings/components/text-style-role-dropdown/TextStyleRoleDropdown.test.ts +141 -0
  510. package/src/shared/content-settings/components/text-style-role-dropdown/TextStyleRoleDropdown.vue +136 -0
  511. package/src/shared/content-settings/store/content-settings-store.test.ts +491 -6
  512. package/src/shared/content-settings/store/content-settings-store.ts +159 -17
  513. package/src/shared/logo-settings/components/EditLogoSettings.vue +1 -0
  514. package/src/shared/logo-settings/components/LogoVariations.vue +1 -0
  515. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.vue +1 -0
  516. package/src/shared/navigation-style-settings/store/index.test.ts +295 -0
  517. package/src/shared/navigation-style-settings/store/index.ts +65 -15
  518. package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +2 -0
  519. package/src/shared/page-settings/components/page-settings/PageElement.vue +1 -0
  520. package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.test.ts +44 -4
  521. package/src/shared/page-settings/components/page-settings-section-styles/HeaderButtonSettings.vue +13 -4
  522. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.navigation.test.ts +89 -0
  523. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.text-style.test.ts +91 -0
  524. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +14 -2
  525. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.vue +2 -0
  526. package/src/shared/page-settings/store/page-settings-store.text-style.test.ts +84 -0
  527. package/src/shared/page-settings/store/page-settings-store.ts +30 -1
  528. package/src/shared/rte-extensions/AutoLink.test.ts +98 -0
  529. package/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/AutoLink.ts +87 -58
  530. package/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/MaxLength.ts +4 -1
  531. package/src/shared/rte-extensions/TextEffects.ts +151 -0
  532. package/src/shared/rte-extensions/ViewDomParser.ts +27 -0
  533. package/src/{brand-page-maker/composables/rich-text-editor/getRteExtensions.ts → shared/rte-extensions.util.ts} +66 -9
  534. package/src/shared/rte-link.util.ts +2 -0
  535. package/src/shared/rte-render.util.ts +385 -0
  536. package/src/shared/rte-shape.util.ts +29 -0
  537. package/src/shared/side-bar/store/side-bar-store.test.ts +27 -1
  538. package/src/shared/side-bar/store/side-bar-store.ts +11 -0
  539. package/src/shared/style-settings/components/StyleSettings.mixin.ts +7 -3
  540. package/src/shared/style-settings/components/StyleSettings.test.ts +116 -0
  541. package/src/shared/style-settings/components/StyleSettings.vue +75 -25
  542. package/src/shared/style-settings/components/i18n/style-settings.de-DE.json +32 -20
  543. package/src/shared/style-settings/components/i18n/style-settings.es-ES.json +32 -20
  544. package/src/shared/style-settings/components/i18n/style-settings.fr-CA.json +32 -20
  545. package/src/shared/style-settings/components/i18n/style-settings.fr-FR.json +32 -20
  546. package/src/shared/style-settings/components/i18n/style-settings.json +13 -3
  547. package/src/shared/style-settings/components/i18n/style-settings.pt-BR.json +32 -20
  548. package/src/shared/style-settings/components/i18n/style-settings.pt-PT.json +32 -20
  549. package/src/shared/style-settings/components/text-style-options-settings-with-state/TextStyleOptionsSettingsWithState.vue +14 -34
  550. package/src/shared/style-settings/store/style-settings-store.ts +3 -49
  551. package/src/shared/style-settings/text-style-size-order.test.ts +76 -0
  552. package/src/shared/style-settings/text-style-size-order.ts +28 -0
  553. package/src/shared/template-settings/components/TemplatesSettings.vue +4 -0
  554. package/src/shared/text-role-typography.util.ts +277 -0
  555. package/src/utils/minify-brand-page-metadata.test.ts +102 -7
  556. package/src/utils/minify-brand-page-metadata.ts +62 -8
  557. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/AutoLink.d.ts +0 -2
  558. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
  559. package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -63
  560. package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -106
  561. package/lib/src/brand-page-maker/utils/rte-shape.d.ts +0 -18
  562. package/lib/src/brand-page-maker/utils/rte-shape.js +0 -21
  563. package/lib/src/metadata-builder/builders/global-font-propagation.d.ts +0 -41
  564. package/lib/src/metadata-builder/builders/global-font-propagation.js +0 -146
  565. package/src/brand-page-maker/utils/rte-render.util.ts +0 -179
  566. package/src/brand-page-maker/utils/rte-shape.ts +0 -29
  567. package/src/metadata-builder/builders/global-font-propagation.test.ts +0 -401
  568. package/src/metadata-builder/builders/global-font-propagation.ts +0 -238
  569. package/src/shared/style-settings/components/StyleSettingsSection.vue +0 -8
  570. /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/FontFamily.d.ts +0 -0
  571. /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/FontFamily.js +0 -0
  572. /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/LetterSpacing.d.ts +0 -0
  573. /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/LetterSpacing.js +0 -0
  574. /package/lib/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/MaxLength.d.ts +0 -0
  575. /package/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/FontFamily.ts +0 -0
  576. /package/src/{brand-page-maker/composables/rich-text-editor/extensions → shared/rte-extensions}/LetterSpacing.ts +0 -0
@@ -4,12 +4,15 @@ import {
4
4
  type TextContent,
5
5
  type MailingListContent,
6
6
  type ContactFormContent,
7
+ type ShopContent,
7
8
  type ColorItem,
9
+ type TextStyleDeclaration,
8
10
  type HeaderButton,
9
11
  } from '../models';
10
12
  import {
11
13
  BRAND_PAGE_TYPES,
12
14
  BUTTON_LINK_TYPE,
15
+ BrandPageModel,
13
16
  PageHeaderDesktop,
14
17
  PageStyles,
15
18
  } from '../models';
@@ -18,20 +21,29 @@ import {
18
21
  mapToContactFormViewModel,
19
22
  mapToImageViewModel,
20
23
  mapToMailingListViewModel,
24
+ mapToPageFooterViewModel,
21
25
  mapToPageHeaderViewModel,
22
26
  mapToProfileViewModel,
23
27
  mapToTextViewModel,
24
28
  mapToViewModel,
29
+ applyViewModelTextStyles,
30
+ getBrandPageViewModelFromBrandPageModel,
31
+ getCriticalResourceURLs,
32
+ resolveSaveContactTitleStyle,
25
33
  } from './view-model.util';
26
- import { ALIGN_CARD_CENTER } from '../constants';
34
+ import type { BrandPageViewModel } from '../view-models';
35
+ import {
36
+ ALIGN_CARD_CENTER,
37
+ HEADER_DISPLAY,
38
+ NAVIGATION_DISPLAY,
39
+ } from '../constants';
27
40
  import * as uuid from 'uuid';
28
41
  import {
29
42
  DEFAULT_TEXT_ALIGN,
30
43
  FONT_SIZE_BODY,
31
44
  FONT_SIZE_HEADINGS,
32
- FONT_SIZE_PROFILE_NAME,
33
- LINE_HEIGHT_BODY,
34
45
  LINE_HEIGHT_HEADINGS,
46
+ FALLBACK_DEFAULT_LINK_COLOR,
35
47
  } from '../constants';
36
48
  import { createInitialBannerSectionContent } from '@brand-page-maker/models';
37
49
 
@@ -58,6 +70,25 @@ const createOldProfileContent = (): ProfileContent => {
58
70
  };
59
71
  };
60
72
 
73
+ // BP-5969 (ADR-0020): a section title's own colour lives in its RTE mark, not on the
74
+ // style object. Tests that exercise the "fall back to the title colour" path (the
75
+ // reCAPTCHA disclosure footnote, the social-links icon fill) must put the colour there.
76
+ const rteTitleWithColour = (color: string): unknown => ({
77
+ type: 'doc',
78
+ content: [
79
+ {
80
+ type: 'paragraph',
81
+ content: [
82
+ {
83
+ type: 'text',
84
+ text: 'Title',
85
+ marks: [{ type: 'textStyle', attrs: { color } }],
86
+ },
87
+ ],
88
+ },
89
+ ],
90
+ });
91
+
61
92
  describe('view model util', () => {
62
93
  test('mapToImageViewModel should return image view model when given an image content', () => {
63
94
  // Arrange
@@ -78,43 +109,44 @@ describe('view model util', () => {
78
109
  expect(viewModel.role).toBe(content.type);
79
110
  expect(viewModel.type).toBe('image');
80
111
  });
81
- test('mapToProfileViewModel should return profile view model with default title text styles when given an old profile content', () => {
82
- // Arrange
83
- const pageStyles = new PageStyles();
84
- const profileContent = createOldProfileContent();
85
-
86
- // Act
87
- const viewModel = mapToProfileViewModel(profileContent, pageStyles);
88
-
89
- // Assert
90
- expect(viewModel.titleStyle?.fontSize).toBe(FONT_SIZE_BODY);
91
- expect(viewModel.titleStyle?.lineHeight).toBe(LINE_HEIGHT_BODY);
92
- });
93
-
94
- test('mapToProfileViewModel should return profile view model with default name text styles when given an old profile content', () => {
95
- // Arrange
96
- const pageStyles = new PageStyles();
97
- const profileContent = createOldProfileContent();
98
-
99
- // Act
100
- const viewModel = mapToProfileViewModel(profileContent, pageStyles);
101
-
102
- // Assert
103
- expect(viewModel.nameStyle?.fontSize).toBe(FONT_SIZE_PROFILE_NAME);
104
- expect(viewModel.nameStyle?.lineHeight).toBe(LINE_HEIGHT_HEADINGS);
105
- });
106
-
107
- test('mapToProfileViewModel should return profile view model with default description text styles when given an old profile content', () => {
108
- // Arrange
112
+ // BP-5969 (R3 revision) — the profile's built-in size/line-height defaults are deleted
113
+ // outright, on a role or off it: an unset field draws through its role, real or a fallback
114
+ // (T3.1). Old field names ("default title/name/description text styles") retired with them.
115
+ test('profile has no built-in size', () => {
109
116
  const pageStyles = new PageStyles();
110
- const profileContent = createOldProfileContent();
117
+ const onRoles: ProfileContent = {
118
+ ...createOldProfileContent(),
119
+ titleStyle: {
120
+ textAlign: DEFAULT_TEXT_ALIGN,
121
+ textStyleType: 'paragraph2',
122
+ },
123
+ descriptionStyle: {
124
+ textAlign: DEFAULT_TEXT_ALIGN,
125
+ textStyleType: 'paragraph2',
126
+ },
127
+ nameStyle: { textAlign: DEFAULT_TEXT_ALIGN, textStyleType: 'heading5' },
128
+ };
129
+ const noRole = createOldProfileContent();
111
130
 
112
- // Act
113
- const viewModel = mapToProfileViewModel(profileContent, pageStyles);
131
+ for (const profileContent of [onRoles, noRole]) {
132
+ const viewModel = mapToProfileViewModel(profileContent, pageStyles);
114
133
 
115
- // Assert
116
- expect(viewModel.descriptionStyle?.fontSize).toBe(FONT_SIZE_BODY);
117
- expect(viewModel.descriptionStyle?.lineHeight).toBe(LINE_HEIGHT_BODY);
134
+ for (const key of [
135
+ 'titleStyle',
136
+ 'descriptionStyle',
137
+ 'nameStyle',
138
+ ] as const) {
139
+ const style = viewModel[key] as TextStyleDeclaration;
140
+
141
+ expect(style.fontSize).toBeUndefined();
142
+ expect(style.lineHeight).toBeUndefined();
143
+ }
144
+ }
145
+
146
+ // The no-role fields still get a canonical fallback role (R3), not a bare `undefined`.
147
+ expect(
148
+ mapToProfileViewModel(noRole, pageStyles).titleStyle?.textStyleType,
149
+ ).toBeTruthy();
118
150
  });
119
151
 
120
152
  // The profile card echoes the global button style — fill, shadow AND outline — so it
@@ -233,7 +265,9 @@ describe('view model util', () => {
233
265
  // element override. `migration-21` strips that field on existing models, which
234
266
  // is what keeps published sites rendering identically; this test covers the
235
267
  // post-cascade resolver contract, and the migration suite covers the strip.
236
- test('mapToTextViewModel should fall back to the global text style colour once the legacy textColor is cleared', () => {
268
+ // R1: page data no longer merges the role's typography in, so a fontSize the field never
269
+ // set itself is undefined — the fallback role's CSS var supplies it at render, not this object.
270
+ test('mapToTextViewModel keeps the legacy-role colour cascade; typography stays own-values-only', () => {
237
271
  // Arrange
238
272
  const pageStyles = new PageStyles();
239
273
 
@@ -269,11 +303,15 @@ describe('view model util', () => {
269
303
  );
270
304
 
271
305
  // Assert — the paragraphs style defines no colour, so this inherits.
272
- expect(viewModel.textStyle?.color).toBeUndefined();
273
- expect(viewModel.textStyle?.fontSize).toBe(FONT_SIZE_HEADINGS);
306
+ expect(
307
+ (viewModel.textStyle as TextStyleDeclaration)?.color,
308
+ ).toBeUndefined();
309
+ expect(
310
+ (viewModel.textStyle as TextStyleDeclaration)?.fontSize,
311
+ ).toBeUndefined();
274
312
  });
275
313
 
276
- test('a legacy textColor still overrides when the text is NOT linked to a global text style', () => {
314
+ test('does not render an un-migrated legacy textColor sidecar', () => {
277
315
  // Arrange
278
316
  const pageStyles = new PageStyles();
279
317
 
@@ -297,8 +335,11 @@ describe('view model util', () => {
297
335
  BRAND_PAGE_TYPES.brandSite,
298
336
  );
299
337
 
300
- // Assert — unlinked, so migration-21 leaves it alone and it must keep rendering.
301
- expect(viewModel.textStyle?.color).toBe('#ff0000');
338
+ // Migration-22 folds this value into textStyle before rendering. The view model deliberately
339
+ // does not keep a legacy sidecar fallback alive for data that bypasses the migration gate.
340
+ expect(
341
+ (viewModel.textStyle as TextStyleDeclaration)?.color,
342
+ ).toBeUndefined();
302
343
  });
303
344
 
304
345
  test('Profile text alignment should not overwrite if there is an existing text alignment', () => {
@@ -345,48 +386,70 @@ describe('view model util', () => {
345
386
  test("the element's own colour wins over the global text style's", () => {
346
387
  const pageStyles = new PageStyles();
347
388
 
348
- pageStyles.textStyles.textStyleOptions.paragraphs.style.color = '#00ff00';
389
+ (
390
+ pageStyles.textStyles.textStyleOptions.paragraphs
391
+ .style as unknown as Record<string, unknown>
392
+ ).color = '#00ff00';
349
393
 
350
394
  const viewModel = mapText(
351
395
  textContentWith({
352
396
  textStyleType: 'paragraphs',
353
397
  color: 'var(--palette-0)',
354
- }),
398
+ } as TextStyleDeclaration),
355
399
  pageStyles,
356
400
  );
357
401
 
358
- expect(viewModel.textStyle?.color).toBe('var(--palette-0)');
402
+ expect((viewModel.textStyle as TextStyleDeclaration)?.color).toBe(
403
+ 'var(--palette-0)',
404
+ );
359
405
  });
360
406
 
361
- test('typography still comes from the global text style even when the element overrides colour', () => {
407
+ // R1: colour keeps its own cascade; typography no longer merges the role's values in — a
408
+ // painter references the role for the rest (`roleTypographyStyle`/`fillRteRunTypography`).
409
+ test('colour still cascades from the global text style; typography does not', () => {
362
410
  const pageStyles = new PageStyles();
363
411
 
364
- pageStyles.textStyles.textStyleOptions.paragraphs.style.color = '#00ff00';
412
+ (
413
+ pageStyles.textStyles.textStyleOptions.paragraphs
414
+ .style as unknown as Record<string, unknown>
415
+ ).color = '#00ff00';
365
416
  pageStyles.textStyles.textStyleOptions.paragraphs.style.fontSize =
366
417
  FONT_SIZE_HEADINGS;
367
418
 
368
419
  const viewModel = mapText(
369
- textContentWith({ textStyleType: 'paragraphs', color: '#ff0000' }),
420
+ textContentWith({
421
+ textStyleType: 'paragraphs',
422
+ color: '#ff0000',
423
+ } as TextStyleDeclaration),
370
424
  pageStyles,
371
425
  );
372
426
 
373
- expect(viewModel.textStyle?.color).toBe('#ff0000');
374
- expect(viewModel.textStyle?.fontSize).toBe(FONT_SIZE_HEADINGS);
427
+ expect((viewModel.textStyle as TextStyleDeclaration)?.color).toBe(
428
+ '#ff0000',
429
+ );
430
+ expect(
431
+ (viewModel.textStyle as TextStyleDeclaration)?.fontSize,
432
+ ).toBeUndefined();
375
433
  });
376
434
 
377
- // The common case: a linked element stores ONLY the link, so the cascade must
378
- // reproduce exactly what rendered before Phase 1.
379
- test('an element with only a link falls back to the global text style colour', () => {
435
+ // ADR-0020: a linked element with only the link takes NO colour from its role —
436
+ // it resolves to undefined and renders the page base coat by root inheritance.
437
+ test('an element with only a link takes no colour from its role (undefined → base coat)', () => {
380
438
  const pageStyles = new PageStyles();
381
439
 
382
- pageStyles.textStyles.textStyleOptions.paragraphs.style.color = '#00ff00';
440
+ (
441
+ pageStyles.textStyles.textStyleOptions.paragraphs
442
+ .style as unknown as Record<string, unknown>
443
+ ).color = '#00ff00';
383
444
 
384
445
  const viewModel = mapText(
385
446
  textContentWith({ textStyleType: 'paragraphs' }),
386
447
  pageStyles,
387
448
  );
388
449
 
389
- expect(viewModel.textStyle?.color).toBe('#00ff00');
450
+ expect(
451
+ (viewModel.textStyle as TextStyleDeclaration)?.color,
452
+ ).toBeUndefined();
390
453
  });
391
454
 
392
455
  test('no element colour and no global colour resolves to undefined (the page base coat applies by root CSS inheritance)', () => {
@@ -400,7 +463,9 @@ describe('view model util', () => {
400
463
  // Deliberately NOT '#0000ff' — the site-wide default reaches the text by
401
464
  // inheritance from the root element, not through the resolver. Seeding it
402
465
  // here would read as an element override and beat the global text style.
403
- expect(viewModel.textStyle?.color).toBeUndefined();
466
+ expect(
467
+ (viewModel.textStyle as TextStyleDeclaration)?.color,
468
+ ).toBeUndefined();
404
469
  });
405
470
  });
406
471
 
@@ -413,11 +478,17 @@ describe('view model util', () => {
413
478
  const mapContent = (content: any, pageStyles: PageStyles): any =>
414
479
  mapToViewModel(pageStyles, BRAND_PAGE_TYPES.brandSite, {})(content);
415
480
 
416
- test('testimonial section styles follow their global text style', () => {
481
+ test('testimonial link-only styles take no colour from their role (role colour retired)', () => {
417
482
  const pageStyles = new PageStyles();
418
483
 
419
- pageStyles.textStyles.textStyleOptions.titles.style.color = '#00ff00';
420
- pageStyles.textStyles.textStyleOptions.paragraphs.style.color = '#00ff00';
484
+ (
485
+ pageStyles.textStyles.textStyleOptions.titles
486
+ .style as unknown as Record<string, unknown>
487
+ ).color = '#00ff00';
488
+ (
489
+ pageStyles.textStyles.textStyleOptions.paragraphs
490
+ .style as unknown as Record<string, unknown>
491
+ ).color = '#00ff00';
421
492
 
422
493
  const viewModel = mapContent(
423
494
  {
@@ -431,34 +502,113 @@ describe('view model util', () => {
431
502
  pageStyles,
432
503
  );
433
504
 
434
- expect(viewModel.titleStyle?.color).toBe('#00ff00');
435
- expect(viewModel.nameStyle?.color).toBe('#00ff00');
436
- expect(viewModel.positionStyle?.color).toBe('#00ff00');
437
- expect(viewModel.testimonialStyle?.color).toBe('#00ff00');
505
+ // ADR-0020: role colour does not cascade — link-only fields resolve to undefined
506
+ // (the mapper still never pre-seeds a default that would beat that).
507
+ expect(viewModel.titleStyle?.color).toBeUndefined();
508
+ expect(viewModel.nameStyle?.color).toBeUndefined();
509
+ expect(viewModel.positionStyle?.color).toBeUndefined();
510
+ expect(viewModel.testimonialStyle?.color).toBeUndefined();
438
511
  });
439
512
 
440
- test('social-links title follows its own global text style, while the icon fill takes the page base coat (paragraphs)', () => {
513
+ test('social-links title takes no role colour; the icon fill never follows the title colour', () => {
441
514
  const pageStyles = new PageStyles();
442
515
 
443
- pageStyles.textStyles.textStyleOptions.paragraphs.style.color = '#0000ff';
444
- pageStyles.textStyles.textStyleOptions.titles.style.color = '#00ff00';
516
+ (
517
+ pageStyles.textStyles.textStyleOptions.heading1
518
+ .style as unknown as Record<string, unknown>
519
+ ).color = '#00ff00';
520
+
521
+ const socialLinks = (iconFillStyles: BrandPageCssStyle): any =>
522
+ mapContent(
523
+ {
524
+ id: '1',
525
+ type: 'social-links',
526
+ title: rteTitleWithColour('#0000ff'),
527
+ titleStyle: { textStyleType: 'heading1' },
528
+ iconFillStyles,
529
+ socialLinks: [],
530
+ },
531
+ pageStyles,
532
+ );
533
+
534
+ // ADR-0020: heading1's role colour does not cascade → the title takes no colour.
535
+ expect(socialLinks({}).titleStyle?.color).toBeUndefined();
536
+ // The icons paint their stored fill (baked at birth / by migration-22), not the
537
+ // title's mark colour — recolouring the title must not repaint them.
538
+ expect(socialLinks({ fill: '#ff0000' }).iconFillStyles?.fill).toBe(
539
+ '#ff0000',
540
+ );
541
+ expect(socialLinks({}).iconFillStyles?.fill).toBeUndefined();
542
+ });
543
+
544
+ test('shop "no products" label takes the title colour when it has one', () => {
545
+ const pageStyles = new PageStyles();
445
546
 
446
547
  const viewModel = mapContent(
447
548
  {
448
549
  id: '1',
449
- type: 'social-links',
450
- titleStyle: { textStyleType: 'titles' },
451
- socialLinks: [],
550
+ type: 'shop-section',
551
+ title: rteTitleWithColour('#0000ff'),
552
+ description: '',
553
+ successMessage: '',
554
+ products: [],
555
+ displayStyle: 'text',
556
+ aspectRatio: '1:1',
557
+ fillColor: {},
558
+ defaultBorderColor: {},
559
+ itemTextStyle: {},
560
+ backgroundStyle: {},
561
+ shopPaymentConfig: {
562
+ paymentConfig: {},
563
+ paymentOptionDisplayType: 'text',
564
+ paymentOptionImageType: 'none',
565
+ isCustomerRequestDetailsEnabled: false,
566
+ isShippingEnabled: false,
567
+ },
452
568
  },
453
569
  pageStyles,
454
570
  );
455
571
 
456
- expect(viewModel.titleStyle?.color).toBe('#00ff00');
457
- // `iconFillStyles` is not a CONTENT_TEXT_STYLE_PROPERTY_KEYS entry, so no
458
- // resolver touches it and this seed is the only source of the icon colour.
459
- // BP-5886 Phase 3: sourced from the paragraphs global style (the page base
460
- // coat) now that the root default text colour is retired.
461
- expect(viewModel.iconFillStyles?.fill).toBe('#0000ff');
572
+ expect(viewModel.noProductsColor).toBe('#0000ff');
573
+ });
574
+
575
+ // A cleared, all-uncoloured or legacy plain-string title has no run colour to read, so
576
+ // the label falls back to the title's own contrast colour — the band's, not the product
577
+ // tile's: a painted tile stacks below the title, not behind it.
578
+ test('shop "no products" label falls back to the title contrast colour when the title has none', () => {
579
+ const pageStyles = new PageStyles();
580
+
581
+ pageStyles.colors = [
582
+ { id: 'palette-0', value: '#000000', type: 'palette' }, // dark brand
583
+ { id: 'palette-1', value: '#ffffff', type: 'palette' }, // light brand
584
+ ];
585
+
586
+ const shopContent: ShopContent = {
587
+ id: '1',
588
+ type: 'shop-section',
589
+ title: 'Shop',
590
+ description: '',
591
+ successMessage: '',
592
+ products: [],
593
+ displayStyle: 'text',
594
+ aspectRatio: '1:1',
595
+ fillColor: { backgroundColor: 'var(--palette-1)' }, // light tile
596
+ defaultBorderColor: {},
597
+ itemTextStyle: {},
598
+ backgroundStyle: { backgroundColor: 'var(--palette-0)' }, // dark band
599
+ shopPaymentConfig: {
600
+ paymentOptions: [],
601
+ paymentOptionDisplayType: 'text',
602
+ paymentOptionImageType: 'auto',
603
+ isCustomerRequestDetailsEnabled: false,
604
+ isShippingEnabled: false,
605
+ },
606
+ };
607
+
608
+ const viewModel = mapContent(shopContent, pageStyles);
609
+
610
+ // Light on the dark band, not the dark-on-dark a tile-routed fallback would give.
611
+ expect(viewModel.noProductsColor).toBe('var(--palette-1)');
462
612
  });
463
613
  });
464
614
 
@@ -658,6 +808,294 @@ describe('view model util', () => {
658
808
  },
659
809
  );
660
810
  });
811
+
812
+ // BP-5969 (R1) — page data keeps only what a field sets itself; the painter references
813
+ // the role for the rest (`roleTypographyStyle`/`fillRteRunTypography`). Colour, alignment,
814
+ // autoscale and `textStyleType` still resolve here; the seven typography keys do not merge.
815
+ describe('page data carries only own typography (R1)', () => {
816
+ const TYPOGRAPHY_KEYS = [
817
+ 'fontFamily',
818
+ 'fontSize',
819
+ 'fontWeight',
820
+ 'fontStyle',
821
+ 'letterSpacing',
822
+ 'lineHeight',
823
+ 'textDecoration',
824
+ ] as const;
825
+
826
+ test('page data carries only own typography', () => {
827
+ const pageStyles = new PageStyles();
828
+ const content: any = {
829
+ titleStyle: {
830
+ textStyleType: 'heading2',
831
+ textAlign: 'left',
832
+ color: '#112233',
833
+ },
834
+ };
835
+
836
+ const style = applyViewModelTextStyles(content, pageStyles)
837
+ .titleStyle as TextStyleDeclaration;
838
+
839
+ for (const key of TYPOGRAPHY_KEYS) {
840
+ expect(style[key]).toBeUndefined();
841
+ }
842
+
843
+ expect(style.textStyleType).toBe('heading2');
844
+ expect(style.textAlign).toBe('left');
845
+ expect(style.color).toBe('#112233');
846
+ });
847
+
848
+ test('page data keeps one override', () => {
849
+ const pageStyles = new PageStyles();
850
+ const content: any = {
851
+ titleStyle: {
852
+ textStyleType: 'heading2',
853
+ textAlign: 'left',
854
+ fontSize: '3em',
855
+ },
856
+ };
857
+
858
+ const style = applyViewModelTextStyles(content, pageStyles)
859
+ .titleStyle as TextStyleDeclaration;
860
+ const present = TYPOGRAPHY_KEYS.filter((key) => style[key] !== undefined);
861
+
862
+ expect(present).toEqual(['fontSize']);
863
+ expect(style.fontSize).toBe('3em');
864
+ });
865
+
866
+ // T6.3 (R6): an item already saved at fontSize: 1em keeps it — covered by the second item.
867
+ test('text items carry only own typography', () => {
868
+ const pageStyles = new PageStyles();
869
+ const textContent: TextContent = {
870
+ backgroundColor: {},
871
+ iconUrl: '',
872
+ id: '0',
873
+ text: 'Test text',
874
+ textItems: [
875
+ { id: 'i1', text: 'One', textStyle: { textStyleType: 'paragraph2' } },
876
+ {
877
+ id: 'i2',
878
+ text: 'Two',
879
+ textStyle: { textStyleType: 'paragraph2', fontSize: '1em' },
880
+ },
881
+ ],
882
+ textAlignment: {},
883
+ textColor: {},
884
+ textSize: {},
885
+ textStyle: {},
886
+ type: 'text',
887
+ };
888
+
889
+ const items = mapToTextViewModel(
890
+ textContent,
891
+ pageStyles,
892
+ BRAND_PAGE_TYPES.brandSite,
893
+ ).textItems as any[];
894
+
895
+ expect(items[0].textStyle.fontSize).toBeUndefined();
896
+ expect(items[0].textStyle.textStyleType).toBe('paragraph2');
897
+ expect(items[1].textStyle.fontSize).toBe('1em');
898
+ });
899
+
900
+ // S6 — an item with no textStyle at all threw (pickTypography/bestFitRole read it with no
901
+ // `?.`); it must fall back the same way an item with an empty textStyle does.
902
+ test('a text item with no textStyle of its own does not throw', () => {
903
+ const pageStyles = new PageStyles();
904
+ const textContent: TextContent = {
905
+ backgroundColor: {},
906
+ iconUrl: '',
907
+ id: '0',
908
+ text: 'Test text',
909
+ textItems: [{ id: 'i1', text: 'One' } as never],
910
+ textAlignment: {},
911
+ textColor: {},
912
+ textSize: {},
913
+ textStyle: {},
914
+ type: 'text',
915
+ };
916
+
917
+ expect(() =>
918
+ mapToTextViewModel(textContent, pageStyles, BRAND_PAGE_TYPES.brandSite),
919
+ ).not.toThrow();
920
+
921
+ const items = mapToTextViewModel(
922
+ textContent,
923
+ pageStyles,
924
+ BRAND_PAGE_TYPES.brandSite,
925
+ ).textItems as any[];
926
+
927
+ expect(items[0].textStyle.textStyleType).toBe('paragraph1');
928
+ });
929
+
930
+ test('text off the ten draws through its fallback role', () => {
931
+ const pageStyles = new PageStyles();
932
+
933
+ // A section title with no role, sized like a heading — nearest tier by default size.
934
+ const titleContent: any = { titleStyle: { fontSize: '3em' } };
935
+ const titleStyle = applyViewModelTextStyles(titleContent, pageStyles)
936
+ .titleStyle as TextStyleDeclaration;
937
+
938
+ expect(titleStyle.textStyleType).toBe('heading3');
939
+ expect(titleStyle.fontSize).toBe('3em');
940
+
941
+ // A description the same size stays on a paragraph tier: it holds body text.
942
+ const descriptionContent: any = { descriptionStyle: { fontSize: '3em' } };
943
+
944
+ expect(
945
+ (
946
+ applyViewModelTextStyles(descriptionContent, pageStyles)
947
+ .descriptionStyle as TextStyleDeclaration
948
+ ).textStyleType,
949
+ ).toBe('paragraph1');
950
+
951
+ // A paragraph linked to the retired `paragraphs` role.
952
+ const paraContent: any = { titleStyle: { textStyleType: 'paragraphs' } };
953
+
954
+ expect(
955
+ (
956
+ applyViewModelTextStyles(paraContent, pageStyles)
957
+ .titleStyle as TextStyleDeclaration
958
+ ).textStyleType,
959
+ ).toBe('paragraph1');
960
+
961
+ // A section button field with no role: every button label starts on Button text.
962
+ const buttonFieldContent: any = { buttonTextStyle: {} };
963
+
964
+ expect(
965
+ (
966
+ applyViewModelTextStyles(buttonFieldContent, pageStyles)
967
+ .buttonTextStyle as TextStyleDeclaration
968
+ ).textStyleType,
969
+ ).toBe('button');
970
+
971
+ // A text item with no role and no size falls to Paragraph 1.
972
+ const textContent: TextContent = {
973
+ backgroundColor: {},
974
+ iconUrl: '',
975
+ id: '0',
976
+ text: 'Test text',
977
+ textItems: [{ id: 'i1', text: 'One', textStyle: {} }],
978
+ textAlignment: {},
979
+ textColor: {},
980
+ textSize: {},
981
+ textStyle: {},
982
+ type: 'text',
983
+ };
984
+ const items = mapToTextViewModel(
985
+ textContent,
986
+ pageStyles,
987
+ BRAND_PAGE_TYPES.brandSite,
988
+ ).textItems as any[];
989
+
990
+ expect(items[0].textStyle.textStyleType).toBe('paragraph1');
991
+ });
992
+
993
+ // S3 — a section's own CTA (banner/booking/images) reuses the SECTION's content type when
994
+ // building its button view model, which `contentFieldReconnectSpec` doesn't recognise as
995
+ // button content; before the fix a heading-sized no-role label landed on a heading tier via
996
+ // `bestFitRole` instead of Button text.
997
+ test('a no-role banner-section button label draws through Button text, not by size', () => {
998
+ const pageStyles = new PageStyles();
999
+ const bannerContent = createInitialBannerSectionContent();
1000
+
1001
+ bannerContent.buttonTextStyle = { fontSize: '2em' };
1002
+
1003
+ const viewModel = mapToBannerSectionViewModel(
1004
+ bannerContent,
1005
+ pageStyles,
1006
+ {},
1007
+ );
1008
+
1009
+ expect(viewModel.buttonViewModel?.titleStyle.textStyleType).toBe(
1010
+ 'button',
1011
+ );
1012
+ });
1013
+ });
1014
+
1015
+ // BP-5969 (R3 revision) — the renderer's built-in typography defaults are deleted outright;
1016
+ // a field with no size/weight of its own carries none in page data, on a role or off it.
1017
+ describe('no built-in typography defaults', () => {
1018
+ test('mailing-list title has no built-in bold or size', () => {
1019
+ const pageStyles = new PageStyles();
1020
+ const contentOnRole: MailingListContent = {
1021
+ id: 'ml-1',
1022
+ type: 'mailing-list',
1023
+ title: 'Subscribe',
1024
+ buttonText: 'Go',
1025
+ titleStyle: { textStyleType: 'heading5' } as TextStyleDeclaration,
1026
+ buttonTextStyle: {},
1027
+ };
1028
+ const contentNoRole: MailingListContent = {
1029
+ ...contentOnRole,
1030
+ titleStyle: {} as TextStyleDeclaration,
1031
+ };
1032
+
1033
+ for (const content of [contentOnRole, contentNoRole]) {
1034
+ const style = mapToMailingListViewModel(content, pageStyles)
1035
+ .titleStyle as TextStyleDeclaration;
1036
+
1037
+ expect(style.fontWeight).toBeUndefined();
1038
+ expect(style.fontSize).toBeUndefined();
1039
+ }
1040
+ });
1041
+
1042
+ test('support-me title has no built-in bold', () => {
1043
+ const pageStyles = new PageStyles();
1044
+ const mapContent = (content: any): any =>
1045
+ mapToViewModel(pageStyles, BRAND_PAGE_TYPES.brandSite, {})(content);
1046
+ const base = {
1047
+ id: 'sm-1',
1048
+ type: 'support-me',
1049
+ title: 'Support me',
1050
+ description: '',
1051
+ buttonText: 'Donate',
1052
+ successMessage: '',
1053
+ items: [],
1054
+ };
1055
+
1056
+ const onRole = mapContent({
1057
+ ...base,
1058
+ titleStyle: { textStyleType: 'heading5' },
1059
+ });
1060
+ const noRole = mapContent({ ...base, titleStyle: {} });
1061
+
1062
+ expect(onRole.titleStyle?.fontWeight).toBeUndefined();
1063
+ expect(noRole.titleStyle?.fontWeight).toBeUndefined();
1064
+ });
1065
+
1066
+ test('text block ignores legacy style siblings', () => {
1067
+ const pageStyles = new PageStyles();
1068
+ const base = (textStyle: TextContent['textStyle']): TextContent => ({
1069
+ backgroundColor: {},
1070
+ iconUrl: '',
1071
+ id: '0',
1072
+ text: 'Test text',
1073
+ textItems: [],
1074
+ textAlignment: { textAlign: 'left' },
1075
+ textColor: { color: '#123456' },
1076
+ textSize: { fontSize: '1.5em', lineHeight: '1.5' },
1077
+ textStyle,
1078
+ type: 'text',
1079
+ });
1080
+
1081
+ for (const textStyle of [{ textStyleType: 'paragraph2' as const }, {}]) {
1082
+ const viewModel = mapToTextViewModel(
1083
+ base(textStyle),
1084
+ pageStyles,
1085
+ BRAND_PAGE_TYPES.brandSite,
1086
+ );
1087
+ const style = viewModel.textStyle as TextStyleDeclaration;
1088
+
1089
+ expect(style.fontSize).toBeUndefined();
1090
+ expect(style.lineHeight).toBeUndefined();
1091
+ expect(style.textAlign).toBe('center');
1092
+ expect(style.color).toBeUndefined();
1093
+ expect(
1094
+ (viewModel as unknown as Record<string, unknown>).textSize,
1095
+ ).toBeUndefined();
1096
+ }
1097
+ });
1098
+ });
661
1099
  });
662
1100
 
663
1101
  // The reCAPTCHA footnote colour is a raw colour anchored to the surface it sits on.
@@ -670,7 +1108,10 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
670
1108
  type: 'mailing-list',
671
1109
  title: 'Subscribe',
672
1110
  buttonText: 'Go',
673
- titleStyle: { textAlign: DEFAULT_TEXT_ALIGN, color: '#ff0000' },
1111
+ titleStyle: {
1112
+ textAlign: DEFAULT_TEXT_ALIGN,
1113
+ color: '#ff0000',
1114
+ } as TextStyleDeclaration,
674
1115
  buttonTextStyle: { textAlign: DEFAULT_TEXT_ALIGN },
675
1116
  });
676
1117
 
@@ -686,7 +1127,7 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
686
1127
  successMessage: '',
687
1128
  fields: [],
688
1129
  responseEmail: '',
689
- titleStyle: { color: '#ff0000' },
1130
+ titleStyle: { color: '#ff0000' } as TextStyleDeclaration,
690
1131
  ...overrides,
691
1132
  });
692
1133
 
@@ -707,7 +1148,9 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
707
1148
 
708
1149
  expect(vm.disclosureColor).toMatch(/^#/);
709
1150
  expect(vm.disclosureColor).not.toContain('var(');
710
- expect(vm.disclosureColor).not.toBe(vm.titleStyle?.color);
1151
+ expect(vm.disclosureColor).not.toBe(
1152
+ (vm.titleStyle as TextStyleDeclaration)?.color,
1153
+ );
711
1154
  });
712
1155
 
713
1156
  test('mailing-list: a raw hex surface fill still yields a muted colour', () => {
@@ -721,7 +1164,9 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
721
1164
  );
722
1165
 
723
1166
  expect(vm.disclosureColor).toMatch(/^#/);
724
- expect(vm.disclosureColor).not.toBe(vm.titleStyle?.color);
1167
+ expect(vm.disclosureColor).not.toBe(
1168
+ (vm.titleStyle as TextStyleDeclaration)?.color,
1169
+ );
725
1170
  });
726
1171
 
727
1172
  test('mailing-list: falls back to the title colour when the surface has no fill', () => {
@@ -730,11 +1175,16 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
730
1175
  pageStyles.buttonStyles.style = {};
731
1176
 
732
1177
  const vm = mapToMailingListViewModel(
733
- createMailingListContent(),
1178
+ {
1179
+ ...createMailingListContent(),
1180
+ title: rteTitleWithColour('#ff0000') as unknown as string,
1181
+ },
734
1182
  pageStyles,
735
1183
  );
736
1184
 
737
- expect(vm.disclosureColor).toBe(vm.titleStyle?.color);
1185
+ // ADR-0020: the title's own colour lives in its RTE mark; with no surface fill the
1186
+ // disclosure falls back to it.
1187
+ expect(vm.disclosureColor).toBe('#ff0000');
738
1188
  expect(vm.disclosureColor).toBeDefined();
739
1189
  });
740
1190
 
@@ -785,7 +1235,9 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
785
1235
 
786
1236
  expect(vm.disclosureColor).toMatch(/^#/);
787
1237
  expect(vm.disclosureColor).not.toContain('var(');
788
- expect(vm.disclosureColor).not.toBe(vm.titleStyle?.color);
1238
+ expect(vm.disclosureColor).not.toBe(
1239
+ (vm.titleStyle as TextStyleDeclaration)?.color,
1240
+ );
789
1241
  });
790
1242
 
791
1243
  test('contact-form (brand.page): anchors to the card colour when the card has a fill', () => {
@@ -808,7 +1260,9 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
808
1260
  // Anchored to the light card, not the dark button fill: a dark muted colour.
809
1261
  expect(vm.disclosureColor).toMatch(/^#/);
810
1262
  expect(vm.disclosureColor).not.toContain('var(');
811
- expect(vm.disclosureColor).not.toBe(vm.titleStyle?.color);
1263
+ expect(vm.disclosureColor).not.toBe(
1264
+ (vm.titleStyle as TextStyleDeclaration)?.color,
1265
+ );
812
1266
  });
813
1267
 
814
1268
  test('contact-form: a raw hex surface fill (pre-V2) still yields a muted colour', () => {
@@ -823,7 +1277,9 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
823
1277
  );
824
1278
 
825
1279
  expect(vm.disclosureColor).toMatch(/^#/);
826
- expect(vm.disclosureColor).not.toBe(vm.titleStyle?.color);
1280
+ expect(vm.disclosureColor).not.toBe(
1281
+ (vm.titleStyle as TextStyleDeclaration)?.color,
1282
+ );
827
1283
  });
828
1284
 
829
1285
  test('contact-form: a blank palette role resolves via role defaults, not the title', () => {
@@ -840,7 +1296,9 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
840
1296
 
841
1297
  expect(vm.disclosureColor).toMatch(/^#/);
842
1298
  expect(vm.disclosureColor).not.toContain('var(');
843
- expect(vm.disclosureColor).not.toBe(vm.titleStyle?.color);
1299
+ expect(vm.disclosureColor).not.toBe(
1300
+ (vm.titleStyle as TextStyleDeclaration)?.color,
1301
+ );
844
1302
  });
845
1303
 
846
1304
  test('contact-form (brandSite, card layout): anchors to the card background', () => {
@@ -862,7 +1320,9 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
862
1320
 
863
1321
  expect(vm.disclosureColor).toMatch(/^#/);
864
1322
  expect(vm.disclosureColor).not.toContain('var(');
865
- expect(vm.disclosureColor).not.toBe(vm.titleStyle?.color);
1323
+ expect(vm.disclosureColor).not.toBe(
1324
+ (vm.titleStyle as TextStyleDeclaration)?.color,
1325
+ );
866
1326
  });
867
1327
 
868
1328
  test('contact-form (brandSite, no layout): falls back to the title colour', () => {
@@ -871,12 +1331,17 @@ describe('reCAPTCHA disclosure colour (view-model layer)', () => {
871
1331
  pageStyles.buttonStyles.style = {};
872
1332
 
873
1333
  const vm = mapToContactFormViewModel(
874
- createContactFormContent({ layoutStyle: '' }),
1334
+ {
1335
+ ...createContactFormContent({ layoutStyle: '' }),
1336
+ title: rteTitleWithColour('#ff0000') as unknown as string,
1337
+ },
875
1338
  pageStyles,
876
1339
  BRAND_PAGE_TYPES.brandSite,
877
1340
  );
878
1341
 
879
- expect(vm.disclosureColor).toBe(vm.titleStyle?.color);
1342
+ // ADR-0020: the title's own colour lives in its RTE mark; with no surface fill the
1343
+ // disclosure falls back to it.
1344
+ expect(vm.disclosureColor).toBe('#ff0000');
880
1345
  expect(vm.disclosureColor).toBeDefined();
881
1346
  });
882
1347
  });
@@ -901,10 +1366,26 @@ describe('container-aware text colour defaults (view-model layer)', () => {
901
1366
  color: BUTTON_TEXT,
902
1367
  };
903
1368
  // Distinct global text-style colours so a fallback to the page is detectable.
904
- pageStyles.textStyles.textStyleOptions.headings.style.color = '#00ff00';
905
- pageStyles.textStyles.textStyleOptions.paragraphs.style.color = '#00ff00';
906
- pageStyles.textStyles.textStyleOptions.titles.style.color = '#00ff00';
907
- pageStyles.textStyles.textStyleOptions.buttons.style.color = '#0000ff';
1369
+ (
1370
+ pageStyles.textStyles.textStyleOptions.headings
1371
+ .style as unknown as Record<string, unknown>
1372
+ ).color = '#00ff00';
1373
+ (
1374
+ pageStyles.textStyles.textStyleOptions.paragraphs
1375
+ .style as unknown as Record<string, unknown>
1376
+ ).color = '#00ff00';
1377
+ (
1378
+ pageStyles.textStyles.textStyleOptions.titles.style as unknown as Record<
1379
+ string,
1380
+ unknown
1381
+ >
1382
+ ).color = '#00ff00';
1383
+ (
1384
+ pageStyles.textStyles.textStyleOptions.buttons.style as unknown as Record<
1385
+ string,
1386
+ unknown
1387
+ >
1388
+ ).color = '#0000ff';
908
1389
 
909
1390
  return pageStyles;
910
1391
  };
@@ -929,8 +1410,8 @@ describe('container-aware text colour defaults (view-model layer)', () => {
929
1410
  stylesWithButton(),
930
1411
  );
931
1412
 
932
- expect(vm.titleStyle?.color).toBe(BUTTON_TEXT);
933
- expect(vm.titleStyle?.color).toContain('var(');
1413
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBe(BUTTON_TEXT);
1414
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toContain('var(');
934
1415
  });
935
1416
 
936
1417
  test('mailing-list: title on a neutral-light card follows its global heading colour, not the button text (BP-5886)', () => {
@@ -948,9 +1429,10 @@ describe('container-aware text colour defaults (view-model layer)', () => {
948
1429
  stylesWithButton(),
949
1430
  );
950
1431
 
951
- // Card ≠ button fill → the button-text default does not apply; the title reads on
952
- // the neutral surface via its Headings global colour.
953
- expect(vm.titleStyle?.color).toBe('#00ff00');
1432
+ // Card ≠ button fill → the button-text default does not apply. ADR-0020: the role's
1433
+ // colour no longer cascades, so the title resolves to undefined and takes the page
1434
+ // base coat (the generalised contrast bake — Phase 2 — will colour non-default surfaces).
1435
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBeUndefined();
954
1436
  });
955
1437
 
956
1438
  test('mailing-list: an explicit title colour still wins over the container default', () => {
@@ -960,16 +1442,19 @@ describe('container-aware text colour defaults (view-model layer)', () => {
960
1442
  type: 'mailing-list',
961
1443
  title: 'Subscribe',
962
1444
  buttonText: 'Go',
963
- titleStyle: { textStyleType: 'headings', color: '#abcabc' },
1445
+ titleStyle: {
1446
+ textStyleType: 'headings',
1447
+ color: '#abcabc',
1448
+ } as TextStyleDeclaration,
964
1449
  backgroundStyle: { backgroundColor: BUTTON_FILL },
965
1450
  } as MailingListContent,
966
1451
  stylesWithButton(),
967
1452
  );
968
1453
 
969
- expect(vm.titleStyle?.color).toBe('#abcabc');
1454
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBe('#abcabc');
970
1455
  });
971
1456
 
972
- test('mailing-list: no visible container falls back to the global text-style colour', () => {
1457
+ test('mailing-list: no visible container and no role colour resolves to undefined (base coat)', () => {
973
1458
  const pageStyles = stylesWithButton();
974
1459
 
975
1460
  pageStyles.buttonStyles.style = {}; // no own container and no button fill
@@ -985,7 +1470,8 @@ describe('container-aware text colour defaults (view-model layer)', () => {
985
1470
  pageStyles,
986
1471
  );
987
1472
 
988
- expect(vm.titleStyle?.color).toBe('#00ff00');
1473
+ // ADR-0020: no container default and the role colour retired → undefined (base coat).
1474
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBeUndefined();
989
1475
  });
990
1476
 
991
1477
  test('mailing-list: with no own container it contrasts the global button fill', () => {
@@ -1000,10 +1486,10 @@ describe('container-aware text colour defaults (view-model layer)', () => {
1000
1486
  stylesWithButton(),
1001
1487
  );
1002
1488
 
1003
- expect(vm.titleStyle?.color).toBe(BUTTON_TEXT);
1489
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBe(BUTTON_TEXT);
1004
1490
  });
1005
1491
 
1006
- test('request-payment: title/description/item text take the container default; a set item textColor still wins', () => {
1492
+ test('request-payment: title/description/success message/item text take the container default; a set item textColor still wins', () => {
1007
1493
  const vm = mapContent(
1008
1494
  {
1009
1495
  id: 'rp-1',
@@ -1011,6 +1497,7 @@ describe('container-aware text colour defaults (view-model layer)', () => {
1011
1497
  title: 'Pay',
1012
1498
  titleStyle: { textStyleType: 'paragraphs' },
1013
1499
  descriptionStyle: { textStyleType: 'paragraphs' },
1500
+ successMessageStyle: { textStyleType: 'paragraphs' },
1014
1501
  itemTextStyle: { textStyleType: 'paragraphs' },
1015
1502
  textColor: { color: '#abcabc' },
1016
1503
  backgroundStyle: { backgroundColor: BUTTON_FILL },
@@ -1019,13 +1506,18 @@ describe('container-aware text colour defaults (view-model layer)', () => {
1019
1506
  stylesWithButton(),
1020
1507
  );
1021
1508
 
1022
- expect(vm.titleStyle?.color).toBe(BUTTON_TEXT);
1023
- expect(vm.descriptionStyle?.color).toBe(BUTTON_TEXT);
1509
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBe(BUTTON_TEXT);
1510
+ expect((vm.descriptionStyle as TextStyleDeclaration)?.color).toBe(
1511
+ BUTTON_TEXT,
1512
+ );
1513
+ expect((vm.successMessageStyle as TextStyleDeclaration)?.color).toBe(
1514
+ BUTTON_TEXT,
1515
+ );
1024
1516
  // itemTextStyle merges content.textColor first, so its explicit colour wins.
1025
- expect(vm.itemTextStyle?.color).toBe('#abcabc');
1517
+ expect((vm.itemTextStyle as TextStyleDeclaration)?.color).toBe('#abcabc');
1026
1518
  });
1027
1519
 
1028
- test('support-me: title/heading/description/item take the container default; button text stays on the buttons global', () => {
1520
+ test('support-me: title/heading/description/success message/item take the container default; button text stays on the buttons global', () => {
1029
1521
  const vm = mapContent(
1030
1522
  {
1031
1523
  id: 'sm-1',
@@ -1035,6 +1527,7 @@ describe('container-aware text colour defaults (view-model layer)', () => {
1035
1527
  titleStyle: { textStyleType: 'headings' },
1036
1528
  headingStyle: { textStyleType: 'paragraphs' },
1037
1529
  descriptionStyle: { textStyleType: 'paragraphs' },
1530
+ successMessageStyle: { textStyleType: 'paragraphs' },
1038
1531
  itemTextStyle: { textStyleType: 'paragraphs' },
1039
1532
  buttonTextStyle: { textStyleType: 'buttons' },
1040
1533
  backgroundStyle: { backgroundColor: BUTTON_FILL },
@@ -1042,12 +1535,18 @@ describe('container-aware text colour defaults (view-model layer)', () => {
1042
1535
  stylesWithButton(),
1043
1536
  );
1044
1537
 
1045
- expect(vm.titleStyle?.color).toBe(BUTTON_TEXT);
1046
- expect(vm.headingStyle?.color).toBe(BUTTON_TEXT);
1047
- expect(vm.descriptionStyle?.color).toBe(BUTTON_TEXT);
1048
- expect(vm.itemTextStyle?.color).toBe(BUTTON_TEXT);
1049
- // buttonTextStyle is NOT container-bound → follows the buttons global colour.
1050
- expect(vm.buttonTextStyle?.color).toBe('#0000ff');
1538
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBe(BUTTON_TEXT);
1539
+ expect((vm.headingStyle as TextStyleDeclaration)?.color).toBe(BUTTON_TEXT);
1540
+ expect((vm.descriptionStyle as TextStyleDeclaration)?.color).toBe(
1541
+ BUTTON_TEXT,
1542
+ );
1543
+ expect((vm.successMessageStyle as TextStyleDeclaration)?.color).toBe(
1544
+ BUTTON_TEXT,
1545
+ );
1546
+ expect((vm.itemTextStyle as TextStyleDeclaration)?.color).toBe(BUTTON_TEXT);
1547
+ // buttonTextStyle is NOT container-bound and ADR-0020 retires the role colour →
1548
+ // it resolves to undefined (the page base coat).
1549
+ expect((vm.buttonTextStyle as TextStyleDeclaration)?.color).toBeUndefined();
1051
1550
  });
1052
1551
 
1053
1552
  test('contact-form (brand.page): title/description/success message take the container default', () => {
@@ -1072,13 +1571,18 @@ describe('container-aware text colour defaults (view-model layer)', () => {
1072
1571
  BRAND_PAGE_TYPES.default,
1073
1572
  );
1074
1573
 
1075
- expect(vm.titleStyle?.color).toBe(BUTTON_TEXT);
1076
- expect(vm.descriptionStyle?.color).toBe(BUTTON_TEXT);
1077
- expect(vm.successMessageStyle?.color).toBe(BUTTON_TEXT);
1078
- expect(vm.buttonTextStyle?.color).toBe('#0000ff');
1574
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBe(BUTTON_TEXT);
1575
+ expect((vm.descriptionStyle as TextStyleDeclaration)?.color).toBe(
1576
+ BUTTON_TEXT,
1577
+ );
1578
+ expect((vm.successMessageStyle as TextStyleDeclaration)?.color).toBe(
1579
+ BUTTON_TEXT,
1580
+ );
1581
+ // ADR-0020: buttonTextStyle (not container-bound) takes no role colour → undefined.
1582
+ expect((vm.buttonTextStyle as TextStyleDeclaration)?.color).toBeUndefined();
1079
1583
  });
1080
1584
 
1081
- test('contact-form (brandSite): container default is out of scope; success message still resolves from the paragraphs global', () => {
1585
+ test('contact-form (brandSite): container default out of scope; link-only fields resolve to undefined (role colour retired)', () => {
1082
1586
  const vm = mapToContactFormViewModel(
1083
1587
  {
1084
1588
  id: 'cf-1',
@@ -1099,10 +1603,13 @@ describe('container-aware text colour defaults (view-model layer)', () => {
1099
1603
  BRAND_PAGE_TYPES.brandSite,
1100
1604
  );
1101
1605
 
1102
- expect(vm.titleStyle?.color).toBe('#00ff00');
1103
- // Phase 1: successMessageStyle now resolves (it was skipped before), and on
1104
- // brandSite the container default is out of scope so it takes the paragraphs global.
1105
- expect(vm.successMessageStyle?.color).toBe('#00ff00');
1606
+ // ADR-0020: role colour retired → link-only fields resolve to undefined (base coat).
1607
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBeUndefined();
1608
+ // successMessageStyle resolves (it was skipped before); on brandSite the container
1609
+ // default is out of scope and the role colour no longer cascades → undefined.
1610
+ expect(
1611
+ (vm.successMessageStyle as TextStyleDeclaration)?.color,
1612
+ ).toBeUndefined();
1106
1613
  });
1107
1614
 
1108
1615
  test('regression: a content type with no container-bound keys is unaffected by the container default', () => {
@@ -1116,8 +1623,9 @@ describe('container-aware text colour defaults (view-model layer)', () => {
1116
1623
  stylesWithButton(),
1117
1624
  );
1118
1625
 
1119
- expect(vm.titleStyle?.color).toBe('#00ff00');
1120
- expect(vm.nameStyle?.color).toBe('#00ff00');
1626
+ // ADR-0020: role colour retired → link-only fields resolve to undefined (base coat).
1627
+ expect((vm.titleStyle as TextStyleDeclaration)?.color).toBeUndefined();
1628
+ expect(vm.nameStyle?.color).toBeUndefined();
1121
1629
  });
1122
1630
  });
1123
1631
 
@@ -1230,7 +1738,10 @@ describe('mapToPageHeaderViewModel header button', () => {
1230
1738
  );
1231
1739
  });
1232
1740
 
1233
- test('resolves the button text style against the site text styles', () => {
1741
+ // R1: page data carries only the label's own typography; the retired `buttons` role's
1742
+ // font family isn't copied in — the fixed successor role (`button`) supplies it at render,
1743
+ // through the CSS var the role class references, not this object.
1744
+ test('resolves the button role, not a copied font family, against the site text styles', () => {
1234
1745
  const styles = new PageStyles();
1235
1746
 
1236
1747
  styles.textStyles.textStyleOptions.buttons.style.fontFamily = 'Lato';
@@ -1241,16 +1752,16 @@ describe('mapToPageHeaderViewModel header button', () => {
1241
1752
  [],
1242
1753
  ).headerButtonViewModel;
1243
1754
 
1244
- expect(vm?.titleStyle?.fontFamily).toBe('Lato');
1755
+ expect(vm?.titleStyle?.fontFamily).toBeUndefined();
1756
+ expect(vm?.titleStyle?.textStyleType).toBe('button');
1245
1757
  });
1246
1758
 
1247
1759
  // The maker writes buttonTextStyle only once the author touches the style field,
1248
- // so an untouched button still has to inherit the site's button text style.
1249
- test('defaults to the site button text style when none is authored', () => {
1760
+ // so an untouched button still has to inherit the site's button role — its role, not a
1761
+ // copied value (R1).
1762
+ test('defaults to the site button role when none is authored', () => {
1250
1763
  const styles = new PageStyles();
1251
1764
 
1252
- styles.textStyles.textStyleOptions.buttons.style.fontFamily = 'Lato';
1253
-
1254
1765
  const vm = mapToPageHeaderViewModel(
1255
1766
  new PageHeaderDesktop({
1256
1767
  headerButton: { ...headerButton, buttonTextStyle: undefined },
@@ -1259,8 +1770,318 @@ describe('mapToPageHeaderViewModel header button', () => {
1259
1770
  [],
1260
1771
  ).headerButtonViewModel;
1261
1772
 
1262
- expect(vm?.titleStyle?.textStyleType).toBe('buttons');
1263
- expect(vm?.titleStyle?.fontFamily).toBe('Lato');
1773
+ expect(vm?.titleStyle?.textStyleType).toBe('button');
1774
+ expect(vm?.titleStyle?.fontFamily).toBeUndefined();
1775
+ });
1776
+
1777
+ test('a header button carries no role font family in page data; its own override holds', () => {
1778
+ const styles = new PageStyles();
1779
+ const mapWith = (buttonTextStyle: TextStyleDeclaration) =>
1780
+ mapToPageHeaderViewModel(
1781
+ new PageHeaderDesktop({
1782
+ headerButton: { ...headerButton, buttonTextStyle },
1783
+ }),
1784
+ styles,
1785
+ [],
1786
+ ).headerButtonViewModel;
1787
+
1788
+ styles.textStyles.textStyleOptions.button.style.fontFamily = 'Oswald';
1789
+
1790
+ // R1: a role edit repaints the label at render, through the CSS var the role class
1791
+ // supplies — page data itself never carries the role's font family.
1792
+ expect(
1793
+ mapWith({ textStyleType: 'button' })?.titleStyle?.fontFamily,
1794
+ ).toBeUndefined();
1795
+ expect(
1796
+ mapWith({ textStyleType: 'button', fontFamily: 'Lora' })?.titleStyle
1797
+ ?.fontFamily,
1798
+ ).toBe('Lora');
1799
+ });
1800
+
1801
+ // R3: no built-in line-height default — on a role or off it, an unset label carries no
1802
+ // line height in page data; it follows the role (real, or the button fallback) at render.
1803
+ describe('label line height', () => {
1804
+ const mapWith = (
1805
+ buttonTextStyle: HeaderButton['buttonTextStyle'],
1806
+ styles = new PageStyles(),
1807
+ ) =>
1808
+ mapToPageHeaderViewModel(
1809
+ new PageHeaderDesktop({
1810
+ headerButton: { ...headerButton, buttonTextStyle },
1811
+ }),
1812
+ styles,
1813
+ [],
1814
+ ).headerButtonViewModel?.titleStyle;
1815
+
1816
+ test.each(['button', 'heading1'] as const)(
1817
+ 'an unset label on %s carries no line height in page data',
1818
+ (textStyleType) => {
1819
+ const styles = new PageStyles();
1820
+
1821
+ styles.textStyles.textStyleOptions[textStyleType].style.lineHeight =
1822
+ '1.15';
1823
+
1824
+ expect(
1825
+ mapWith({ textStyleType }, styles)?.lineHeight,
1826
+ ).toBeUndefined();
1827
+ },
1828
+ );
1829
+
1830
+ test('the label own line height beats the role', () => {
1831
+ expect(
1832
+ mapWith({ textStyleType: 'heading1', lineHeight: '1.5' })?.lineHeight,
1833
+ ).toBe('1.5');
1834
+ });
1835
+
1836
+ test('an unset label on no role has no built-in default; it falls back to Button text', () => {
1837
+ const titleStyle = mapWith({ textAlign: 'center' });
1838
+
1839
+ expect(titleStyle?.lineHeight).toBeUndefined();
1840
+ expect(titleStyle?.textStyleType).toBe('button');
1841
+ });
1264
1842
  });
1265
1843
  });
1266
1844
  });
1845
+
1846
+ describe('save contact button text style', () => {
1847
+ test('with no field of its own, the Save Contact label carries no typography — only the Button text role', () => {
1848
+ const styles = new PageStyles();
1849
+
1850
+ const resolved = resolveSaveContactTitleStyle(undefined, styles.textStyles);
1851
+
1852
+ expect(resolved.textStyleType).toBe('button');
1853
+ expect(resolved.fontFamily).toBeUndefined();
1854
+ expect(resolved.fontSize).toBeUndefined();
1855
+ });
1856
+
1857
+ test('the label always resolves to the Button text role, including when the field names the retired Buttons role', () => {
1858
+ const styles = new PageStyles();
1859
+
1860
+ expect(
1861
+ resolveSaveContactTitleStyle(
1862
+ { textStyleType: 'button' },
1863
+ styles.textStyles,
1864
+ ).textStyleType,
1865
+ ).toBe('button');
1866
+ expect(
1867
+ resolveSaveContactTitleStyle(
1868
+ { textStyleType: 'buttons' as never },
1869
+ styles.textStyles,
1870
+ ).textStyleType,
1871
+ ).toBe('button');
1872
+ });
1873
+
1874
+ // S8 — the field naming a DIFFERENT canonical role (not just the retired `buttons`) must
1875
+ // still normalise to Button text; Save Contact never draws through any other role.
1876
+ test('a stray canonical role on the field is still normalised to Button text', () => {
1877
+ const styles = new PageStyles();
1878
+
1879
+ expect(
1880
+ resolveSaveContactTitleStyle(
1881
+ { textStyleType: 'heading2' as never },
1882
+ styles.textStyles,
1883
+ ).textStyleType,
1884
+ ).toBe('button');
1885
+ });
1886
+
1887
+ test('a save contact override holds; a Button text role edit is not copied in', () => {
1888
+ const styles = new PageStyles();
1889
+
1890
+ styles.textStyles.textStyleOptions.button.style.fontFamily = 'Oswald';
1891
+
1892
+ const resolved = resolveSaveContactTitleStyle(
1893
+ { textStyleType: 'button', fontSize: '1.25em', lineHeight: '1.5' },
1894
+ styles.textStyles,
1895
+ );
1896
+
1897
+ expect(resolved.fontSize).toBe('1.25em');
1898
+ expect(resolved.lineHeight).toBe('1.5');
1899
+ expect(resolved.fontFamily).toBeUndefined();
1900
+ expect(resolved.textStyleType).toBe('button');
1901
+ });
1902
+
1903
+ test('the page view model passes the save contact buttonTextStyle through', () => {
1904
+ const buttonTextStyle: TextStyleDeclaration = {
1905
+ textStyleType: 'button',
1906
+ fontSize: '1.25em',
1907
+ };
1908
+ const model = new BrandPageModel({
1909
+ brandPageType: BRAND_PAGE_TYPES.brandContact,
1910
+ saveContactSettings: {
1911
+ isSavingEnabled: true,
1912
+ hideBranding: false,
1913
+ buttonTextStyle,
1914
+ },
1915
+ });
1916
+
1917
+ const viewModel = getBrandPageViewModelFromBrandPageModel(
1918
+ model,
1919
+ undefined,
1920
+ true,
1921
+ {} as Parameters<typeof getBrandPageViewModelFromBrandPageModel>[3],
1922
+ );
1923
+
1924
+ expect(viewModel.saveContactSettings?.buttonTextStyle).toEqual(
1925
+ buttonTextStyle,
1926
+ );
1927
+ });
1928
+ });
1929
+
1930
+ // Header and footer text is rebuilt through the model constructors on every load, so the
1931
+ // render is checked after a round trip through `new BrandPageModel`, the way the published
1932
+ // page and the maker both load a saved site.
1933
+ describe('header and footer text follow their role after a reload', () => {
1934
+ const load = (
1935
+ navStyle: TextStyleDeclaration,
1936
+ hamburgerStyle: TextStyleDeclaration,
1937
+ copyrightStyle: TextStyleDeclaration = {
1938
+ textStyleType: 'paragraph1',
1939
+ textAlign: 'center',
1940
+ },
1941
+ ) => {
1942
+ const model = new BrandPageModel({
1943
+ pageHeaderDesktop: {
1944
+ displayStyle: HEADER_DISPLAY.DESKTOP_TAB_5,
1945
+ navigation: {
1946
+ displayType: NAVIGATION_DISPLAY.TAB_1,
1947
+ defaultButtonStyle: navStyle,
1948
+ hamburgerMenuStyle: { defaultButtonStyle: hamburgerStyle },
1949
+ },
1950
+ },
1951
+ pageFooterDesktop: {
1952
+ copyrightText: '© Acme',
1953
+ copyrightStyle,
1954
+ },
1955
+ } as never);
1956
+
1957
+ const styles = new PageStyles();
1958
+ const roles = styles.textStyles.textStyleOptions;
1959
+
1960
+ roles.paragraph1.style = {
1961
+ ...roles.paragraph1.style,
1962
+ fontFamily: 'Anton',
1963
+ fontSize: '1.5em',
1964
+ fontWeight: 'bold',
1965
+ };
1966
+ roles.button.style = { ...roles.button.style, fontFamily: 'Oswald' };
1967
+
1968
+ return {
1969
+ header: mapToPageHeaderViewModel(model.pageHeaderDesktop, styles, []),
1970
+ footer: mapToPageFooterViewModel(model.pageFooterDesktop, styles),
1971
+ };
1972
+ };
1973
+
1974
+ test('a linked nav tab and hamburger keep their role and carry no role typography', () => {
1975
+ const { header } = load(
1976
+ { textStyleType: 'paragraph1' },
1977
+ { textStyleType: 'button' },
1978
+ );
1979
+
1980
+ expect(header.navigation.defaultButtonStyle.textStyleType).toBe(
1981
+ 'paragraph1',
1982
+ );
1983
+ expect(header.navigation.defaultButtonStyle.fontFamily).toBeUndefined();
1984
+ expect(header.navigation.defaultButtonStyle.fontSize).toBeUndefined();
1985
+ expect(
1986
+ header.navigation.hamburgerMenuStyle.defaultButtonStyle.textStyleType,
1987
+ ).toBe('button');
1988
+ expect(
1989
+ header.navigation.hamburgerMenuStyle.defaultButtonStyle.fontFamily,
1990
+ ).toBeUndefined();
1991
+ });
1992
+
1993
+ test('a linked nav tab keeps its own override and no role typography for the rest', () => {
1994
+ const { header } = load(
1995
+ { textStyleType: 'paragraph1', fontSize: '1em' },
1996
+ { textStyleType: 'button' },
1997
+ );
1998
+
1999
+ expect(header.navigation.defaultButtonStyle).toMatchObject({
2000
+ textStyleType: 'paragraph1',
2001
+ fontSize: '1em',
2002
+ });
2003
+ expect(header.navigation.defaultButtonStyle.fontFamily).toBeUndefined();
2004
+ });
2005
+
2006
+ test('a linked nav tab keeps a fallback link colour when its own is cleared', () => {
2007
+ const { header } = load(
2008
+ { textStyleType: 'paragraph1', color: '' },
2009
+ { textStyleType: 'button' },
2010
+ );
2011
+
2012
+ expect(header.navigation.defaultButtonStyle.color).toBe(
2013
+ FALLBACK_DEFAULT_LINK_COLOR,
2014
+ );
2015
+ });
2016
+
2017
+ // S3 — nav tabs and the hamburger take an explicit starting role now (matching what the
2018
+ // upgrade assigns them), not `bestFitRole` — so the model's own default size no longer needs
2019
+ // clearing to see the true no-role fallback; it stays as an override either way.
2020
+ test('a tab on no role draws through Paragraph 1 regardless of its own size; a hamburger on no role draws through Button text', () => {
2021
+ const { header } = load({}, {});
2022
+
2023
+ expect(header.navigation.defaultButtonStyle.textStyleType).toBe(
2024
+ 'paragraph1',
2025
+ );
2026
+ expect(header.navigation.defaultButtonStyle.fontSize).toBe(FONT_SIZE_BODY);
2027
+ expect(
2028
+ header.navigation.hamburgerMenuStyle.defaultButtonStyle.textStyleType,
2029
+ ).toBe('button');
2030
+ });
2031
+
2032
+ test('the footer copyright takes its role', () => {
2033
+ const { footer } = load(
2034
+ { textStyleType: 'paragraph1' },
2035
+ { textStyleType: 'button' },
2036
+ { textStyleType: 'paragraph2', textAlign: 'center', color: '#111111' },
2037
+ );
2038
+
2039
+ expect(footer.copyrightStyle).toMatchObject({
2040
+ textStyleType: 'paragraph2',
2041
+ textAlign: 'center',
2042
+ color: '#111111',
2043
+ overflowWrap: 'anywhere',
2044
+ wordBreak: 'normal',
2045
+ });
2046
+ expect(footer.copyrightStyle.fontFamily).toBeUndefined();
2047
+ expect(footer.copyrightStyle.fontSize).toBeUndefined();
2048
+
2049
+ const { footer: noRoleFooter } = load(
2050
+ { textStyleType: 'paragraph1' },
2051
+ { textStyleType: 'button' },
2052
+ {},
2053
+ );
2054
+
2055
+ expect(noRoleFooter.copyrightStyle.textStyleType).toBe('paragraph1');
2056
+ });
2057
+ });
2058
+
2059
+ describe('getCriticalResourceURLs', () => {
2060
+ const SHAPE_URL = 'https://cdn.example.com/shape.svg';
2061
+
2062
+ const viewModelWith = (
2063
+ textStyles: Record<string, unknown>,
2064
+ ): BrandPageViewModel =>
2065
+ ({
2066
+ styles: {
2067
+ textStyles,
2068
+ backgroundStylesViewModel: { backgroundImageUrl: SHAPE_URL },
2069
+ },
2070
+ }) as unknown as BrandPageViewModel;
2071
+
2072
+ // The renderer layout already preloads every font (0036-retire-text-font-family.md).
2073
+ test.each([
2074
+ ['a stale font key', { textFontFamily: 'quattrocentoSans' }],
2075
+ ['a stale display name', { textFontFamily: 'Anton SC' }],
2076
+ ['no site text font', {}],
2077
+ ])(
2078
+ 'preloads the background shape and no font, given %s',
2079
+ (_label, textStyles) => {
2080
+ expect(getCriticalResourceURLs(viewModelWith(textStyles))).toEqual({
2081
+ images: [],
2082
+ fonts: [],
2083
+ shapes: [SHAPE_URL],
2084
+ });
2085
+ },
2086
+ );
2087
+ });