@designcrowd/library.brand-page 5.18.4 → 5.18.5

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 (162) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DQLtpdLe.js → CreatedOnBrandCrowd-de-de-ciKpCAd9.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CvEhlADd.js → CreatedOnBrandCrowd-en-us-CZbTS1AZ.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-pYQeTTC0.js → CreatedOnBrandCrowd-es-es-C8MExng5.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CcE-iCEq.js → CreatedOnBrandCrowd-fr-ca-CO0L9NK2.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CcE-iCEq.js → CreatedOnBrandCrowd-fr-fr-CO0L9NK2.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-OyhWWWyO.js → CreatedOnBrandCrowd-pt-br-zVDvBF2H.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-OyhWWWyO.js → CreatedOnBrandCrowd-pt-pt-zVDvBF2H.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-DH98FFLS.js → CreatedOnDcom-de-de-WrUSQdjf.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DK67NN1r.js → CreatedOnDcom-en-us-BqC9YtkL.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-9-QU4BU6.js → CreatedOnDcom-es-es-BzH32Oya.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-U9J9H9VB.js → CreatedOnDcom-fr-ca-D7TYfCW0.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DqdzB1DJ.js → CreatedOnDcom-fr-fr-DVlyfBtb.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-c4UiWtkB.js → CreatedOnDcom-pt-br-CSRB91ey.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-Do2qJsYp.js → CreatedOnDcom-pt-pt-9KMj5gpc.js} +1 -1
  15. package/lib/src/brand-page/models/navigation-styles.model.d.ts +7 -0
  16. package/lib/src/brand-page/models/navigation-styles.model.js +25 -2
  17. package/lib/src/brand-page/utils/migration/migration-16.d.ts +2 -0
  18. package/lib/src/brand-page/utils/migration/migration-16.js +47 -0
  19. package/lib/src/brand-page/utils/migration.util.js +7 -0
  20. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +7 -2
  21. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +7 -2
  22. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +8 -0
  23. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +7 -2
  24. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.js +1 -0
  25. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +87 -2
  26. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.js +5 -0
  27. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +7 -2
  28. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +3 -0
  29. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.js +11 -0
  30. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +7 -2
  31. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +7 -2
  32. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +7 -2
  33. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +7 -2
  34. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +9 -0
  35. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +20 -1
  36. package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +20 -0
  37. package/lib/src/brand-page-maker/composables/useEventTracker.js +17 -0
  38. package/lib/src/brand-page-maker/composables/useUndoRedoFlag.d.ts +17 -0
  39. package/lib/src/brand-page-maker/composables/useUndoRedoFlag.js +25 -0
  40. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +41 -2
  41. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +308 -14
  42. package/lib/src/brand-page-maker/stores/history/history-store.d.ts +83 -0
  43. package/lib/src/brand-page-maker/stores/history/history-store.js +495 -0
  44. package/lib/src/brand-page-maker/stores/logo-store/logo-store.d.ts +1 -0
  45. package/lib/src/brand-page-maker/stores/user/user-store.js +1 -0
  46. package/lib/src/bundled-translations-D1q3rSEz.js +175 -0
  47. package/lib/src/bundled-translations.de-DE-BxqLLwbh.js +175 -0
  48. package/lib/src/bundled-translations.es-ES-uh249NlJ.js +175 -0
  49. package/lib/src/bundled-translations.fr-CA-BEQmv2fX.js +175 -0
  50. package/lib/src/bundled-translations.fr-FR-BnNuCojH.js +175 -0
  51. package/lib/src/bundled-translations.pt-BR-CJUOxVlq.js +175 -0
  52. package/lib/src/bundled-translations.pt-PT-Ds9cMEqK.js +175 -0
  53. package/lib/src/bundles/bundled-translations.de-DE.json +3 -1
  54. package/lib/src/bundles/bundled-translations.es-ES.json +3 -1
  55. package/lib/src/bundles/bundled-translations.fr-CA.json +3 -1
  56. package/lib/src/bundles/bundled-translations.fr-FR.json +3 -1
  57. package/lib/src/bundles/bundled-translations.json +3 -1
  58. package/lib/src/bundles/bundled-translations.pt-BR.json +3 -1
  59. package/lib/src/bundles/bundled-translations.pt-PT.json +3 -1
  60. package/lib/src/constants/ui.d.ts +1 -0
  61. package/lib/src/css/library.brand-page-brandCrowd.min.css +77 -82
  62. package/lib/src/css/library.brand-page-brandPage.min.css +77 -83
  63. package/lib/src/css/library.brand-page-designCom.min.css +77 -82
  64. package/lib/src/css/library.brand-page-preview.min.css +77 -82
  65. package/lib/src/index.mjs +62 -60
  66. package/lib/src/{lib-BI3J4Mbk.js → lib-BXbo_N88.js} +35072 -34447
  67. package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +13 -0
  68. package/lib/src/metadata-builder/builders/reorder-pages-builder.js +49 -0
  69. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +87 -2
  70. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +5 -0
  71. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.stories.js +1 -0
  72. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +23 -2
  73. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +16 -0
  74. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +7 -2
  75. package/lib/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.d.ts +3 -0
  76. package/lib/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.js +19 -2
  77. package/lib/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.js +18 -8
  78. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +64 -0
  79. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.js +6 -0
  80. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +19 -0
  81. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.js +11 -0
  82. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +10 -2
  83. package/lib/src/shared/logo-settings/store/editable-logo-store.d.ts +5 -1
  84. package/lib/src/shared/logo-settings/store/editable-logo-store.js +31 -0
  85. package/lib/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.js +10 -8
  86. package/lib/src/shared/navigation-style-settings/store/index.js +1 -12
  87. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +24 -2
  88. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +10 -2
  89. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +23 -2
  90. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +16 -0
  91. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +23 -2
  92. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +16 -0
  93. package/package.json +1 -1
  94. package/src/brand-page/components/button/Button.vue +2 -10
  95. package/src/brand-page/components/contact-form/ContactForm.vue +2 -2
  96. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.vue +4 -1
  97. package/src/brand-page/models/navigation-styles.model.ts +39 -1
  98. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
  99. package/src/brand-page/utils/migration/migration-16.test.ts +319 -0
  100. package/src/brand-page/utils/migration/migration-16.ts +75 -0
  101. package/src/brand-page/utils/migration.util.ts +8 -0
  102. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +9 -0
  103. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.vue +2 -3
  104. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +1 -0
  105. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.ts +5 -0
  106. package/src/brand-page-maker/components/color-palette/BpColorPalette.vue +9 -7
  107. package/src/brand-page-maker/components/cookie-collection/CookieSettings.vue +18 -17
  108. package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.ts +13 -0
  109. package/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.vue +26 -3
  110. package/src/brand-page-maker/components/quick-link-bar/RedoButton.vue +66 -0
  111. package/src/brand-page-maker/components/quick-link-bar/UndoButton.vue +66 -0
  112. package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.de-DE.json +6 -4
  113. package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.es-ES.json +6 -4
  114. package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.fr-CA.json +6 -4
  115. package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.fr-FR.json +6 -4
  116. package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.json +3 -1
  117. package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.pt-BR.json +6 -4
  118. package/src/brand-page-maker/components/quick-link-bar/i18n/quick-link-bar.pt-PT.json +6 -4
  119. package/src/brand-page-maker/components/quick-link-bar/undo-redo-buttons.test.ts +116 -0
  120. package/src/brand-page-maker/components/save-contact/SaveContact.vue +12 -19
  121. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +10 -0
  122. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +24 -1
  123. package/src/brand-page-maker/composables/useEventTracker.ts +29 -0
  124. package/src/brand-page-maker/composables/useUndoRedoFlag.ts +30 -0
  125. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +377 -0
  126. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +371 -18
  127. package/src/brand-page-maker/stores/history/history-store.test.ts +1120 -0
  128. package/src/brand-page-maker/stores/history/history-store.ts +660 -0
  129. package/src/brand-page-maker/stores/logo-store/logo-store.ts +5 -0
  130. package/src/brand-page-maker/stores/user/user-store.ts +1 -0
  131. package/src/bundles/bundled-translations.de-DE.json +3 -1
  132. package/src/bundles/bundled-translations.es-ES.json +3 -1
  133. package/src/bundles/bundled-translations.fr-CA.json +3 -1
  134. package/src/bundles/bundled-translations.fr-FR.json +3 -1
  135. package/src/bundles/bundled-translations.json +3 -1
  136. package/src/bundles/bundled-translations.pt-BR.json +3 -1
  137. package/src/bundles/bundled-translations.pt-PT.json +3 -1
  138. package/src/constants/ui.ts +1 -0
  139. package/src/metadata-builder/builders/reorder-pages-builder.ts +80 -0
  140. package/src/shared/content-settings/components/color-field/ColorField.vue +10 -5
  141. package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +5 -0
  142. package/src/shared/content-settings/components/content-settings/ContentSettings.stories.ts +1 -0
  143. package/src/shared/content-settings/components/draggable-item-list/DraggableItemList.mixin.ts +19 -2
  144. package/src/shared/content-settings/components/draggable-item-list/DraggableItemList.vue +1 -1
  145. package/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.ts +26 -10
  146. package/src/shared/content-settings/components/logo-selection-field/LogoSelection.vue +63 -48
  147. package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +4 -0
  148. package/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.ts +7 -0
  149. package/src/shared/content-settings/components/text-field/TextField.vue +1 -0
  150. package/src/shared/content-settings/components/text-image-field/TextImageField.vue +33 -4
  151. package/src/shared/logo-settings/components/EditLogoSettings.mixin.ts +11 -0
  152. package/src/shared/logo-settings/store/editable-logo-store.ts +42 -1
  153. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.ts +17 -11
  154. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.vue +4 -1
  155. package/src/shared/navigation-style-settings/store/index.ts +1 -21
  156. package/lib/src/bundled-translations-B5Fkt3qd.js +0 -175
  157. package/lib/src/bundled-translations.de-DE-DpdVePM9.js +0 -175
  158. package/lib/src/bundled-translations.es-ES-BsIcrzZF.js +0 -175
  159. package/lib/src/bundled-translations.fr-CA-G1XCp2Qh.js +0 -175
  160. package/lib/src/bundled-translations.fr-FR-BdwLXOnB.js +0 -175
  161. package/lib/src/bundled-translations.pt-BR-Bvn5ePe7.js +0 -175
  162. package/lib/src/bundled-translations.pt-PT-CDtkb2k1.js +0 -175
@@ -0,0 +1,319 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { migration16 } from './migration-16';
3
+ import type { MigrationData, MigrationOptions } from '../migration.util';
4
+ import { STYLE_THEME_KEYS, NAVIGATION_DISPLAY } from '../../constants';
5
+ import { NavigationDesktop } from '../../models';
6
+
7
+ const createMigrationData = (brandPageModel: any): MigrationData => ({
8
+ brandPageModel,
9
+ useOldLogoRenderSize: false,
10
+ });
11
+
12
+ // The sub nav default is applied when content is first materialised — a template
13
+ // load, the create-draft flow, or the admin maker (isUserDraft === false). An
14
+ // existing user draft already carries its resolved value, so it is skipped.
15
+ const APPLY_DEFAULTS: MigrationOptions = {
16
+ isUserDraft: false,
17
+ isServerRender: false,
18
+ };
19
+ const EXISTING_DRAFT_SKIP: MigrationOptions = {
20
+ isUserDraft: true,
21
+ isServerRender: false,
22
+ };
23
+
24
+ const desktopNav = (displayType: string, overrides: any = {}) => ({
25
+ pageHeaderDesktop: {
26
+ navigation: {
27
+ displayType,
28
+ defaultButtonStyle: {},
29
+ hamburgerMenuStyle: { defaultButtonStyle: {} },
30
+ ...overrides,
31
+ },
32
+ },
33
+ });
34
+
35
+ describe('migration16', () => {
36
+ test('sets color matched sub nav bg for TAB_2 using the top-level styles', async () => {
37
+ const model = desktopNav(NAVIGATION_DISPLAY.TAB_2, {
38
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
39
+ });
40
+
41
+ const result = await migration16(
42
+ createMigrationData(model),
43
+ APPLY_DEFAULTS,
44
+ );
45
+
46
+ expect(
47
+ result.brandPageModel.pageHeaderDesktop.navigation.subNavBackgroundColor,
48
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
49
+ });
50
+
51
+ test('sets color matched sub nav bg on hamburgerMenuStyle when unset (vice versa: logo bg text -> logo text sub nav)', async () => {
52
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
53
+ hamburgerMenuStyle: {
54
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoBackgroundColor },
55
+ },
56
+ });
57
+
58
+ const result = await migration16(
59
+ createMigrationData(model),
60
+ APPLY_DEFAULTS,
61
+ );
62
+
63
+ const nav = result.brandPageModel.pageHeaderDesktop.navigation;
64
+
65
+ expect(nav.hamburgerMenuStyle.subNavBackgroundColor).toBe(
66
+ STYLE_THEME_KEYS.LogoTextColor,
67
+ );
68
+ // top-level value untouched for hamburger
69
+ expect(nav.subNavBackgroundColor).toBeUndefined();
70
+ });
71
+
72
+ test('preserves an explicit hex hamburger sub nav even when the text is color matched (admin choice)', async () => {
73
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
74
+ hamburgerMenuStyle: {
75
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
76
+ subNavBackgroundColor: 'rgb(10,20,30)',
77
+ },
78
+ });
79
+
80
+ const result = await migration16(
81
+ createMigrationData(model),
82
+ APPLY_DEFAULTS,
83
+ );
84
+
85
+ expect(
86
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
87
+ .subNavBackgroundColor,
88
+ ).toBe('rgb(10,20,30)');
89
+ });
90
+
91
+ test('color matches an empty-string hamburger sub nav when the text is color matched', async () => {
92
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
93
+ hamburgerMenuStyle: {
94
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
95
+ subNavBackgroundColor: '',
96
+ },
97
+ });
98
+
99
+ const result = await migration16(
100
+ createMigrationData(model),
101
+ APPLY_DEFAULTS,
102
+ );
103
+
104
+ expect(
105
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
106
+ .subNavBackgroundColor,
107
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
108
+ });
109
+
110
+ test('color matches a transparent hamburger sub nav when the text is color matched', async () => {
111
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
112
+ hamburgerMenuStyle: {
113
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
114
+ subNavBackgroundColor: 'transparent',
115
+ },
116
+ });
117
+
118
+ const result = await migration16(
119
+ createMigrationData(model),
120
+ APPLY_DEFAULTS,
121
+ );
122
+
123
+ expect(
124
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
125
+ .subNavBackgroundColor,
126
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
127
+ });
128
+
129
+ test('leaves an empty hamburger sub nav untouched when text color is not color matched', async () => {
130
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
131
+ hamburgerMenuStyle: {
132
+ defaultButtonStyle: { color: 'rgb(50,50,50)' },
133
+ subNavBackgroundColor: '',
134
+ },
135
+ });
136
+
137
+ const result = await migration16(
138
+ createMigrationData(model),
139
+ APPLY_DEFAULTS,
140
+ );
141
+
142
+ expect(
143
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
144
+ .subNavBackgroundColor,
145
+ ).toBe('');
146
+ });
147
+
148
+ test('leaves hamburger sub nav unset when default text color is not color matched', async () => {
149
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_2, {
150
+ hamburgerMenuStyle: {
151
+ defaultButtonStyle: { color: 'rgb(50,50,50)' },
152
+ },
153
+ });
154
+
155
+ const result = await migration16(
156
+ createMigrationData(model),
157
+ APPLY_DEFAULTS,
158
+ );
159
+
160
+ expect(
161
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
162
+ .subNavBackgroundColor,
163
+ ).toBeUndefined();
164
+ });
165
+
166
+ test('keeps an already color matched value', async () => {
167
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
168
+ hamburgerMenuStyle: {
169
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
170
+ subNavBackgroundColor: STYLE_THEME_KEYS.LogoBackgroundColor,
171
+ },
172
+ });
173
+
174
+ const result = await migration16(
175
+ createMigrationData(model),
176
+ APPLY_DEFAULTS,
177
+ );
178
+
179
+ expect(
180
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
181
+ .subNavBackgroundColor,
182
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
183
+ });
184
+
185
+ test('applies to all display types: TAB_1 color matches when unset', async () => {
186
+ const model = desktopNav(NAVIGATION_DISPLAY.TAB_1, {
187
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
188
+ });
189
+
190
+ const result = await migration16(
191
+ createMigrationData(model),
192
+ APPLY_DEFAULTS,
193
+ );
194
+
195
+ expect(
196
+ result.brandPageModel.pageHeaderDesktop.navigation.subNavBackgroundColor,
197
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
198
+ });
199
+
200
+ test('existing user draft is left untouched, even when the font is matched and the sub nav is unset', async () => {
201
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
202
+ hamburgerMenuStyle: {
203
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
204
+ },
205
+ });
206
+
207
+ const result = await migration16(
208
+ createMigrationData(model),
209
+ EXISTING_DRAFT_SKIP,
210
+ );
211
+
212
+ expect(
213
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
214
+ .subNavBackgroundColor,
215
+ ).toBeUndefined();
216
+ });
217
+
218
+ test('existing user draft keeps a deliberate transparent sub nav (not re-matched on load)', async () => {
219
+ const model = desktopNav(NAVIGATION_DISPLAY.HAMBURGER_1, {
220
+ hamburgerMenuStyle: {
221
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
222
+ subNavBackgroundColor: 'transparent',
223
+ },
224
+ });
225
+
226
+ const result = await migration16(
227
+ createMigrationData(model),
228
+ EXISTING_DRAFT_SKIP,
229
+ );
230
+
231
+ expect(
232
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
233
+ .subNavBackgroundColor,
234
+ ).toBe('transparent');
235
+ });
236
+
237
+ test('migrates mobile navigation as well', async () => {
238
+ const model = {
239
+ pageHeaderMobile: {
240
+ navigation: {
241
+ displayType: NAVIGATION_DISPLAY.HAMBURGER_3,
242
+ hamburgerMenuStyle: {
243
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
244
+ },
245
+ },
246
+ },
247
+ };
248
+
249
+ const result = await migration16(
250
+ createMigrationData(model),
251
+ APPLY_DEFAULTS,
252
+ );
253
+
254
+ expect(
255
+ result.brandPageModel.pageHeaderMobile.navigation.hamburgerMenuStyle
256
+ .subNavBackgroundColor,
257
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
258
+ });
259
+
260
+ test('handles missing navigation gracefully', async () => {
261
+ const result = await migration16(createMigrationData({}), APPLY_DEFAULTS);
262
+
263
+ expect(result.brandPageModel).toEqual({});
264
+ });
265
+
266
+ // Materialisation path (create-draft / server render): NavigationDesktop is
267
+ // constructed WITH a theme. The constructor must leave the sub nav unset, else the
268
+ // baked hex would defeat the migration when the default is applied.
269
+ const theme = {
270
+ primaryColor: 'rgb(0,0,0)',
271
+ secondaryColor: 'rgb(255,255,255)',
272
+ };
273
+
274
+ test('materialised hamburger content (colour-matched font, theme passed) colour-matches the sub nav', async () => {
275
+ const navigation = new NavigationDesktop(
276
+ {
277
+ displayType: NAVIGATION_DISPLAY.HAMBURGER_1,
278
+ hamburgerMenuStyle: {
279
+ defaultButtonStyle: { color: STYLE_THEME_KEYS.LogoTextColor },
280
+ },
281
+ },
282
+ theme,
283
+ );
284
+
285
+ expect(navigation.hamburgerMenuStyle.subNavBackgroundColor).toBeUndefined();
286
+
287
+ const result = await migration16(
288
+ createMigrationData({ pageHeaderDesktop: { navigation } }),
289
+ APPLY_DEFAULTS,
290
+ );
291
+
292
+ expect(
293
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
294
+ .subNavBackgroundColor,
295
+ ).toBe(STYLE_THEME_KEYS.LogoBackgroundColor);
296
+ });
297
+
298
+ test('leaves the sub nav untoggled when the default text color is not colour matched', async () => {
299
+ const navigation = new NavigationDesktop(
300
+ {
301
+ displayType: NAVIGATION_DISPLAY.HAMBURGER_1,
302
+ hamburgerMenuStyle: {
303
+ defaultButtonStyle: { color: 'rgb(50,50,50)' },
304
+ },
305
+ },
306
+ theme,
307
+ );
308
+
309
+ const result = await migration16(
310
+ createMigrationData({ pageHeaderDesktop: { navigation } }),
311
+ APPLY_DEFAULTS,
312
+ );
313
+
314
+ expect(
315
+ result.brandPageModel.pageHeaderDesktop.navigation.hamburgerMenuStyle
316
+ .subNavBackgroundColor,
317
+ ).toBeUndefined();
318
+ });
319
+ });
@@ -0,0 +1,75 @@
1
+ import type { MigrationData, MigrationOptions } from '../migration.util';
2
+ import {
3
+ getDefaultSubNavBackgroundColor,
4
+ isUnsetOrTransparentColor,
5
+ } from '../../models';
6
+ import type {
7
+ NavigationDesktop,
8
+ NavigationMobile,
9
+ NavigationMenuStyles,
10
+ } from '../../models';
11
+
12
+ /**
13
+ * Migration 16: default the sub nav background colour to "colour matched" (the opposite
14
+ * logo colour) when the menu font is itself colour matched and the sub nav has no
15
+ * deliberate value. The default is applied ONCE, when content is first materialised — a
16
+ * template load, the create-draft flow, or the admin maker (isUserDraft === false). An
17
+ * existing user draft already carries its resolved value (including a deliberate
18
+ * transparent/explicit choice), so it is left untouched.
19
+ */
20
+
21
+ // Hamburger keeps its styles on hamburgerMenuStyle; every other nav type uses the
22
+ // top-level navigation styles. (This only locates the styles - the rule itself is uniform.)
23
+ const isHamburger = (displayType: string): boolean =>
24
+ displayType.includes('hamburger');
25
+
26
+ const applyDefaultSubNavColorMatch = (
27
+ navigation: NavigationDesktop | NavigationMobile | undefined,
28
+ ): void => {
29
+ if (!navigation?.displayType) {
30
+ return;
31
+ }
32
+
33
+ let menuStyles: NavigationMenuStyles | NavigationDesktop | NavigationMobile;
34
+
35
+ if (isHamburger(navigation.displayType)) {
36
+ navigation.hamburgerMenuStyle =
37
+ navigation.hamburgerMenuStyle ?? ({} as NavigationMenuStyles);
38
+ menuStyles = navigation.hamburgerMenuStyle;
39
+ } else {
40
+ menuStyles = navigation;
41
+ }
42
+
43
+ // Leave a deliberately-set colour (an explicit hex or an already colour-matched key).
44
+ if (!isUnsetOrTransparentColor(menuStyles.subNavBackgroundColor)) {
45
+ return;
46
+ }
47
+
48
+ // Only when the menu font is itself colour matched: take the opposite logo colour
49
+ // (the helper returns undefined when the font is not colour matched).
50
+ const colorMatchedSubNav = getDefaultSubNavBackgroundColor(
51
+ menuStyles.defaultButtonStyle?.color,
52
+ );
53
+
54
+ if (colorMatchedSubNav) {
55
+ menuStyles.subNavBackgroundColor = colorMatchedSubNav;
56
+ }
57
+ };
58
+
59
+ export const migration16 = async (
60
+ migrationResult: MigrationData,
61
+ options: MigrationOptions,
62
+ ): Promise<MigrationData> => {
63
+ // Skip user drafts (isUserDraft === true) — they already have their value. Only
64
+ // templates (isUserDraft === false) get the sub nav default.
65
+ if (options.isUserDraft) {
66
+ return migrationResult;
67
+ }
68
+
69
+ const { brandPageModel } = migrationResult;
70
+
71
+ applyDefaultSubNavColorMatch(brandPageModel.pageHeaderDesktop?.navigation);
72
+ applyDefaultSubNavColorMatch(brandPageModel.pageHeaderMobile?.navigation);
73
+
74
+ return migrationResult;
75
+ };
@@ -18,6 +18,7 @@ import { migration12_1 } from './migration/migration-12_1';
18
18
  import { migration13 } from './migration/migration-13';
19
19
  import { migration14 } from './migration/migration-14';
20
20
  import { migration15 } from './migration/migration-15';
21
+ import { migration16 } from './migration/migration-16';
21
22
 
22
23
  export interface MigrationData {
23
24
  brandPageModel: BrandPageModel;
@@ -141,6 +142,13 @@ const MIGRATION_TABLE = [
141
142
  migrateFn: migration15,
142
143
  description: 'Adding default mediaOverlayStyle to background styles',
143
144
  },
145
+ {
146
+ version: 16,
147
+ migrateFn: migration16,
148
+ runAlways: true,
149
+ description:
150
+ 'Default sub nav background color to color match on for TAB_2 and hamburger navs depending on font color only on first load flow',
151
+ },
144
152
  ] as MigrationTableItem[];
145
153
 
146
154
  export const GetLatestModelVersion = () =>
@@ -35,6 +35,8 @@ import {
35
35
  useSnapshotStore,
36
36
  useVideoStore,
37
37
  } from '@brand-page-maker/stores';
38
+ import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
39
+ import { useUndoRedoFlag } from '@brand-page-maker/composables/useUndoRedoFlag';
38
40
  import type { AdminPreviewLogoTemplateModel } from '@brand-page-maker/stores/admin-logo-settings';
39
41
  import { useUiContentSettingsStore } from '@shared/content-settings/store';
40
42
  import { useUiTemplateSettingsStore } from '@shared/template-settings/store';
@@ -283,6 +285,13 @@ export default defineComponent({
283
285
  // Mark brand page as fully loaded
284
286
  this.brandPageMakerStore.setIsBrandPageLoaded(true);
285
287
 
288
+ // Capture baseline for undo/redo when the feature is active for this
289
+ // session — see useUndoRedoFlag.ts for the gate (LaunchDarkly flag +
290
+ // brand-site + non-admin maker).
291
+ if (useUndoRedoFlag().value) {
292
+ useHistoryStore().captureBaseline();
293
+ }
294
+
286
295
  this.$emit(EVENTS.Initialised);
287
296
  },
288
297
  mounted() {
@@ -23,7 +23,6 @@
23
23
  >
24
24
  <div class="tw-flex tw-flex-col tw-w-full tw-h-full">
25
25
  <QuickLinkBar
26
- v-if="isPublished && !isAdmin"
27
26
  class="c-quick-link-bar"
28
27
  :brand-page-base-url="brandPageBaseUrl"
29
28
  :slug="brandPageSlug"
@@ -87,8 +86,8 @@
87
86
  v-if="isBrandPageLoaded"
88
87
  :is-mobile-device="isMobileDevice"
89
88
  :is-design-com="isDesignCom"
90
- :has-quick-links="isPublished"
91
89
  :container="previewContainer"
90
+ :has-quick-links="isPublished"
92
91
  >
93
92
  <div ref="previewContent" class="tw-h-full">
94
93
  <PreviewWithState :device-mode="deviceMode" />
@@ -123,9 +122,9 @@
123
122
  </div>
124
123
 
125
124
  <SideBar
126
- :has-quick-links="isPublished"
127
125
  :mobile-tab-name="SideBarTabNames.PREVIEW"
128
126
  :is-mobile-screen-size="isMobileScreenSize"
127
+ :has-quick-links="isPublished"
129
128
  >
130
129
  <template #tab>
131
130
  <component
@@ -90,6 +90,7 @@ export const userFeatures: UserFeatures = {
90
90
  canUseGettyVideos: false,
91
91
  canUseGoogleAdsense: false,
92
92
  googleAdsenseEnabled: false,
93
+ canUseUndoRedo: false,
93
94
  };
94
95
 
95
96
  export const BRAND_PAGE: BrandPageData = {
@@ -36,6 +36,7 @@ import {
36
36
  import { useExternalRichTextEditor } from '@brand-page-maker/composables/rich-text-editor/useRichTextEditor';
37
37
  import { useUiStyleSettingsStore } from '@shared/style-settings/store';
38
38
  import { STYLE_THEME_KEYS } from '@brand-page/constants';
39
+ import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
39
40
 
40
41
  export default defineComponent({
41
42
  mixins: [PageMixin],
@@ -51,6 +52,7 @@ export default defineComponent({
51
52
  const eventTracker = useEventTracker();
52
53
  const { externalEditor } = useExternalRichTextEditor();
53
54
  const styleSettingsStore = useUiStyleSettingsStore();
55
+ const historyStore = useHistoryStore();
54
56
 
55
57
  return {
56
58
  items: computed(() => contentSettingsStore.items),
@@ -84,6 +86,7 @@ export default defineComponent({
84
86
  eventTracker,
85
87
  externalEditor,
86
88
  styleSettingsStore,
89
+ historyStore,
87
90
  };
88
91
  },
89
92
  data() {
@@ -301,6 +304,8 @@ export default defineComponent({
301
304
  }
302
305
  }
303
306
 
307
+ // RTE rewrap on section selection — fold into current head.
308
+ this.historyStore.mergeNextMutation();
304
309
  transformToRTE(
305
310
  this.pageSettingsStore,
306
311
  this.externalEditor,
@@ -27,8 +27,9 @@
27
27
  </div>
28
28
  <button
29
29
  v-else
30
- :style="generateStyle(color)"
30
+ :style="color === 'transparent' ? undefined : generateStyle(color)"
31
31
  class="c-color tw-bg-transparent tw-block tw-w-full tw-h-full tw-border-none tw-rounded tw-cursor-pointer tw-transition-all tw-duration-300 tw-ease-out"
32
+ :class="{ 'c-transparent': color === 'transparent' }"
32
33
  @click="onClick"
33
34
  />
34
35
  </div>
@@ -197,12 +198,13 @@ const clickOutside = (event: MouseEvent): void => {
197
198
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.3);
198
199
  transform: scale(1.025);
199
200
  }
200
- .c-color {
201
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
202
- }
203
- .c-color:hover {
204
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.3);
205
- transform: scale(1.025);
201
+ .c-transparent {
202
+ background: linear-gradient(
203
+ 135deg,
204
+ transparent calc(50% - 1.5px),
205
+ red 50%,
206
+ transparent calc(50% + 1.5px)
207
+ );
206
208
  }
207
209
  .maker-color-palette__popup {
208
210
  position: absolute;
@@ -20,14 +20,14 @@
20
20
  </div>
21
21
  <Toggle
22
22
  id="show-cookie-banner"
23
- :model-value="data.isToggled"
23
+ :model-value="isToggled"
24
24
  class="tw-pointer-events-none tw-touch-none"
25
25
  color="primary"
26
26
  @update:model-value="onShowCookieBanner()"
27
27
  />
28
28
  </div>
29
29
  </div>
30
- <div v-if="data.isToggled" class="tw-flex-none tw-mt-4">
30
+ <div v-if="isToggled" class="tw-flex-none tw-mt-4">
31
31
  <div
32
32
  class="tw-flex tw-items-center tw-justify-between tw-cursor-pointer"
33
33
  @click="onShowPreviewCookieBanner"
@@ -41,7 +41,7 @@
41
41
  </div>
42
42
  <Toggle
43
43
  id="show-preview-cookie-banner"
44
- :model-value="data.isPreviewToggled"
44
+ :model-value="isPreviewToggled"
45
45
  class="tw-pointer-events-none tw-touch-none"
46
46
  color="primary"
47
47
  @update:model-value="onShowPreviewCookieBanner()"
@@ -55,10 +55,10 @@
55
55
  import { Toggle } from '@designcrowd/fe-shared-lib';
56
56
  import { cookieSettingTr } from '@src/composables/useTranslate';
57
57
  import type { CookieOptions } from '@brand-page/models';
58
- import { reactive, type PropType } from 'vue';
58
+ import { computed, type PropType } from 'vue';
59
59
  import { useBrandPageMakerStore } from '@brand-page-maker/stores';
60
60
 
61
- const props = defineProps({
61
+ defineProps({
62
62
  viewModel: {
63
63
  type: Object as PropType<CookieOptions>,
64
64
  required: true,
@@ -67,25 +67,26 @@ const props = defineProps({
67
67
 
68
68
  const brandPageMakerStore = useBrandPageMakerStore();
69
69
 
70
- const data = reactive({
71
- isToggled: props.viewModel.isCookieEnabled,
72
- isPreviewToggled: props.viewModel.isCookiePreviewEnabled,
73
- });
70
+ // Read directly from the store so undo/redo (and any other external
71
+ // mutation of cookieOptions) is reflected in the toggles.
72
+ const isToggled = computed(
73
+ () => brandPageMakerStore.cookieOptions.isCookieEnabled,
74
+ );
75
+ const isPreviewToggled = computed(
76
+ () => brandPageMakerStore.cookieOptions.isCookiePreviewEnabled,
77
+ );
74
78
 
75
79
  const onShowCookieBanner = () => {
76
- data.isToggled = !data.isToggled;
77
- brandPageMakerStore.setCookieEnabled({
78
- isCookieEnabled: data.isToggled,
79
- });
80
- data.isPreviewToggled = data.isToggled;
80
+ const next = !isToggled.value;
81
+
82
+ brandPageMakerStore.setCookieEnabled({ isCookieEnabled: next });
81
83
  brandPageMakerStore.setCookiePreviewEnabled({
82
- isCookiePreviewEnabled: data.isPreviewToggled,
84
+ isCookiePreviewEnabled: next,
83
85
  });
84
86
  };
85
87
  const onShowPreviewCookieBanner = () => {
86
- data.isPreviewToggled = !data.isPreviewToggled;
87
88
  brandPageMakerStore.setCookiePreviewEnabled({
88
- isCookiePreviewEnabled: data.isPreviewToggled,
89
+ isCookiePreviewEnabled: !isPreviewToggled.value,
89
90
  });
90
91
  };
91
92
  </script>
@@ -4,6 +4,7 @@ import {
4
4
  useBrandPageContainerStore,
5
5
  useEventManagerStore,
6
6
  } from '@brand-page-maker/stores';
7
+ import { useUndoRedoFlag } from '@brand-page-maker/composables/useUndoRedoFlag';
7
8
  import { quickLinkBarTr } from '@src/composables/useTranslate';
8
9
 
9
10
  export default defineComponent({
@@ -21,6 +22,7 @@ export default defineComponent({
21
22
  return {
22
23
  brandPageContainerStore: useBrandPageContainerStore(),
23
24
  eventManagerStore: useEventManagerStore(),
25
+ undoRedoEnabled: useUndoRedoFlag(),
24
26
  };
25
27
  },
26
28
  data() {
@@ -29,6 +31,17 @@ export default defineComponent({
29
31
  };
30
32
  },
31
33
  computed: {
34
+ isPublished(): boolean {
35
+ return this.brandPageContainerStore.isPublished;
36
+ },
37
+
38
+ // Only render the bar when at least one inner cluster has something to
39
+ // show. Otherwise we'd render an empty white strip — e.g. on an
40
+ // unpublished page with the undo flag off.
41
+ shouldShowBar(): boolean {
42
+ return Boolean(this.undoRedoEnabled) || this.isPublished;
43
+ },
44
+
32
45
  brandPageUrl(): string {
33
46
  if (
34
47
  this.brandPageContainerStore.isCustomDomainEnabled &&