@designcrowd/library.brand-page 5.19.0 → 5.19.1-error-html2canvas-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DsoA9hc-.js → CreatedOnBrandCrowd-de-de-CxCJU9cc.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-BP6NEQ66.js → CreatedOnBrandCrowd-en-us-zDJCWP5n.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-D424uM1Z.js → CreatedOnBrandCrowd-es-es-DNna1Sh9.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CJvuLTkB.js → CreatedOnBrandCrowd-fr-ca-C5XVf_hD.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CJvuLTkB.js → CreatedOnBrandCrowd-fr-fr-C5XVf_hD.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-Cc9L314u.js → CreatedOnBrandCrowd-pt-br-9eUHV_M3.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-Cc9L314u.js → CreatedOnBrandCrowd-pt-pt-9eUHV_M3.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-CX-295FH.js → CreatedOnDcom-de-de-D9HMpK8Q.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-UFlORUgZ.js → CreatedOnDcom-en-us-BCTkchV5.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-D1tRpcq9.js → CreatedOnDcom-es-es-B0N7VoTp.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-Dz_ry9uY.js → CreatedOnDcom-fr-ca-Gi4oz_Mm.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-8Ydu6b1T.js → CreatedOnDcom-fr-fr-DJI8eJo1.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-COwvw5k4.js → CreatedOnDcom-pt-br-YNWMAuQf.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-CB72-eaa.js → CreatedOnDcom-pt-pt-DCIIK3sy.js} +1 -1
  15. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +4 -0
  16. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +8 -4
  17. package/lib/src/brand-page/components/image-gallery/ImageGallery.mixin.d.ts +4 -0
  18. package/lib/src/brand-page/components/image-gallery/ImageGallery.mixin.js +4 -0
  19. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.d.ts +4 -0
  20. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +4 -1
  21. package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.d.ts +4 -0
  22. package/lib/src/brand-page/components/payment/request-payment/RequestPayment.mixin.js +4 -0
  23. package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.d.ts +4 -0
  24. package/lib/src/brand-page/components/payment/support-me/SupportMe.mixin.js +4 -1
  25. package/lib/src/brand-page/components/video/Video.mixin.d.ts +4 -0
  26. package/lib/src/brand-page/components/video/Video.mixin.js +4 -0
  27. package/lib/src/brand-page/constants/font.constants.d.ts +4 -4
  28. package/lib/src/brand-page/constants/font.constants.js +4 -4
  29. package/lib/src/brand-page/constants/misc.constants.d.ts +35 -0
  30. package/lib/src/brand-page/constants/misc.constants.js +51 -0
  31. package/lib/src/brand-page/models/content.model.d.ts +2 -0
  32. package/lib/src/brand-page/utils/content-padding.util.d.ts +13 -0
  33. package/lib/src/brand-page/utils/content-padding.util.js +10 -0
  34. package/lib/src/brand-page/utils/index.d.ts +1 -0
  35. package/lib/src/brand-page/utils/index.js +1 -0
  36. package/lib/src/brand-page/utils/migration/migration-17.d.ts +2 -0
  37. package/lib/src/brand-page/utils/migration/migration-17.js +60 -0
  38. package/lib/src/brand-page/utils/migration.util.js +10 -0
  39. package/lib/src/brand-page-maker/components/admin-logo-settings/AdminLogoSettings.mixin.d.ts +2 -0
  40. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +12 -0
  41. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +14 -0
  42. package/lib/src/brand-page-maker/constants/config-settings-constant.js +39 -0
  43. package/lib/src/brand-page-maker/convertors/about-display-style.convertor.js +9 -1
  44. package/lib/src/brand-page-maker/convertors/banner-align-display-style.convertor.js +7 -1
  45. package/lib/src/brand-page-maker/convertors/contact-layout-style.convertor.js +6 -1
  46. package/lib/src/brand-page-maker/models/content-settings.js +39 -2
  47. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +12 -0
  48. package/lib/src/{bundled-translations-D1q3rSEz.js → bundled-translations-BUyt4scr.js} +9 -9
  49. package/lib/src/{bundled-translations.de-DE-BxqLLwbh.js → bundled-translations.de-DE-DEw_OVai.js} +1 -1
  50. package/lib/src/{bundled-translations.es-ES-uh249NlJ.js → bundled-translations.es-ES-CFKWmkgY.js} +17 -17
  51. package/lib/src/{bundled-translations.fr-CA-BEQmv2fX.js → bundled-translations.fr-CA-DCl5LJS4.js} +1 -1
  52. package/lib/src/{bundled-translations.fr-FR-BnNuCojH.js → bundled-translations.fr-FR-B5BjgllA.js} +1 -1
  53. package/lib/src/{bundled-translations.pt-BR-CJUOxVlq.js → bundled-translations.pt-BR-5PWpMPuM.js} +1 -1
  54. package/lib/src/{bundled-translations.pt-PT-Ds9cMEqK.js → bundled-translations.pt-PT-B9_wN0HR.js} +9 -9
  55. package/lib/src/bundles/bundled-translations.de-DE.json +2 -0
  56. package/lib/src/bundles/bundled-translations.es-ES.json +2 -0
  57. package/lib/src/bundles/bundled-translations.fr-CA.json +2 -0
  58. package/lib/src/bundles/bundled-translations.fr-FR.json +2 -0
  59. package/lib/src/bundles/bundled-translations.json +2 -0
  60. package/lib/src/bundles/bundled-translations.pt-BR.json +2 -0
  61. package/lib/src/bundles/bundled-translations.pt-PT.json +2 -0
  62. package/lib/src/composables/useSnapshot.js +18 -18
  63. package/lib/src/css/library.brand-page-brandCrowd.min.css +141 -155
  64. package/lib/src/css/library.brand-page-brandPage.min.css +141 -155
  65. package/lib/src/css/library.brand-page-designCom.min.css +141 -155
  66. package/lib/src/css/library.brand-page-preview.min.css +141 -155
  67. package/lib/src/index.mjs +1 -1
  68. package/lib/src/{lib-tHPi9EKk.js → lib-CaY174HB.js} +21159 -21017
  69. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +14 -0
  70. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +14 -0
  71. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +12 -0
  72. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +12 -0
  73. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +12 -0
  74. package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.js +9 -0
  75. package/lib/src/shared/content-settings/store/content-settings-store.d.ts +12 -0
  76. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +16 -0
  77. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +14 -0
  78. package/lib/src/utils/minify-brand-page-metadata.js +11 -1
  79. package/package.json +1 -1
  80. package/src/brand-page/components/about-section/AboutSection.vue +10 -4
  81. package/src/brand-page/components/banner-section/BannerSection.vue +11 -4
  82. package/src/brand-page/components/booking-section/BookingSection.vue +11 -3
  83. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +12 -6
  84. package/src/brand-page/components/contact-form/ContactForm.vue +105 -104
  85. package/src/brand-page/components/image-gallery/ImageGallery.mixin.ts +5 -0
  86. package/src/brand-page/components/image-gallery/ImageGallery.vue +1 -1
  87. package/src/brand-page/components/mailing-list/MailingList.mixin.ts +8 -1
  88. package/src/brand-page/components/mailing-list/MailingList.vue +1 -1
  89. package/src/brand-page/components/map/Map.vue +5 -1
  90. package/src/brand-page/components/payment/request-payment/RequestPayment.mixin.ts +5 -0
  91. package/src/brand-page/components/payment/request-payment/RequestPayment.vue +1 -0
  92. package/src/brand-page/components/payment/support-me/SupportMe.mixin.ts +8 -1
  93. package/src/brand-page/components/payment/support-me/SupportMe.vue +1 -0
  94. package/src/brand-page/components/shop/Shop.vue +7 -2
  95. package/src/brand-page/components/social-links/SocialLinks.vue +7 -3
  96. package/src/brand-page/components/testimonial-section/TestimonialSection.vue +120 -11
  97. package/src/brand-page/components/text/Text.vue +12 -5
  98. package/src/brand-page/components/text-media-section/TextMediaSection.vue +11 -3
  99. package/src/brand-page/components/video/Video.mixin.ts +5 -0
  100. package/src/brand-page/components/video/Video.vue +1 -1
  101. package/src/brand-page/constants/font.constants.ts +4 -4
  102. package/src/brand-page/constants/misc.constants.ts +58 -0
  103. package/src/brand-page/models/content.model.ts +2 -0
  104. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +9 -3
  105. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +6 -6
  106. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +12 -12
  107. package/src/brand-page/utils/content-padding.util.test.ts +29 -0
  108. package/src/brand-page/utils/content-padding.util.ts +13 -0
  109. package/src/brand-page/utils/index.ts +1 -0
  110. package/src/brand-page/utils/migration/migration-17.ts +77 -0
  111. package/src/brand-page/utils/migration.util.test.ts +290 -0
  112. package/src/brand-page/utils/migration.util.ts +11 -0
  113. package/src/brand-page-maker/constants/config-settings-constant.ts +45 -0
  114. package/src/brand-page-maker/convertors/about-display-style.convertor.ts +10 -0
  115. package/src/brand-page-maker/convertors/banner-align-display-style.convertor.ts +8 -0
  116. package/src/brand-page-maker/convertors/contact-layout-style.convertor.test.ts +50 -0
  117. package/src/brand-page-maker/convertors/contact-layout-style.convertor.ts +8 -0
  118. package/src/brand-page-maker/models/content-settings.ts +43 -2
  119. package/src/bundles/bundled-translations.de-DE.json +2 -0
  120. package/src/bundles/bundled-translations.es-ES.json +2 -0
  121. package/src/bundles/bundled-translations.fr-CA.json +2 -0
  122. package/src/bundles/bundled-translations.fr-FR.json +2 -0
  123. package/src/bundles/bundled-translations.json +2 -0
  124. package/src/bundles/bundled-translations.pt-BR.json +2 -0
  125. package/src/bundles/bundled-translations.pt-PT.json +2 -0
  126. package/src/composables/useSnapshot.ts +19 -22
  127. package/src/shared/content-settings/components/content-settings/i18n/content-settings.de-DE.json +2 -0
  128. package/src/shared/content-settings/components/content-settings/i18n/content-settings.es-ES.json +2 -0
  129. package/src/shared/content-settings/components/content-settings/i18n/content-settings.fr-CA.json +2 -0
  130. package/src/shared/content-settings/components/content-settings/i18n/content-settings.fr-FR.json +2 -0
  131. package/src/shared/content-settings/components/content-settings/i18n/content-settings.json +2 -0
  132. package/src/shared/content-settings/components/content-settings/i18n/content-settings.pt-BR.json +2 -0
  133. package/src/shared/content-settings/components/content-settings/i18n/content-settings.pt-PT.json +2 -0
  134. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.vue +1 -1
  135. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.vue +2 -2
  136. package/src/shared/content-settings/components/slider-field/SliderField.mixin.ts +9 -0
  137. package/src/utils/minify-brand-page-metadata.ts +11 -1
  138. package/lib/src/brand-page/components/testimonial-section/TestimonialSection.mixin.d.ts +0 -97
  139. package/lib/src/brand-page/components/testimonial-section/TestimonialSection.mixin.js +0 -112
  140. package/src/brand-page/components/testimonial-section/TestimonialSection.mixin.ts +0 -132
@@ -0,0 +1,77 @@
1
+ import type { MigrationData } from '../migration.util';
2
+ import type { BannerSectionContent, ContactFormContent } from '../../models';
3
+ import { BRAND_PAGE_TYPES } from '../../models';
4
+ import {
5
+ CONTACT_FORM_LAYOUT_DEFAULT_PADDING,
6
+ TESTIMONIAL_DEFAULT_PADDING,
7
+ bannerDisplayStyleDefaultPadding,
8
+ } from '../../constants';
9
+
10
+ /**
11
+ * Padding Control backfill (BP-5699 Tier 2, BP-5700 Store): blocks that already
12
+ * had hard-coded top/bottom spacing. Backfill that spacing as the block's explicit
13
+ * topPadding/bottomPadding so existing pages render unchanged once the spacing
14
+ * moves to the Padding Control sliders. Type-scoped and idempotent — only the
15
+ * listed types, and only when the value isn't already set.
16
+ *
17
+ * Exception — `testimonial-section` (BP-5703): testimonials had 32px mobile / 80px
18
+ * desktop and are intentionally collapsing to a single value across both breakpoints,
19
+ * so the backfill sets that single value rather than preserving the old desktop 80px.
20
+ * Existing testimonials will look tighter on desktop / roomier on mobile — by design.
21
+ */
22
+ const BLOCK_DEFAULT_PADDING: Partial<Record<string, number>> = {
23
+ 'images-section': 32,
24
+ 'social-links': 32,
25
+ 'shapes-section': 32,
26
+ 'booking-section': 32,
27
+ 'shop-section': 16,
28
+ 'about-section': 32,
29
+ 'testimonial-section': TESTIMONIAL_DEFAULT_PADDING,
30
+ };
31
+
32
+ export const migration17 = async (
33
+ migrationResult: MigrationData,
34
+ ): Promise<MigrationData> => {
35
+ const { brandPageModel } = migrationResult;
36
+
37
+ brandPageModel.pages.forEach((page) => {
38
+ page.contents.forEach((content) => {
39
+ // Contact Form and Banner previously varied their hard-coded spacing by
40
+ // layout/display style (full-bleed image layouts had no outer padding), so they're
41
+ // keyed by that rather than a single per-type value.
42
+ let value: number | undefined;
43
+
44
+ if (content.type === 'contact-form') {
45
+ value =
46
+ CONTACT_FORM_LAYOUT_DEFAULT_PADDING[
47
+ (content as ContactFormContent).layoutStyle ?? ''
48
+ ];
49
+ } else if (content.type === 'banner-section') {
50
+ value = bannerDisplayStyleDefaultPadding(
51
+ (content as BannerSectionContent).displayStyle,
52
+ );
53
+ } else if (content.type === 'text') {
54
+ // Text outer padding is brand-page-type specific: 12 on Brand Site, 0 on
55
+ // Brand Page / Brand Contact - backfill per type so existing pages render unchanged.
56
+ value =
57
+ brandPageModel.brandPageType === BRAND_PAGE_TYPES.brandSite ? 12 : 0;
58
+ } else {
59
+ value = BLOCK_DEFAULT_PADDING[content.type];
60
+ }
61
+
62
+ if (value === undefined) {
63
+ return;
64
+ }
65
+
66
+ if (content.topPadding === undefined) {
67
+ content.topPadding = value;
68
+ }
69
+
70
+ if (content.bottomPadding === undefined) {
71
+ content.bottomPadding = value;
72
+ }
73
+ });
74
+ });
75
+
76
+ return migrationResult;
77
+ };
@@ -1,5 +1,6 @@
1
1
  import { migrateBrandPageModel } from './migration.util';
2
2
  import { migrateNewLogoLogic } from './migration/migration-01';
3
+ import { migration17 } from './migration/migration-17';
3
4
  import {
4
5
  BrandPageModel,
5
6
  type BrandPageData,
@@ -24,11 +25,18 @@ import {
24
25
  createInitialTestimonialSectionContent,
25
26
  } from '@brand-page-maker/models';
26
27
  import {
28
+ ALIGN_CARD_CENTER_IMAGE,
29
+ ALIGN_LEFT_FULL_IMAGE,
30
+ ALIGN_RIGHT_FULL_IMAGE,
31
+ ALIGN_TOP_FULL_IMAGE,
32
+ ALIGN_BOTTOM_FULL_IMAGE,
33
+ ALIGN_MIDDLE_FULL_IMAGE,
27
34
  DEFAULT_LETTER_SPACING,
28
35
  LINE_HEIGHT_BODY,
29
36
  LINE_HEIGHT_HEADINGS,
30
37
  LINE_HEIGHT_TITLE,
31
38
  TEXT_ONLY,
39
+ TESTIMONIAL_DEFAULT_PADDING,
32
40
  } from '../constants';
33
41
 
34
42
  describe('migration.util', () => {
@@ -1464,6 +1472,288 @@ describe('migration.util', () => {
1464
1472
  expect(content.layoutStyle).toBe(TEXT_ONLY);
1465
1473
  });
1466
1474
 
1475
+ test('migration17 should backfill contact form padding from its layout style', async () => {
1476
+ const model = await migrateBrandPageModel(getBsModel(), {
1477
+ isUserDraft: false,
1478
+ isServerRender: false,
1479
+ });
1480
+
1481
+ const collapsible = model.brandPageModel.pages[3]
1482
+ .contents[0] as ContactFormContent;
1483
+ const textOnly = model.brandPageModel.pages[3]
1484
+ .contents[1] as ContactFormContent;
1485
+
1486
+ expect(collapsible.topPadding).toBe(0);
1487
+ expect(collapsible.bottomPadding).toBe(0);
1488
+ expect(textOnly.topPadding).toBe(32);
1489
+ expect(textOnly.bottomPadding).toBe(32);
1490
+ });
1491
+
1492
+ test.each([
1493
+ [ALIGN_LEFT_FULL_IMAGE],
1494
+ [ALIGN_RIGHT_FULL_IMAGE],
1495
+ [ALIGN_CARD_CENTER_IMAGE],
1496
+ ])(
1497
+ 'migration17 should backfill zero padding for the %s contact form layout',
1498
+ async (layoutStyle) => {
1499
+ const brandPageModel = getBsModel();
1500
+
1501
+ (
1502
+ brandPageModel.pages[3].contents[1] as ContactFormContent
1503
+ ).layoutStyle = layoutStyle;
1504
+
1505
+ const model = await migrateBrandPageModel(brandPageModel, {
1506
+ isUserDraft: false,
1507
+ isServerRender: false,
1508
+ });
1509
+
1510
+ const content = model.brandPageModel.pages[3]
1511
+ .contents[1] as ContactFormContent;
1512
+
1513
+ expect(content.topPadding).toBe(0);
1514
+ expect(content.bottomPadding).toBe(0);
1515
+ },
1516
+ );
1517
+
1518
+ test('migration17 should not overwrite contact form padding that is already set', async () => {
1519
+ const brandPageModel = getBsModel();
1520
+
1521
+ (brandPageModel.pages[3].contents[1] as ContactFormContent).topPadding =
1522
+ 10;
1523
+
1524
+ const model = await migrateBrandPageModel(brandPageModel, {
1525
+ isUserDraft: false,
1526
+ isServerRender: false,
1527
+ });
1528
+
1529
+ const content = model.brandPageModel.pages[3]
1530
+ .contents[1] as ContactFormContent;
1531
+
1532
+ expect(content.topPadding).toBe(10);
1533
+ expect(content.bottomPadding).toBe(32);
1534
+ });
1535
+
1536
+ test('migration17 should backfill 32 padding for standard banner display styles', async () => {
1537
+ const model = await migrateBrandPageModel(getBsModel(), {
1538
+ isUserDraft: false,
1539
+ isServerRender: false,
1540
+ });
1541
+
1542
+ // pages[0].contents[0] is a 'centered' banner (standard, not full-bleed).
1543
+ const banner = model.brandPageModel.pages[0]
1544
+ .contents[0] as BannerSectionContent;
1545
+
1546
+ expect(banner.topPadding).toBe(32);
1547
+ expect(banner.bottomPadding).toBe(32);
1548
+ });
1549
+
1550
+ test.each([
1551
+ [ALIGN_LEFT_FULL_IMAGE],
1552
+ [ALIGN_RIGHT_FULL_IMAGE],
1553
+ [ALIGN_TOP_FULL_IMAGE],
1554
+ [ALIGN_BOTTOM_FULL_IMAGE],
1555
+ [ALIGN_MIDDLE_FULL_IMAGE],
1556
+ ])(
1557
+ 'migration17 should backfill zero padding for the %s full-bleed banner style',
1558
+ async (displayStyle) => {
1559
+ const brandPageModel = getBsModel();
1560
+
1561
+ (
1562
+ brandPageModel.pages[0].contents[0] as BannerSectionContent
1563
+ ).displayStyle = displayStyle;
1564
+
1565
+ const model = await migrateBrandPageModel(brandPageModel, {
1566
+ isUserDraft: false,
1567
+ isServerRender: false,
1568
+ });
1569
+
1570
+ const banner = model.brandPageModel.pages[0]
1571
+ .contents[0] as BannerSectionContent;
1572
+
1573
+ expect(banner.topPadding).toBe(0);
1574
+ expect(banner.bottomPadding).toBe(0);
1575
+ },
1576
+ );
1577
+
1578
+ test('migration17 should backfill 32 padding for standard about display styles', async () => {
1579
+ const model = await migrateBrandPageModel(getBsModel(), {
1580
+ isUserDraft: false,
1581
+ isServerRender: false,
1582
+ });
1583
+
1584
+ // pages[0].contents[2] is a 'centered' about section (standard, not full-image).
1585
+ const about = model.brandPageModel.pages[0]
1586
+ .contents[2] as AboutSectionContent;
1587
+
1588
+ expect(about.topPadding).toBe(32);
1589
+ expect(about.bottomPadding).toBe(32);
1590
+ });
1591
+
1592
+ test.each([[ALIGN_LEFT_FULL_IMAGE], [ALIGN_RIGHT_FULL_IMAGE]])(
1593
+ 'migration17 should backfill 32 padding for the %s full-image about style',
1594
+ async (displayStyle) => {
1595
+ const brandPageModel = getBsModel();
1596
+
1597
+ (
1598
+ brandPageModel.pages[0].contents[2] as AboutSectionContent
1599
+ ).displayStyle = displayStyle;
1600
+
1601
+ const model = await migrateBrandPageModel(brandPageModel, {
1602
+ isUserDraft: false,
1603
+ isServerRender: false,
1604
+ });
1605
+
1606
+ const about = model.brandPageModel.pages[0]
1607
+ .contents[2] as AboutSectionContent;
1608
+
1609
+ expect(about.topPadding).toBe(32);
1610
+ expect(about.bottomPadding).toBe(32);
1611
+ },
1612
+ );
1613
+
1614
+ test('migration17 should backfill 16 padding for the shop section', async () => {
1615
+ const oldTemplate: BrandPageModel = {
1616
+ pages: [
1617
+ {
1618
+ slug: 'shop',
1619
+ title: 'Shop',
1620
+ contents: [{ type: 'shop-section', id: 'shop-1' }],
1621
+ },
1622
+ ],
1623
+ brandPageType: 'brandSite',
1624
+ } as any;
1625
+
1626
+ const model = await migrateBrandPageModel(
1627
+ new BrandPageModel(oldTemplate),
1628
+ {
1629
+ isUserDraft: false,
1630
+ isServerRender: false,
1631
+ },
1632
+ );
1633
+ const shop = model.brandPageModel.pages[0].contents[0] as any;
1634
+
1635
+ expect(shop.topPadding).toBe(16);
1636
+ expect(shop.bottomPadding).toBe(16);
1637
+ });
1638
+
1639
+ test('migration17 should not overwrite shop padding that is already set', async () => {
1640
+ const oldTemplate: BrandPageModel = {
1641
+ pages: [
1642
+ {
1643
+ slug: 'shop',
1644
+ title: 'Shop',
1645
+ contents: [{ type: 'shop-section', id: 'shop-1', topPadding: 40 }],
1646
+ },
1647
+ ],
1648
+ brandPageType: 'brandSite',
1649
+ } as any;
1650
+
1651
+ const model = await migrateBrandPageModel(
1652
+ new BrandPageModel(oldTemplate),
1653
+ {
1654
+ isUserDraft: false,
1655
+ isServerRender: false,
1656
+ },
1657
+ );
1658
+ const shop = model.brandPageModel.pages[0].contents[0] as any;
1659
+
1660
+ expect(shop.topPadding).toBe(40);
1661
+ expect(shop.bottomPadding).toBe(16);
1662
+ });
1663
+
1664
+ test('migration17 should backfill the single testimonial padding across both breakpoints', async () => {
1665
+ const oldTemplate: BrandPageModel = {
1666
+ pages: [
1667
+ {
1668
+ slug: 'testimonials',
1669
+ title: 'Testimonials',
1670
+ contents: [{ type: 'testimonial-section', id: 'testimonial-1' }],
1671
+ },
1672
+ ],
1673
+ brandPageType: 'brandSite',
1674
+ } as any;
1675
+
1676
+ const model = await migrateBrandPageModel(
1677
+ new BrandPageModel(oldTemplate),
1678
+ {
1679
+ isUserDraft: false,
1680
+ isServerRender: false,
1681
+ },
1682
+ );
1683
+ const testimonial = model.brandPageModel.pages[0].contents[0] as any;
1684
+
1685
+ expect(testimonial.topPadding).toBe(TESTIMONIAL_DEFAULT_PADDING);
1686
+ expect(testimonial.bottomPadding).toBe(TESTIMONIAL_DEFAULT_PADDING);
1687
+ });
1688
+
1689
+ test('migration17 should not overwrite testimonial padding that is already set', async () => {
1690
+ const oldTemplate: BrandPageModel = {
1691
+ pages: [
1692
+ {
1693
+ slug: 'testimonials',
1694
+ title: 'Testimonials',
1695
+ contents: [
1696
+ {
1697
+ type: 'testimonial-section',
1698
+ id: 'testimonial-1',
1699
+ topPadding: 40,
1700
+ },
1701
+ ],
1702
+ },
1703
+ ],
1704
+ brandPageType: 'brandSite',
1705
+ } as any;
1706
+
1707
+ const model = await migrateBrandPageModel(
1708
+ new BrandPageModel(oldTemplate),
1709
+ {
1710
+ isUserDraft: false,
1711
+ isServerRender: false,
1712
+ },
1713
+ );
1714
+ const testimonial = model.brandPageModel.pages[0].contents[0] as any;
1715
+
1716
+ expect(testimonial.topPadding).toBe(40);
1717
+ expect(testimonial.bottomPadding).toBe(TESTIMONIAL_DEFAULT_PADDING);
1718
+ });
1719
+
1720
+ const runMigration17ForText = async (brandPageType: string) => {
1721
+ const brandPageModel = {
1722
+ brandPageType,
1723
+ pages: [
1724
+ {
1725
+ slug: 'home',
1726
+ title: 'Home',
1727
+ contents: [{ type: 'text', id: 'text-1' }],
1728
+ },
1729
+ ],
1730
+ } as any;
1731
+
1732
+ const result = await migration17({
1733
+ brandPageModel,
1734
+ useOldLogoRenderSize: false,
1735
+ });
1736
+
1737
+ return result.brandPageModel.pages[0].contents[0] as any;
1738
+ };
1739
+
1740
+ test('migration17 should backfill 12 text padding for brand site', async () => {
1741
+ const text = await runMigration17ForText('brandSite');
1742
+
1743
+ expect(text.topPadding).toBe(12);
1744
+ expect(text.bottomPadding).toBe(12);
1745
+ });
1746
+
1747
+ test.each(['default', 'brandContact'])(
1748
+ 'migration17 should backfill 0 text padding for %s (Brand Page / Brand Contact)',
1749
+ async (brandPageType) => {
1750
+ const text = await runMigration17ForText(brandPageType);
1751
+
1752
+ expect(text.topPadding).toBe(0);
1753
+ expect(text.bottomPadding).toBe(0);
1754
+ },
1755
+ );
1756
+
1467
1757
  test('migrateNewLogoLogic should migrate logoDraftToken and logoVariation', () => {
1468
1758
  const oldTemplate: BrandPageData = {
1469
1759
  pages: [
@@ -19,6 +19,7 @@ import { migration13 } from './migration/migration-13';
19
19
  import { migration14 } from './migration/migration-14';
20
20
  import { migration15 } from './migration/migration-15';
21
21
  import { migration16 } from './migration/migration-16';
22
+ import { migration17 } from './migration/migration-17';
22
23
 
23
24
  export interface MigrationData {
24
25
  brandPageModel: BrandPageModel;
@@ -149,6 +150,16 @@ const MIGRATION_TABLE = [
149
150
  description:
150
151
  '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
152
  },
153
+ {
154
+ version: 17,
155
+ migrateFn: migration17,
156
+ // runAlways: re-apply on every load (ticket stack) so already-migrated pages
157
+ // pick up newly-added block defaults (e.g. Store) without a new migration.
158
+ // Idempotent — only backfills when topPadding/bottomPadding aren't already set.
159
+ runAlways: true,
160
+ description:
161
+ 'Padding Control backfill (BP-5699 Tier 2 + BP-5700 Store): backfill existing top/bottom spacing as explicit padding for text (12px), images/social links/shapes/booking (32px), store (16px), contact form (per layout), banner and about (per display style)',
162
+ },
152
163
  ] as MigrationTableItem[];
153
164
 
154
165
  export const GetLatestModelVersion = () =>
@@ -201,6 +201,34 @@ const SETTINGS_STYLE_FIElD_GROUPS = (): ContentSettingsFieldGroup[] => {
201
201
  ];
202
202
  };
203
203
 
204
+ /**
205
+ * Shared Top/Bottom padding sliders (BP-5687). Reuses the same `slider` control
206
+ * as the Divider block (track + editable number box), range 0–100, continuous.
207
+ * Spread into a block's `fields` to expose the Padding Control on that block.
208
+ * Pass the Style field-group id for blocks that use grouped panels.
209
+ */
210
+ const PADDING_CONTROL_FIELDS = (
211
+ groupId?: string,
212
+ ): {
213
+ topPadding: ContentSettingsField<Content>;
214
+ bottomPadding: ContentSettingsField<Content>;
215
+ } => ({
216
+ topPadding: {
217
+ type: 'slider',
218
+ title: contentSettingsTr(`topPaddingTitle`),
219
+ minLength: 0,
220
+ maxLength: 100,
221
+ groupId,
222
+ },
223
+ bottomPadding: {
224
+ type: 'slider',
225
+ title: contentSettingsTr(`bottomPaddingTitle`),
226
+ minLength: 0,
227
+ maxLength: 100,
228
+ groupId,
229
+ },
230
+ });
231
+
204
232
  const DEFAULT_TITLE_STYLE_SETTINGS_FILED =
205
233
  (): ContentSettingsField<Content> => ({
206
234
  type: 'text-style',
@@ -1097,6 +1125,7 @@ export const IMAGE_GALLERY_CONTENT_SETTINGS_CONFIG = (): ContentSettingsConfig<
1097
1125
  defaultValue: false,
1098
1126
  visibilityAssessor: imageGalleryFieldVisibilityAssessor,
1099
1127
  },
1128
+ ...PADDING_CONTROL_FIELDS(),
1100
1129
  },
1101
1130
  isEnabled: true,
1102
1131
  isLocked: (features) => !features.canAddImageContents,
@@ -1136,6 +1165,7 @@ export const VIDEO_CONTENT_SETTINGS_CONFIG = (): ContentSettingsConfig<
1136
1165
  label: contentSettingsTr(`muteLabel`),
1137
1166
  valueConvertor: videoContentStateConvertor,
1138
1167
  },
1168
+ ...PADDING_CONTROL_FIELDS(),
1139
1169
  },
1140
1170
  isEnabled: true,
1141
1171
  isLocked: (features) => !features.canAddVideoContents,
@@ -1278,6 +1308,7 @@ export const TEXT_CONTENT_SETTINGS_CONFIG = (): ContentSettingsConfig<
1278
1308
  variant: 'icon',
1279
1309
  visibilityAssessor: brandSiteTypeVisibilityAssessor,
1280
1310
  },
1311
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
1281
1312
  backgroundColor: {
1282
1313
  type: 'color',
1283
1314
  defaultValue: {},
@@ -1736,6 +1767,7 @@ export const MAP_CONTENT_SETTINGS_CONFIG =
1736
1767
  type: 'address',
1737
1768
  title: contentSettingsTr(`addressTitle`),
1738
1769
  },
1770
+ ...PADDING_CONTROL_FIELDS(),
1739
1771
  },
1740
1772
  isEnabled: true,
1741
1773
  isLocked: (features) => !features.canAddMapContents,
@@ -1787,6 +1819,7 @@ export const MAILING_LIST_CONTENT_SETTINGS_CONFIG =
1787
1819
  },
1788
1820
  titleStyle: DEFAULT_TITLE_STYLE_SETTINGS_FILED_WITH_TITLE(),
1789
1821
  buttonTextStyle: DEFAULT_BUTTON_TEXT_STYLE_FIELD(),
1822
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
1790
1823
  },
1791
1824
  isEnabled: true,
1792
1825
  isLocked: false,
@@ -1971,6 +2004,7 @@ export const REQUEST_PAYMENT_CONTENT_SETTINGS_CONFIG =
1971
2004
  defaultValue: {},
1972
2005
  label: contentSettingsTr(`buttonTextStyleLabel`),
1973
2006
  },
2007
+ ...PADDING_CONTROL_FIELDS(REQUEST_PAYMENT_GROUPS.style),
1974
2008
  },
1975
2009
  isEnabled: (features) => features.canAddRequestPaymentContents,
1976
2010
  isLocked: false,
@@ -2038,6 +2072,7 @@ export const SUPPORT_ME_CONTENT_SETTINGS_CONFIG =
2038
2072
  label: contentSettingsTr(`itemStyleLabel`),
2039
2073
  },
2040
2074
  buttonTextStyle: DEFAULT_BUTTON_TEXT_STYLE_FIELD(),
2075
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2041
2076
  },
2042
2077
  isEnabled: (features) => features.canAddRequestPaymentContents,
2043
2078
  isLocked: false,
@@ -2170,6 +2205,7 @@ export const CONTACT_FORM_SETTINGS_CONFIG =
2170
2205
  variant: 'icon',
2171
2206
  visibilityAssessor: brandSiteTypeVisibilityAssessor,
2172
2207
  },
2208
+ ...PADDING_CONTROL_FIELDS(CONTACT_FORM_GROUPS.style),
2173
2209
  titleStyle: DEFAULT_TITLE_STYLE_SETTINGS_FILED_WITH_TITLE(),
2174
2210
  descriptionStyle: DEFAULT_DESCRIPTION_STYLE_FILED(),
2175
2211
  buttonTextStyle: DEFAULT_BUTTON_TEXT_STYLE_FIELD(),
@@ -2229,6 +2265,7 @@ export const BANNER_SECTION_SETTINGS_CONFIG =
2229
2265
  groupId: SETTINGS_STYLE_FIELD_GROUP_IDS.style,
2230
2266
  variant: 'icon',
2231
2267
  },
2268
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2232
2269
  overlayColor: {
2233
2270
  type: 'color',
2234
2271
  title: contentSettingsTr(`overlayColorTitle`),
@@ -2507,6 +2544,7 @@ export const ABOUT_SECTION_SETTINGS_CONFIG =
2507
2544
  groupId: SETTINGS_STYLE_FIELD_GROUP_IDS.style,
2508
2545
  variant: 'icon',
2509
2546
  },
2547
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2510
2548
  titleStyle: DEFAULT_TITLE_STYLE_SETTINGS_FILED_WITH_TITLE(),
2511
2549
  paragraphStyle: {
2512
2550
  type: 'text-style',
@@ -2594,6 +2632,7 @@ export const IMAGES_SECTION_SETTINGS_CONFIG =
2594
2632
  ],
2595
2633
  fields: {
2596
2634
  displayStyle: TEXT_MEDIA_DISPLAY_STYLE_FIELD(MEDIA_TYPES.image),
2635
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2597
2636
  aspectRatio: {
2598
2637
  type: 'radio',
2599
2638
  role: 'multi',
@@ -2681,6 +2720,7 @@ export const TESTIMONIALS_SECTION_SETTINGS_CONFIG =
2681
2720
  groupId: SETTINGS_STYLE_FIELD_GROUP_IDS.style,
2682
2721
  variant: 'icon',
2683
2722
  },
2723
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2684
2724
  title: {
2685
2725
  type: 'text',
2686
2726
  placeholder: contentSettingsTr(`titlePlaceholder`),
@@ -2793,6 +2833,7 @@ export const SOCIAL_LINKS_SETTINGS_CONFIG =
2793
2833
  optionsConvertor: socialLinksIconDisplayStyleConvertor,
2794
2834
  variant: 'icon',
2795
2835
  },
2836
+ ...PADDING_CONTROL_FIELDS(SOCIAL_LINKS_GROUPS.style),
2796
2837
  titleStyle: {
2797
2838
  type: 'text-style',
2798
2839
  groupId: SOCIAL_LINKS_GROUPS.style,
@@ -2905,6 +2946,7 @@ export const SHAPES_SECTION_SETTINGS_CONFIG =
2905
2946
  ],
2906
2947
  fields: {
2907
2948
  displayStyle: TEXT_MEDIA_DISPLAY_STYLE_FIELD(MEDIA_TYPES.shape),
2949
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2908
2950
  size: IMAGE_SIZE_FIELD(),
2909
2951
  title: TEXT_MEDIA_TITLE_FIELD(),
2910
2952
  description: TEXT_MEDIA_DESCRIPTION_FIELD(),
@@ -2942,6 +2984,7 @@ export const SHAPES_SECTION_SETTINGS_CONFIG =
2942
2984
  ? MAX_BS_MEDIA_AND_IMAGE_ITEMS
2943
2985
  : MAX_BP_MEDIA_AND_IMAGE_ITEMS,
2944
2986
  },
2987
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
2945
2988
  },
2946
2989
  isEnabled: true,
2947
2990
  isLocked: false,
@@ -3003,6 +3046,7 @@ export const SHOP_SECTION_SETTINGS_CONFIG = (): ContentSettingsConfig<
3003
3046
  optionsConvertor: shopAspectRatioConvertor,
3004
3047
  visibilityAssessor: shopImageOptionVisibilityAssessor,
3005
3048
  },
3049
+ ...PADDING_CONTROL_FIELDS(REQUEST_PAYMENT_GROUPS.style),
3006
3050
  title: {
3007
3051
  type: 'title',
3008
3052
  groupId: REQUEST_PAYMENT_GROUPS.settings,
@@ -3166,6 +3210,7 @@ export const BOOKING_SECTION_SETTINGS_CONFIG =
3166
3210
  visibilityAssessor: (c, b) =>
3167
3211
  personalisationOverrideVisibility(c, b, 'buttonText'),
3168
3212
  },
3213
+ ...PADDING_CONTROL_FIELDS(SETTINGS_STYLE_FIELD_GROUP_IDS.style),
3169
3214
  backgroundStyle: {
3170
3215
  type: 'color',
3171
3216
  groupId: SETTINGS_STYLE_FIELD_GROUP_IDS.style,
@@ -6,6 +6,7 @@ import {
6
6
  ALIGN_RIGHT_ALIGNED,
7
7
  ALIGN_RIGHT_FULL_IMAGE,
8
8
  TEXT_ONLY,
9
+ ABOUT_DEFAULT_PADDING,
9
10
  } from '@brand-page/constants';
10
11
 
11
12
  import type { AboutSectionContent } from '@brand-page';
@@ -76,11 +77,18 @@ export const aboutDisplayStyleConvertor: ValueConvertor<
76
77
  convertBack(config: ButtonGroupConfig): Partial<AboutSectionContent> {
77
78
  const selectedOption = config.options.find((v) => v.isSelected);
78
79
 
80
+ // Switching display style adopts that style's canonical look — like the
81
+ // textAlign reset below — including its default block padding (a flat 32
82
+ // for every About style).
83
+ const padding = ABOUT_DEFAULT_PADDING;
84
+
79
85
  if (!selectedOption || selectedOption.value === ALIGN_CENTERED) {
80
86
  const textAlign = 'center';
81
87
 
82
88
  return {
83
89
  displayStyle: selectedOption?.value,
90
+ topPadding: padding,
91
+ bottomPadding: padding,
84
92
  titleStyle: {
85
93
  textAlign,
86
94
  },
@@ -94,6 +102,8 @@ export const aboutDisplayStyleConvertor: ValueConvertor<
94
102
 
95
103
  return {
96
104
  displayStyle: selectedOption?.value,
105
+ topPadding: padding,
106
+ bottomPadding: padding,
97
107
  titleStyle: {
98
108
  textAlign,
99
109
  },
@@ -15,6 +15,7 @@ import {
15
15
  ALIGN_TOP_FULL_IMAGE,
16
16
  ALIGN_BOTTOM_FULL_IMAGE,
17
17
  ALIGN_MIDDLE_FULL_IMAGE,
18
+ bannerDisplayStyleDefaultPadding,
18
19
  } from '@brand-page/constants';
19
20
  import type { BannerSectionContent } from '@brand-page';
20
21
 
@@ -172,8 +173,15 @@ export const bannerAlignDisplayStyleConvertor: ValueConvertor<
172
173
  textAlign = 'center';
173
174
  }
174
175
 
176
+ // Switching display style adopts that style's canonical look — like the
177
+ // textAlign resets above — including its default block padding. Full-bleed
178
+ // image styles default to 0 (image flush to the edges); the rest to 32.
179
+ const padding = bannerDisplayStyleDefaultPadding(selectedOption?.value);
180
+
175
181
  return {
176
182
  displayStyle: selectedOption?.value,
183
+ topPadding: padding,
184
+ bottomPadding: padding,
177
185
  titleStyle: {
178
186
  textAlign,
179
187
  },
@@ -0,0 +1,50 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { contactLayoutStyleConvertor } from './contact-layout-style.convertor';
3
+ import {
4
+ ALIGN_CENTERED,
5
+ ALIGN_CARD_CENTER,
6
+ ALIGN_CARD_CENTER_IMAGE,
7
+ ALIGN_LEFT_FULL_IMAGE,
8
+ TEXT_ONLY,
9
+ } from '@brand-page/constants';
10
+
11
+ const configWithSelected = (selected: string) => ({
12
+ options: contactLayoutStyleConvertor
13
+ .convert(selected)
14
+ .options.map((option) => ({
15
+ ...option,
16
+ isSelected: option.value === selected,
17
+ })),
18
+ });
19
+
20
+ describe('contact-layout-style.convertor', () => {
21
+ test.each([
22
+ [ALIGN_CARD_CENTER, 32, 'center'],
23
+ [ALIGN_CENTERED, 32, 'center'],
24
+ [ALIGN_CARD_CENTER_IMAGE, 0, 'center'],
25
+ [TEXT_ONLY, 32, 'left'],
26
+ [ALIGN_LEFT_FULL_IMAGE, 0, 'left'],
27
+ ])(
28
+ 'convertBack should apply the default padding and text alignment for %s',
29
+ (layoutStyle, padding, textAlign) => {
30
+ const result = contactLayoutStyleConvertor.convertBack(
31
+ configWithSelected(layoutStyle),
32
+ );
33
+
34
+ expect(result).toStrictEqual({
35
+ layoutStyle,
36
+ topPadding: padding,
37
+ bottomPadding: padding,
38
+ titleStyle: {
39
+ textAlign,
40
+ },
41
+ descriptionStyle: {
42
+ textAlign,
43
+ },
44
+ buttonTextStyle: {
45
+ textAlign: 'center',
46
+ },
47
+ });
48
+ },
49
+ );
50
+ });