@designcrowd/library.brand-page 5.19.1-error-html2canvas-2 → 5.19.1-id-based-items-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 (371) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DmKe7W8L.js → CreatedOnBrandCrowd-de-de-0PcGqy0N.js} +3 -3
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-D2EeiNep.js → CreatedOnBrandCrowd-en-us-DB8wcFMI.js} +3 -3
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BqVwNY1D.js → CreatedOnBrandCrowd-es-es-DVWQ243C.js} +3 -3
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-C756a27-.js → CreatedOnBrandCrowd-fr-ca-D-M_L0Du.js} +3 -3
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-C756a27-.js → CreatedOnBrandCrowd-fr-fr-D-M_L0Du.js} +3 -3
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-FNHAnZUw.js → CreatedOnBrandCrowd-pt-br-CNpzknxU.js} +3 -3
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-FNHAnZUw.js → CreatedOnBrandCrowd-pt-pt-CNpzknxU.js} +3 -3
  8. package/lib/src/{CreatedOnDcom-de-de-8AZZFmDt.js → CreatedOnDcom-de-de-DMJoD5wE.js} +3 -3
  9. package/lib/src/{CreatedOnDcom-en-us-DizTP32i.js → CreatedOnDcom-en-us-B-F3Pmjv.js} +3 -3
  10. package/lib/src/{CreatedOnDcom-es-es-qnp9_n_A.js → CreatedOnDcom-es-es-DmLk-cA2.js} +3 -3
  11. package/lib/src/{CreatedOnDcom-fr-ca-jUs352uK.js → CreatedOnDcom-fr-ca-ntnsCzvA.js} +3 -3
  12. package/lib/src/{CreatedOnDcom-fr-fr-CTxXtHyk.js → CreatedOnDcom-fr-fr-DFcO0gHL.js} +3 -3
  13. package/lib/src/{CreatedOnDcom-pt-br-nQHj8P7d.js → CreatedOnDcom-pt-br-CtuJoVXW.js} +3 -3
  14. package/lib/src/{CreatedOnDcom-pt-pt-DJmVYQuL.js → CreatedOnDcom-pt-pt-B7shZVVn.js} +3 -3
  15. package/lib/src/brand-page/clients/brand-page.client.d.ts +9 -9
  16. package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +2 -1
  17. package/lib/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.d.ts +2 -4
  18. package/lib/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.js +0 -1
  19. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +2 -4
  20. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +4 -5
  21. package/lib/src/brand-page/components/page/Page.mixin.d.ts +1 -1
  22. package/lib/src/brand-page/components/page/PageComponent.mixin.d.ts +1 -1
  23. package/lib/src/brand-page/components/payment/Payment.mixin.d.ts +3 -3
  24. package/lib/src/brand-page/components/payment/__fixtures__/request-payment.d.ts +13 -13
  25. package/lib/src/brand-page/components/payment/__fixtures__/support-me.d.ts +13 -13
  26. package/lib/src/brand-page/constants/sections/footer-display.constants.js +1 -1
  27. package/lib/src/brand-page/models/index.d.ts +13 -2
  28. package/lib/src/brand-page/models/index.js +0 -3
  29. package/lib/src/brand-page/models/payment.model.d.ts +1 -1
  30. package/lib/src/brand-page/utils/personalisation.util.js +14 -72
  31. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +19 -21
  32. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.js +0 -1
  33. package/lib/src/brand-page-maker/components/admin-background-settings/AdminBackgroundSettings.mixin.d.ts +10 -16
  34. package/lib/src/brand-page-maker/components/admin-logo-settings/AdminLogoSettings.mixin.d.ts +3 -3
  35. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +170 -181
  36. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +0 -3
  37. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.js +0 -1
  38. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +12 -12
  39. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.stories.js +2 -2
  40. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-payment.js +7 -0
  41. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +149 -148
  42. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +21 -21
  43. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.js +1 -1
  44. package/lib/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.d.ts +2 -4
  45. package/lib/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.js +0 -1
  46. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.d.ts +2 -4
  47. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.js +0 -1
  48. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.d.ts +2 -5
  49. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.js +0 -1
  50. package/lib/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.d.ts +2 -4
  51. package/lib/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.js +0 -1
  52. package/lib/src/brand-page-maker/components/device/Device.mixin.d.ts +2 -4
  53. package/lib/src/brand-page-maker/components/device/Device.mixin.js +0 -1
  54. package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.d.ts +2 -4
  55. package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.js +0 -1
  56. package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +3 -3
  57. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +11 -11
  58. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +25 -27
  59. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.js +0 -1
  60. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +27 -27
  61. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.js +1 -1
  62. package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +12 -17
  63. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +12 -12
  64. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +32 -30
  65. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +5 -16
  66. package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +11 -11
  67. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +1 -1
  68. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
  69. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +6 -6
  70. package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +2 -2
  71. package/lib/src/brand-page-maker/constants/config-settings-constant.d.ts +6 -6
  72. package/lib/src/brand-page-maker/constants/config-settings-constant.js +1 -1
  73. package/lib/src/brand-page-maker/convertors/text-image-display-style.convertor.d.ts +1 -1
  74. package/lib/src/brand-page-maker/convertors/text-image-display-style.convertor.js +1 -1
  75. package/lib/src/brand-page-maker/models/content-settings.d.ts +1 -1
  76. package/lib/src/brand-page-maker/models/navigation-style-configs.js +1 -1
  77. package/lib/src/brand-page-maker/routing/brand-page-maker-router.d.ts +2 -1
  78. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +378 -366
  79. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +4 -5
  80. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +5 -8
  81. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +1 -3
  82. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +3 -7
  83. package/lib/src/brand-page-maker/stores/history/history-store.d.ts +15 -15
  84. package/lib/src/brand-page-maker/stores/ui/ui-store.d.ts +0 -6
  85. package/lib/src/brand-page-maker/stores/ui/ui-store.js +0 -8
  86. package/lib/src/brand-page-maker/utils/content-settings.util.js +1 -1
  87. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +1 -1
  88. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +4 -4
  89. package/lib/src/clients/brand-page-client.d.ts +15 -16
  90. package/lib/src/clients/design-template-client.d.ts +0 -3
  91. package/lib/src/clients/media-client.d.ts +2 -2
  92. package/lib/src/clients/models.d.ts +0 -2
  93. package/lib/src/composables/useSnapshot.js +18 -18
  94. package/lib/src/css/library.brand-page-brandCrowd.min.css +324 -342
  95. package/lib/src/css/library.brand-page-brandPage.min.css +324 -620
  96. package/lib/src/css/library.brand-page-designCom.min.css +324 -342
  97. package/lib/src/css/library.brand-page-preview.min.css +324 -598
  98. package/lib/src/index.d.ts +1 -1
  99. package/lib/src/index.mjs +15 -16
  100. package/lib/src/{lib-DityivZa.js → lib-BQmJBVbS.js} +33238 -36458
  101. package/lib/src/lib.js +1 -1
  102. package/lib/src/metadata-builder/builders/footer-builder.js +56 -91
  103. package/lib/src/metadata-builder/builders/item-id-utils.d.ts +41 -0
  104. package/lib/src/metadata-builder/builders/item-id-utils.js +68 -0
  105. package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +57 -68
  106. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +58 -93
  107. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +66 -99
  108. package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +57 -17
  109. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +51 -85
  110. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +49 -74
  111. package/lib/src/metadata-builder/builders/shape-color-utils.js +1 -1
  112. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +13 -4
  113. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +66 -48
  114. package/lib/src/plugins/google-maps-plugin/google-maps-plugin.d.ts +1 -1
  115. package/lib/src/plugins/google-maps-plugin/google-maps-plugin.js +8 -8
  116. package/lib/src/shared/background-settings/components/background-shapes/BackgroundShapeSettings.mixin.d.ts +13 -13
  117. package/lib/src/shared/background-settings/store/background-settings-store.d.ts +3 -3
  118. package/lib/src/shared/color-picker/components/ColorPicker.mixin.d.ts +2 -4
  119. package/lib/src/shared/color-picker/components/ColorPicker.mixin.js +2 -2
  120. package/lib/src/shared/color-picker/components/ColorPickerSwatch.mixin.d.ts +2 -4
  121. package/lib/src/shared/color-picker/components/ColorPickerSwatch.mixin.js +0 -1
  122. package/lib/src/shared/confirm-modal/ConfirmModal.mixin.d.ts +2 -5
  123. package/lib/src/shared/confirm-modal/ConfirmModal.mixin.js +0 -1
  124. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -0
  125. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +129 -131
  126. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +1 -2
  127. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +110 -111
  128. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +81 -84
  129. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.js +0 -5
  130. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +109 -117
  131. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +14 -1
  132. package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -0
  133. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +70 -72
  134. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.js +3 -4
  135. package/lib/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.d.ts +4 -4
  136. package/lib/src/shared/content-settings/components/icon-library/IconLibrary.mixin.d.ts +1 -3
  137. package/lib/src/shared/content-settings/components/icon-library/IconLibrary.mixin.js +0 -1
  138. package/lib/src/shared/content-settings/components/images-field/ImagesField.mixin.d.ts +2 -2
  139. package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.d.ts +3 -4
  140. package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.js +1 -1
  141. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +40 -40
  142. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigOptionField.mixin.d.ts +6 -5
  143. package/lib/src/shared/content-settings/components/shape-field/ShapeField.mixin.d.ts +1 -0
  144. package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.d.ts +2 -2
  145. package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -0
  146. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -1
  147. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -0
  148. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +2 -1
  149. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -1
  150. package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.d.ts +28 -28
  151. package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -0
  152. package/lib/src/shared/content-settings/components/toggle-field/ToggleField.mixin.d.ts +2 -2
  153. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -0
  154. package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -0
  155. package/lib/src/shared/content-settings/store/content-settings-store.d.ts +39 -39
  156. package/lib/src/shared/content-settings/utils.js +1 -0
  157. package/lib/src/shared/font-family-selector/FontFamilySelector.mixin.d.ts +4 -6
  158. package/lib/src/shared/font-family-selector/FontFamilySelector.mixin.js +0 -1
  159. package/lib/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.d.ts +2 -4
  160. package/lib/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.js +0 -1
  161. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +21 -29
  162. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.js +1 -1
  163. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +51 -50
  164. package/lib/src/shared/logo-settings/components/LogoSettingsHeader.mixin.d.ts +4 -6
  165. package/lib/src/shared/logo-settings/components/LogoSettingsHeader.mixin.js +0 -1
  166. package/lib/src/shared/logo-settings/store/editable-logo-store.js +1 -1
  167. package/lib/src/shared/logo-settings/store/logo-settings-store.d.ts +1 -1
  168. package/lib/src/shared/logo-versions/LogoVersions.mixin.d.ts +5 -13
  169. package/lib/src/shared/logo-versions/LogoVersions.mixin.js +0 -1
  170. package/lib/src/shared/models.d.ts +0 -2
  171. package/lib/src/shared/my-designs/MyDesigns.mixin.d.ts +6 -14
  172. package/lib/src/shared/my-designs/MyDesigns.mixin.js +0 -1
  173. package/lib/src/shared/my-uploads/MyMedia.mixin.d.ts +5 -14
  174. package/lib/src/shared/my-uploads/MyMedia.mixin.js +0 -1
  175. package/lib/src/shared/my-uploads/MyUploads.mixin.d.ts +5 -14
  176. package/lib/src/shared/my-uploads/MyUploads.mixin.js +0 -1
  177. package/lib/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.d.ts +3 -5
  178. package/lib/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.js +0 -1
  179. package/lib/src/shared/navigation-style-settings/store/index.d.ts +2 -2
  180. package/lib/src/shared/page-navigation/store/page-navigation-store.d.ts +2 -2
  181. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +28 -27
  182. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.js +2 -0
  183. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +179 -188
  184. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +177 -181
  185. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +1 -1
  186. package/lib/src/shared/page-settings/store/page-settings-store.d.ts +9 -9
  187. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +39 -39
  188. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.js +1 -0
  189. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +18 -18
  190. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +6 -6
  191. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +60 -66
  192. package/lib/src/shared/style-settings/store/style-settings-store.d.ts +1 -1
  193. package/lib/src/shared/style-settings/store/style-settings-store.js +1 -1
  194. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +450 -20
  195. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.js +2 -2
  196. package/lib/src/shared/template-settings/models.d.ts +0 -1
  197. package/lib/src/shared/template-settings/store/template-settings-store.d.ts +0 -4
  198. package/lib/src/shared/template-settings/store/template-settings-store.js +11 -79
  199. package/lib/src/shared/unsplash-library/UnsplashLibrary.mixin.d.ts +4 -9
  200. package/package.json +57 -61
  201. package/src/brand-page/components/about-section/AboutSection.vue.test.ts +5 -7
  202. package/src/brand-page/components/brand-page-footer/BrandPageFooter.vue +1 -1
  203. package/src/brand-page/components/image/Image.mixin.ts +1 -1
  204. package/src/brand-page/components/image/Image.test.ts +1 -1
  205. package/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.ts +2 -1
  206. package/src/brand-page/components/images-section/ImagesSection.vue +1 -0
  207. package/src/brand-page/components/logo/LogoRender.vue +188 -188
  208. package/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.ts +0 -1
  209. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +6 -15
  210. package/src/brand-page/components/page-footer/PageFooter.vue +0 -1
  211. package/src/brand-page/components/page-header/HeaderLayout.vue +6 -12
  212. package/src/brand-page/components/page-header/PageHeader.vue +0 -1
  213. package/src/brand-page/components/shapes-section/ShapesSection.vue +1 -1
  214. package/src/brand-page/components/shop/CartModal.vue +3 -1
  215. package/src/brand-page/components/shop/FeaturedDisplay.vue +2 -2
  216. package/src/brand-page/components/shop/PaymentModal.vue +5 -1
  217. package/src/brand-page/components/shop/ProductDetailModal.vue +1 -1
  218. package/src/brand-page/components/shop/Shop.vue +1 -1
  219. package/src/brand-page/components/shop/TextDisplay.vue +1 -1
  220. package/src/brand-page/components/shop/ThumbnailDisplay.vue +2 -2
  221. package/src/brand-page/components/testimonial-section/TestimonialSection.vue +3 -3
  222. package/src/brand-page/components/text-wrapper/TextWrapper.vue +1 -3
  223. package/src/brand-page/constants/sections/footer-display.constants.ts +1 -1
  224. package/src/brand-page/models/index.ts +12 -4
  225. package/src/brand-page/models/payment.model.ts +1 -2
  226. package/src/brand-page/models/shop.model.ts +2 -0
  227. package/src/brand-page/models/user-personalisation.model.ts +2 -1
  228. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +0 -3
  229. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +0 -1
  230. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +0 -2
  231. package/src/brand-page/utils/migration/migration-01_1.ts +1 -1
  232. package/src/brand-page/utils/migration/migration-02.ts +1 -0
  233. package/src/brand-page/utils/migration/migration-03.ts +1 -0
  234. package/src/brand-page/utils/migration/migration-04.ts +1 -0
  235. package/src/brand-page/utils/migration/migration-05.ts +5 -1
  236. package/src/brand-page/utils/migration/migration-05_2.ts +1 -1
  237. package/src/brand-page/utils/migration/migration-08.test.ts +1 -1
  238. package/src/brand-page/utils/personalisation.util.test.ts +0 -35
  239. package/src/brand-page/utils/personalisation.util.ts +23 -119
  240. package/src/brand-page/view-models.ts +56 -38
  241. package/src/brand-page-maker/assessors/banner-section-description-visibility.assessor.ts +6 -6
  242. package/src/brand-page-maker/assessors/color-replacement-icon-visibility.assessor.ts +2 -2
  243. package/src/brand-page-maker/assessors/color-replacement-profile-visibility.assessor.ts +2 -2
  244. package/src/brand-page-maker/assessors/color-replacement-visibility.assessor.ts +2 -2
  245. package/src/brand-page-maker/assessors/contact-form-visibility.assessor.ts +14 -14
  246. package/src/brand-page-maker/components/action-bar/ActionBar.mixin.ts +0 -1
  247. package/src/brand-page-maker/components/ai-text-edit/TextBubble.vue +0 -1
  248. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +1 -5
  249. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.ts +0 -1
  250. package/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.stories.ts +5 -2
  251. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +5 -0
  252. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-payment.ts +8 -0
  253. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-site-template.examples.ts +2 -0
  254. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.vue +0 -1
  255. package/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.ts +1 -1
  256. package/src/brand-page-maker/components/color-palette/BpColorPalette.vue +2 -1
  257. package/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.ts +0 -1
  258. package/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.ts +0 -1
  259. package/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.ts +0 -1
  260. package/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.ts +0 -1
  261. package/src/brand-page-maker/components/device/Device.mixin.ts +0 -1
  262. package/src/brand-page-maker/components/navigation/Navigation.mixin.ts +0 -1
  263. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +1 -3
  264. package/src/brand-page-maker/components/sensitive-content/SensitiveContent.vue +4 -1
  265. package/src/brand-page-maker/components/seo-settings/DeleteDomainModal.vue +1 -1
  266. package/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.ts +0 -1
  267. package/src/brand-page-maker/components/seo-settings/SeoPageSettings.vue +1 -1
  268. package/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.ts +1 -0
  269. package/src/brand-page-maker/components/seo-settings/UrlSettings.vue +1 -0
  270. package/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.vue +5 -1
  271. package/src/brand-page-maker/components/shop/AddOrEditProductContainer.vue +1 -1
  272. package/src/brand-page-maker/components/shop/ReplaceImageSettingsForShop.vue +1 -1
  273. package/src/brand-page-maker/components/social-share/SocialShare.vue +1 -1
  274. package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +8 -22
  275. package/src/brand-page-maker/components/template-preview/TemplatePreview.vue +10 -5
  276. package/src/brand-page-maker/components/video-play/VideoPlayButton.vue +1 -1
  277. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +1 -1
  278. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +6 -6
  279. package/src/brand-page-maker/constants/config-settings-constant.ts +3 -3
  280. package/src/brand-page-maker/convertors/text-image-display-style.convertor.ts +1 -1
  281. package/src/brand-page-maker/models/button-styles-config.ts +2 -4
  282. package/src/brand-page-maker/models/content-settings-configs.ts +2 -4
  283. package/src/brand-page-maker/models/navigation-style-configs.ts +6 -5
  284. package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.ts +1 -0
  285. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +5 -8
  286. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +3 -1
  287. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +4 -12
  288. package/src/brand-page-maker/stores/ui/ui-store.ts +0 -16
  289. package/src/brand-page-maker/utils/content-settings.util.ts +1 -1
  290. package/src/brand-page-maker/utils/rich-text-editor.util.ts +4 -4
  291. package/src/clients/design-template-client.ts +0 -3
  292. package/src/clients/models.ts +0 -2
  293. package/src/composables/useSnapshot.ts +22 -19
  294. package/src/lib.ts +0 -1
  295. package/src/metadata-builder/builders/footer-builder.test.ts +245 -84
  296. package/src/metadata-builder/builders/footer-builder.ts +81 -130
  297. package/src/metadata-builder/builders/item-id-utils.test.ts +118 -0
  298. package/src/metadata-builder/builders/item-id-utils.ts +85 -0
  299. package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +62 -88
  300. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +66 -129
  301. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +105 -160
  302. package/src/metadata-builder/builders/section-builders/social-links-builder.ts +65 -20
  303. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +83 -146
  304. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +70 -122
  305. package/src/metadata-builder/builders/shape-color-utils.ts +2 -2
  306. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +13 -4
  307. package/src/metadata-builder/parsers/section-tool-arguments.ts +66 -48
  308. package/src/plugins/google-maps-plugin/google-maps-plugin.ts +8 -8
  309. package/src/shared/background-settings/components/BackgroundSettings.vue +1 -1
  310. package/src/shared/background-settings/components/background-color/BackgroundColorSettings.vue +1 -1
  311. package/src/shared/color-picker/components/ColorPicker.mixin.ts +7 -2
  312. package/src/shared/color-picker/components/ColorPickerSwatch.mixin.ts +0 -1
  313. package/src/shared/common/NumberInput.vue +0 -1
  314. package/src/shared/common/SideBarSettingsHeader.vue +2 -2
  315. package/src/shared/confirm-modal/ConfirmModal.mixin.ts +0 -1
  316. package/src/shared/content-settings/components/booking-url-field/BookingUrlField.vue +0 -1
  317. package/src/shared/content-settings/components/button-configuration-field/ButtonConfigurationField.vue +0 -1
  318. package/src/shared/content-settings/components/color-field/ColorField.vue +10 -3
  319. package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +1 -1
  320. package/src/shared/content-settings/components/content-settings/ContentSettingsLockedButton.vue +0 -1
  321. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.ts +0 -5
  322. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +14 -1
  323. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.ts +6 -4
  324. package/src/shared/content-settings/components/icon-library/IconLibrary.mixin.ts +0 -1
  325. package/src/shared/content-settings/components/items-field/ItemsField.mixin.ts +1 -1
  326. package/src/shared/content-settings/components/logo-selection-field/LogoSelectionField.vue +1 -1
  327. package/src/shared/content-settings/components/logo-size-field/LogoSizeSliderField.vue +1 -1
  328. package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +1 -1
  329. package/src/shared/content-settings/components/personalisation-role-field/PersonalisationRoleField.vue +4 -4
  330. package/src/shared/content-settings/components/text-image-field/TextImageField.vue +8 -2
  331. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -1
  332. package/src/shared/content-settings/models.ts +2 -3
  333. package/src/shared/content-settings/utils.ts +1 -0
  334. package/src/shared/dropzone-image-uploader/components/DropzoneImageUploader.vue +0 -1
  335. package/src/shared/ecommerce-settings/store/cart-store.test.ts +1 -1
  336. package/src/shared/font-family-selector/FontFamilySelector.mixin.ts +0 -1
  337. package/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.ts +0 -1
  338. package/src/shared/logo-settings/components/EditLogoSettings.mixin.ts +1 -1
  339. package/src/shared/logo-settings/components/LogoSettings.mixin.ts +1 -0
  340. package/src/shared/logo-settings/components/LogoSettingsHeader.mixin.ts +0 -1
  341. package/src/shared/logo-settings/components/LogoStyleSettingsWithState.vue +2 -2
  342. package/src/shared/logo-settings/components/LogoTemplateSearch.vue +1 -1
  343. package/src/shared/logo-settings/store/editable-logo-store.ts +1 -1
  344. package/src/shared/logo-settings/store/logo-settings-store.test.ts +4 -4
  345. package/src/shared/logo-versions/LogoVersions.mixin.ts +0 -1
  346. package/src/shared/models.ts +0 -6
  347. package/src/shared/my-designs/MyDesigns.mixin.ts +0 -1
  348. package/src/shared/my-uploads/MyMedia.mixin.ts +0 -1
  349. package/src/shared/my-uploads/MyMediaSelectionItem.vue +1 -2
  350. package/src/shared/my-uploads/MyUploads.mixin.ts +0 -1
  351. package/src/shared/my-uploads/models.ts +2 -1
  352. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.ts +0 -1
  353. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.vue +1 -1
  354. package/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.ts +3 -0
  355. package/src/shared/page-settings/components/page-settings/NestedDraggable.vue +1 -1
  356. package/src/shared/page-settings/components/page-settings/PageSettings.vue +2 -1
  357. package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.vue +1 -1
  358. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +1 -1
  359. package/src/shared/page-settings/store/page-settings-store.test.ts +8 -4
  360. package/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.ts +1 -0
  361. package/src/shared/shape-library/ShapeLibrary.vue +2 -2
  362. package/src/shared/style-settings/store/style-settings-store.ts +1 -1
  363. package/src/shared/template-settings/components/TemplatesSettings.mixin.ts +2 -2
  364. package/src/shared/template-settings/models.ts +0 -1
  365. package/src/shared/template-settings/store/template-settings-store.ts +18 -105
  366. package/src/shared/unsplash-library/UnsplashLibrary.vue +6 -2
  367. package/src/shared/video-library/VideoLibrary.vue +1 -1
  368. package/lib/src/brand-page/components/banner-section/BannerSection.mixin.d.ts +0 -103
  369. package/lib/src/brand-page/components/banner-section/BannerSection.mixin.js +0 -178
  370. package/src/brand-page/components/banner-section/BannerSection.mixin.ts +0 -210
  371. package/src/brand-page-maker/components/template-preview/TemplatePreviewAiBadge.vue +0 -12
package/lib/src/lib.js CHANGED
@@ -32,7 +32,7 @@ export { getOptimalLogoSize, calculateOptimizedLogoDimensions, applyLogoImageBas
32
32
  export { sanitizeRTEHtml } from './brand-page-maker/utils';
33
33
  export { getPageSlugFromPageTitle } from './brand-page-maker/utils/slug.util';
34
34
  // stores
35
- export { useBrandPageMakerStore, useBrandPageMakerUserStore, useUiStore, useBrandPageContainerStore, useEventManagerStore, useLocalisedRouteStore, useVideoStore, useRouteStore, } from './brand-page-maker/stores';
35
+ export { useBrandPageMakerStore, useBrandPageMakerUserStore, useUiStore, useBrandPageContainerStore, useEventManagerStore, useLocalisedRouteStore, useVideoStore, } from './brand-page-maker/stores';
36
36
  export { useShopConfigStore } from './shared/ecommerce-settings/store/shop-settings-store';
37
37
  // for jest, need to set active pinia in library.brand-page as well
38
38
  import { createPinia, setActivePinia } from 'pinia';
@@ -2,6 +2,7 @@ import {} from '../../brand-page/models';
2
2
  import { BaseMetadataBuilder, } from '../models';
3
3
  import { FOOTER_CHECKS } from './section-rules/footer.rules';
4
4
  import { applyDetachedStyleOverride } from './section-field-utils';
5
+ import { ensureItemIds, generateItemId, removeItemsByIds, repositionItemByAnchor, } from './item-id-utils';
5
6
  const normaliseSocialUrl = (url) => {
6
7
  for (const prefix of ['mailto:', 'tel:']) {
7
8
  if (url.startsWith(prefix))
@@ -9,88 +10,65 @@ const normaliseSocialUrl = (url) => {
9
10
  }
10
11
  return url;
11
12
  };
12
- const makeSocialLink = (icon, url) => ({
13
- imageUrl: icon || '',
14
- url: normaliseSocialUrl(url || ''),
15
- imageTitle: '',
16
- width: 0,
17
- height: 0,
18
- imageLink: '',
19
- });
20
- const hasSocialLinkMutation = (args) => args._replace_social_links === true ||
21
- (args._remove_social_link_indices?.length ?? 0) > 0 ||
22
- (args._reorder_social_links?.length ?? 0) > 0 ||
23
- (args.social_links?.length ?? 0) > 0;
24
- /**
25
- * Social links are a GLOBAL footer concern — one logical list that both
26
- * viewports render identically. Derive the new list ONCE from the current one
27
- * WITHOUT mutating the input, so the caller can assign an independent copy to
28
- * each viewport.
29
- *
30
- * Why this can't mutate per-viewport in place: footers migrated from the legacy
31
- * single `footerStyles` model (migration-01) share ONE `socialLinks` array
32
- * reference across pageFooterDesktop and pageFooterMobile, and `cloneDeep`
33
- * preserves that shared reference. Running an in-place `splice` once per
34
- * viewport therefore applies it TWICE to the same array — e.g. one requested
35
- * removal index drops two links. Compute-once + assign-fresh is correct for
36
- * both shared (legacy) and independent (new-model) footers.
37
- */
38
- const computeSocialLinks = (current, args) => {
39
- // _replace_social_links: replace entire list (empty/absent social_links = clear all).
40
- // A replacement supersedes every other social link mutation.
41
- if (args._replace_social_links) {
42
- return (args.social_links ?? []).map((l) => makeSocialLink(l.social_link_icon, l.url));
43
- }
44
- const links = [...(current ?? [])];
45
- // item_index-based updates for existing links — replace the item reference
46
- // (never mutate the original object, which may be shared across viewports).
47
- for (const link of args.social_links ?? []) {
48
- if (link.item_index === undefined)
49
- continue;
50
- const existing = links[link.item_index];
51
- if (!existing)
52
- continue;
53
- links[link.item_index] = {
54
- ...existing,
55
- ...(link.social_link_icon !== undefined
56
- ? { imageUrl: link.social_link_icon }
57
- : {}),
58
- ...(link.url !== undefined ? { url: normaliseSocialUrl(link.url) } : {}),
59
- };
60
- }
61
- // Remove by index (descending order to preserve indices during splice).
62
- if (args._remove_social_link_indices?.length) {
63
- const sorted = [...args._remove_social_link_indices].sort((a, b) => b - a);
64
- for (const idx of sorted) {
65
- if (idx >= 0 && idx < links.length)
66
- links.splice(idx, 1);
67
- }
68
- }
69
- // Insert new links (descending insert_at order to preserve positions).
70
- const insertLinks = (args.social_links ?? []).filter((l) => l.insert_at !== undefined && l.item_index === undefined);
71
- const sortedInserts = [...insertLinks].sort((a, b) => b.insert_at - a.insert_at);
72
- for (const link of sortedInserts) {
73
- const pos = Math.max(0, Math.min(link.insert_at, links.length));
74
- links.splice(pos, 0, makeSocialLink(link.social_link_icon, link.url));
75
- }
76
- // Reorder (requires ALL indices — partial reorder is not supported).
77
- if (args._reorder_social_links?.length) {
78
- const newOrder = args._reorder_social_links;
79
- const unique = new Set(newOrder);
80
- if (unique.size === newOrder.length &&
81
- newOrder.every((idx) => idx >= 0 && idx < links.length)) {
82
- const reordered = newOrder.map((idx) => links[idx]);
83
- if (reordered.length === links.length)
84
- return reordered;
85
- }
86
- }
87
- return links;
88
- };
89
13
  const applyGlobalFooterChanges = (footer, args, textStyleOptions) => {
90
14
  if (args.copyright_text !== undefined)
91
15
  footer.copyrightText = args.copyright_text;
92
16
  if (args.copyright_text_font_family !== undefined)
93
17
  footer.copyrightStyle = applyDetachedStyleOverride(footer.copyrightStyle, { fontFamily: args.copyright_text_font_family }, textStyleOptions);
18
+ const links = args.social_links;
19
+ // _replace_social_links: replace entire list (empty/absent social_links = clear all).
20
+ // When replacing, all other social link mutations (remove, insert, reorder) are skipped —
21
+ // they are superseded by the replacement and running them would cause duplicates.
22
+ if (args._replace_social_links) {
23
+ footer.socialLinks = (links ?? []).map((l) => ({
24
+ id: generateItemId(),
25
+ imageUrl: l.social_link_icon || '',
26
+ url: normaliseSocialUrl(l.url || ''),
27
+ imageTitle: '',
28
+ width: 0,
29
+ height: 0,
30
+ imageLink: '',
31
+ }));
32
+ }
33
+ else {
34
+ // Targeted mutations — only run when NOT replacing the entire list.
35
+ // BP-5603: id-based contract (item_id / move_before_item_id / _remove_social_link_ids).
36
+ // Backfill ids on legacy links so they become referenceable after this call.
37
+ ensureItemIds(footer.socialLinks);
38
+ // Remove by id (order-independent — no index shifting needed).
39
+ footer.socialLinks = removeItemsByIds(footer.socialLinks, args._remove_social_link_ids);
40
+ // Update existing links (item_id present) or insert new links (no item_id).
41
+ if (links?.length) {
42
+ footer.socialLinks = footer.socialLinks ?? [];
43
+ for (const link of links) {
44
+ if (link.item_id) {
45
+ // Update existing link in place; reposition if move_before_item_id set.
46
+ const existing = footer.socialLinks.find((l) => l.id === link.item_id);
47
+ // Unknown id — skip rather than guess at a position.
48
+ if (!existing)
49
+ continue;
50
+ if (link.social_link_icon !== undefined)
51
+ existing.imageUrl = link.social_link_icon;
52
+ if (link.url !== undefined)
53
+ existing.url = normaliseSocialUrl(link.url);
54
+ repositionItemByAnchor(footer.socialLinks, existing, link.move_before_item_id);
55
+ }
56
+ else {
57
+ // New link — assign a stable id and position via move_before_item_id.
58
+ const newLink = {
59
+ id: generateItemId(),
60
+ imageUrl: link.social_link_icon || '',
61
+ url: normaliseSocialUrl(link.url || ''),
62
+ imageTitle: '',
63
+ width: 0,
64
+ height: 0,
65
+ imageLink: '',
66
+ };
67
+ repositionItemByAnchor(footer.socialLinks, newLink, link.move_before_item_id);
68
+ }
69
+ }
70
+ }
71
+ }
94
72
  };
95
73
  const applyViewportFooterChanges = (footer, overrides) => {
96
74
  if (overrides.display_style !== undefined)
@@ -148,24 +126,11 @@ export class FooterBuilder extends BaseMetadataBuilder {
148
126
  }
149
127
  const args = this.request.payload ?? {};
150
128
  const textStyleOptions = model.styles?.textStyles?.textStyleOptions;
151
- // Global scalar/style fields (copyright text + font) apply to both
152
- // viewports. These are idempotent assignments, safe to run per-viewport.
129
+ // Global fields (copyright_text, social links) always apply to both viewports.
153
130
  if (model.pageFooterDesktop)
154
131
  applyGlobalFooterChanges(model.pageFooterDesktop, args, textStyleOptions);
155
132
  if (model.pageFooterMobile)
156
133
  applyGlobalFooterChanges(model.pageFooterMobile, args, textStyleOptions);
157
- // Social links are a single global list. Derive the new list ONCE, then
158
- // assign an INDEPENDENT copy to each viewport — never mutate a (possibly
159
- // shared) viewport array in place. See computeSocialLinks.
160
- if (hasSocialLinkMutation(args)) {
161
- const current = model.pageFooterDesktop?.socialLinks ??
162
- model.pageFooterMobile?.socialLinks;
163
- const updated = computeSocialLinks(current, args);
164
- if (model.pageFooterDesktop)
165
- model.pageFooterDesktop.socialLinks = updated.map((l) => ({ ...l }));
166
- if (model.pageFooterMobile)
167
- model.pageFooterMobile.socialLinks = updated.map((l) => ({ ...l }));
168
- }
169
134
  // Viewport-specific styling via explicit sub-objects.
170
135
  if (args.desktop && model.pageFooterDesktop)
171
136
  applyViewportFooterChanges(model.pageFooterDesktop, args.desktop);
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Sentinel anchor meaning "the end of the list", mirroring the section-level
3
+ * `move_before_content_id: "__end__"` convention used in base-section-builder.
4
+ */
5
+ export declare const END_ITEM_ANCHOR = "__end__";
6
+ /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
7
+ export declare const generateItemId: () => string;
8
+ /**
9
+ * Backfill ids onto any items missing one. Per BP-5603 this runs in the
10
+ * metadata builder when a section is processed, so the persisted metadata
11
+ * returned to the client gains stable ids that subsequent AI tool calls can
12
+ * reference. Mutates in place.
13
+ */
14
+ export declare const ensureItemIds: <T extends {
15
+ id?: string | undefined;
16
+ }>(items: T[] | undefined) => void;
17
+ /**
18
+ * Remove items whose id appears in `ids`. Order-independent (no descending
19
+ * index sort needed) — the whole point of the id-based migration. Items
20
+ * without an id are never removed by id.
21
+ */
22
+ export declare const removeItemsByIds: <T extends {
23
+ id?: string | undefined;
24
+ }>(items: T[] | undefined, ids: string[] | undefined) => T[];
25
+ /**
26
+ * Position `item` within `items` relative to an anchor id — the item-level
27
+ * mirror of base-section-builder's `moveContent`:
28
+ *
29
+ * - `moveBeforeItemId === undefined` → leave an existing item where it is;
30
+ * append a new (not-yet-present) item to the end.
31
+ * - `moveBeforeItemId === "__end__"` → move/insert at the very end.
32
+ * - anchor id found → insert immediately before it.
33
+ * - anchor id not found → fall through to the end (same
34
+ * forgiving behaviour as `moveContent`).
35
+ *
36
+ * Mutates `items` in place. `item` may be an existing element of `items`
37
+ * (a move) or a brand-new object (an insert).
38
+ */
39
+ export declare const repositionItemByAnchor: <T extends {
40
+ id?: string | undefined;
41
+ }>(items: T[], item: T, moveBeforeItemId: string | undefined) => void;
@@ -0,0 +1,68 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ /**
3
+ * Sentinel anchor meaning "the end of the list", mirroring the section-level
4
+ * `move_before_content_id: "__end__"` convention used in base-section-builder.
5
+ */
6
+ export const END_ITEM_ANCHOR = '__end__';
7
+ /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
8
+ export const generateItemId = () => uuidv4();
9
+ /**
10
+ * Backfill ids onto any items missing one. Per BP-5603 this runs in the
11
+ * metadata builder when a section is processed, so the persisted metadata
12
+ * returned to the client gains stable ids that subsequent AI tool calls can
13
+ * reference. Mutates in place.
14
+ */
15
+ export const ensureItemIds = (items) => {
16
+ if (!items)
17
+ return;
18
+ for (const item of items) {
19
+ if (!item.id)
20
+ item.id = generateItemId();
21
+ }
22
+ };
23
+ /**
24
+ * Remove items whose id appears in `ids`. Order-independent (no descending
25
+ * index sort needed) — the whole point of the id-based migration. Items
26
+ * without an id are never removed by id.
27
+ */
28
+ export const removeItemsByIds = (items, ids) => {
29
+ if (!items?.length)
30
+ return items ?? [];
31
+ if (!ids?.length)
32
+ return items;
33
+ const toRemove = new Set(ids);
34
+ return items.filter((item) => !item.id || !toRemove.has(item.id));
35
+ };
36
+ /**
37
+ * Position `item` within `items` relative to an anchor id — the item-level
38
+ * mirror of base-section-builder's `moveContent`:
39
+ *
40
+ * - `moveBeforeItemId === undefined` → leave an existing item where it is;
41
+ * append a new (not-yet-present) item to the end.
42
+ * - `moveBeforeItemId === "__end__"` → move/insert at the very end.
43
+ * - anchor id found → insert immediately before it.
44
+ * - anchor id not found → fall through to the end (same
45
+ * forgiving behaviour as `moveContent`).
46
+ *
47
+ * Mutates `items` in place. `item` may be an existing element of `items`
48
+ * (a move) or a brand-new object (an insert).
49
+ */
50
+ export const repositionItemByAnchor = (items, item, moveBeforeItemId) => {
51
+ const currentIndex = items.indexOf(item);
52
+ if (moveBeforeItemId === undefined) {
53
+ if (currentIndex === -1)
54
+ items.push(item);
55
+ return;
56
+ }
57
+ if (currentIndex !== -1)
58
+ items.splice(currentIndex, 1);
59
+ const anchorIndex = moveBeforeItemId === END_ITEM_ANCHOR
60
+ ? -1
61
+ : items.findIndex((i) => i.id === moveBeforeItemId);
62
+ if (anchorIndex !== -1) {
63
+ items.splice(anchorIndex, 0, item);
64
+ }
65
+ else {
66
+ items.push(item);
67
+ }
68
+ };
@@ -1,14 +1,21 @@
1
1
  import { applyScalarFields, isValidUrl } from '../section-field-utils';
2
2
  import { BaseSectionBuilder } from './base-section-builder';
3
3
  import { validateImageGallery } from '../section-rules/image-gallery.rules';
4
+ import { ensureItemIds, generateItemId, removeItemsByIds, repositionItemByAnchor, } from '../item-id-utils';
4
5
  const applyGalleryType = (content, galleryType) => {
5
6
  content.galleryStyles = {
6
7
  ...(content.galleryStyles || {}),
7
8
  galleryType: galleryType,
8
9
  };
9
10
  };
11
+ /**
12
+ * Full rebuild: discard existing images and map the payload directly.
13
+ * Used on create and when `_replace_items` is set. Each item gets a fresh id
14
+ * (or keeps the caller-supplied `item_id` when provided).
15
+ */
10
16
  const applyGalleryImages = (content, images) => {
11
17
  content.images = images.map((img) => ({
18
+ id: img.item_id || generateItemId(),
12
19
  imageUrl: isValidUrl(img.image_url) ? img.image_url : '',
13
20
  imageTitle: img.caption || '',
14
21
  width: 0,
@@ -17,59 +24,46 @@ const applyGalleryImages = (content, images) => {
17
24
  imageLink: '',
18
25
  }));
19
26
  };
20
- const mergeGalleryImages = (content, images, replaceAll) => {
21
- const itemsToMerge = images.filter((img) => img.insert_at === undefined);
22
- const updatedImages = itemsToMerge.map((img, index) => {
23
- const existing = content.images?.[index];
24
- const imageUrl = isValidUrl(img.image_url)
25
- ? img.image_url
26
- : (existing?.imageUrl ?? '');
27
- return {
28
- ...(existing || {}),
29
- imageUrl,
30
- imageTitle: img.caption ?? existing?.imageTitle ?? '',
31
- width: existing?.width ?? 0,
32
- height: existing?.height ?? 0,
33
- mediaType: existing?.mediaType ?? 'image',
34
- imageLink: existing?.imageLink ?? '',
35
- };
36
- });
37
- if (!replaceAll &&
38
- content.images &&
39
- content.images.length > itemsToMerge.length) {
40
- const extras = content.images.slice(itemsToMerge.length).map((img) => ({
41
- imageUrl: img.imageUrl ?? '',
42
- imageTitle: img.imageTitle ?? '',
43
- width: img.width ?? 0,
44
- height: img.height ?? 0,
45
- mediaType: img.mediaType ?? 'image',
46
- imageLink: img.imageLink ?? '',
47
- }));
48
- updatedImages.push(...extras);
49
- }
50
- content.images = updatedImages;
51
- };
52
- const insertGalleryImages = (content, images) => {
53
- const inserts = images.filter((img) => img.insert_at !== undefined);
54
- if (!inserts.length)
55
- return;
56
- const sorted = [...inserts].sort((a, b) => (b.insert_at ?? 0) - (a.insert_at ?? 0));
57
- for (const img of sorted) {
58
- if (!isValidUrl(img.image_url))
59
- continue;
60
- if (content.images?.some((existing) => existing.imageUrl === img.image_url))
61
- continue;
62
- content.images = content.images ?? [];
63
- const pos = Math.max(0, Math.min(img.insert_at ?? 0, content.images.length));
64
- content.images.splice(pos, 0, {
65
- imageUrl: img.image_url,
66
- imageTitle: img.caption ?? '',
67
- width: 0,
68
- height: 0,
69
- mediaType: 'image',
70
- imageLink: '',
71
- colorReplacements: [],
72
- });
27
+ /**
28
+ * Id-based merge (BP-5603), mirroring testimonial-section-builder one level
29
+ * deeper into `content.images`:
30
+ * - `item_id` matches existing item → update in place; reposition if
31
+ * `move_before_item_id` is set.
32
+ * - no `item_id` → add new item, positioned by
33
+ * `move_before_item_id`.
34
+ * - `removeIds` → delete by id (order-independent).
35
+ *
36
+ * Existing items are id-backfilled first so legacy galleries become
37
+ * referenceable as soon as the metadata builder touches them.
38
+ */
39
+ const mergeGalleryImages = (content, images, removeIds) => {
40
+ ensureItemIds(content.images);
41
+ content.images = removeItemsByIds(content.images, removeIds);
42
+ for (const img of images) {
43
+ if (img.item_id) {
44
+ const existing = content.images.find((i) => i.id === img.item_id);
45
+ // Unknown id — skip rather than guess at a position.
46
+ if (!existing)
47
+ continue;
48
+ if (img.image_url !== undefined) {
49
+ existing.imageUrl = isValidUrl(img.image_url) ? img.image_url : '';
50
+ }
51
+ if (img.caption !== undefined)
52
+ existing.imageTitle = img.caption;
53
+ repositionItemByAnchor(content.images, existing, img.move_before_item_id);
54
+ }
55
+ else {
56
+ const newItem = {
57
+ id: generateItemId(),
58
+ imageUrl: isValidUrl(img.image_url) ? img.image_url : '',
59
+ imageTitle: img.caption ?? '',
60
+ width: 0,
61
+ height: 0,
62
+ mediaType: 'image',
63
+ imageLink: '',
64
+ };
65
+ repositionItemByAnchor(content.images, newItem, img.move_before_item_id);
66
+ }
73
67
  }
74
68
  };
75
69
  export class ImageGalleryBuilder extends BaseSectionBuilder {
@@ -91,24 +85,19 @@ export class ImageGalleryBuilder extends BaseSectionBuilder {
91
85
  if (args.gallery_type) {
92
86
  applyGalleryType(content, args.gallery_type);
93
87
  }
94
- if (args.images && Array.isArray(args.images)) {
95
- if (this.mode === 'create') {
96
- applyGalleryImages(content, args.images);
97
- }
98
- else {
99
- mergeGalleryImages(content, args.images, !!args._replace_items);
100
- }
88
+ if (args._replace_items && args.images && Array.isArray(args.images)) {
89
+ // Full replacement: discard existing images and rebuild from the payload.
90
+ applyGalleryImages(content, args.images);
101
91
  }
102
- if (args._remove_item_indices?.length && content.images) {
103
- const sorted = [...args._remove_item_indices].sort((a, b) => b - a);
104
- for (const idx of sorted) {
105
- if (idx >= 0 && idx < content.images.length) {
106
- content.images.splice(idx, 1);
107
- }
92
+ else if (this.mode === 'create') {
93
+ if (args.images && Array.isArray(args.images)) {
94
+ applyGalleryImages(content, args.images);
108
95
  }
109
96
  }
110
- if (args.images && Array.isArray(args.images) && this.mode === 'update') {
111
- insertGalleryImages(content, args.images);
97
+ else if ((args.images && Array.isArray(args.images)) ||
98
+ args._remove_item_ids?.length) {
99
+ // Update: id-based add / edit / move / remove in a single pass.
100
+ mergeGalleryImages(content, args.images ?? [], args._remove_item_ids);
112
101
  }
113
102
  return { request: this.request, model };
114
103
  }
@@ -3,8 +3,15 @@ import { isTiptapDocument, } from '../../parsers/section-tool-arguments';
3
3
  import { applyScalarFields, applyStyleFields, isValidUrl, resolveGlobalPresetIntoStyle, } from '../section-field-utils';
4
4
  import { BaseSectionBuilder } from './base-section-builder';
5
5
  import { validateImagesSection } from '../section-rules/images-section.rules';
6
+ import { ensureItemIds, generateItemId, removeItemsByIds, repositionItemByAnchor, } from '../item-id-utils';
7
+ /**
8
+ * Full replacement: discard existing items and rebuild from the payload.
9
+ * Assigns a stable `id` to each item; `itemsId` is set positionally here
10
+ * but will be re-indexed at the end of buildAsync.
11
+ */
6
12
  const applyImageItems = (content, items) => {
7
13
  content.items = items.map((item, index) => ({
14
+ id: item.item_id || generateItemId(),
8
15
  imageUrl: isValidUrl(item.image_url) ? item.image_url : '',
9
16
  imageTitle: '',
10
17
  width: 0,
@@ -18,16 +25,28 @@ const applyImageItems = (content, items) => {
18
25
  colorReplacements: [],
19
26
  }));
20
27
  };
21
- const mergeImageItems = (content, items, replaceAll) => {
22
- const hasIndexedItems = items.some((item) => item.item_index !== undefined);
23
- if (hasIndexedItems) {
24
- for (const item of items) {
25
- const targetIndex = item.item_index;
26
- if (targetIndex === undefined ||
27
- targetIndex < 0 ||
28
- !content.items?.[targetIndex])
28
+ /**
29
+ * Apply a batch of item operations by id (BP-5603), mirroring the
30
+ * section-level id-based contract one level deeper:
31
+ * - item with `item_id` matching an existing item → update in place (and
32
+ * reposition if `move_before_item_id` is set);
33
+ * - item without `item_id` → add a new item,
34
+ * positioned by `move_before_item_id`;
35
+ * - `_remove_item_ids` → delete by id.
36
+ *
37
+ * Existing items are id-backfilled first so legacy sections become
38
+ * referenceable the moment the metadata builder touches them.
39
+ * Order-independent: there are no positional indices to shift between ops.
40
+ */
41
+ const mergeImageItems = (content, items, removeIds) => {
42
+ ensureItemIds(content.items);
43
+ content.items = removeItemsByIds(content.items, removeIds);
44
+ for (const item of items) {
45
+ if (item.item_id) {
46
+ const existing = content.items.find((i) => i.id === item.item_id);
47
+ // Unknown id — skip rather than guess at a position.
48
+ if (!existing)
29
49
  continue;
30
- const existing = content.items[targetIndex];
31
50
  if (isValidUrl(item.image_url))
32
51
  existing.imageUrl = item.image_url;
33
52
  if (item.heading !== undefined)
@@ -36,36 +55,26 @@ const mergeImageItems = (content, items, replaceAll) => {
36
55
  existing.paragraph = item.text;
37
56
  if (item.button_text !== undefined)
38
57
  existing.buttonText = item.button_text || false;
58
+ repositionItemByAnchor(content.items, existing, item.move_before_item_id);
59
+ }
60
+ else {
61
+ const newItem = {
62
+ id: generateItemId(),
63
+ imageUrl: isValidUrl(item.image_url) ? item.image_url : '',
64
+ imageTitle: '',
65
+ width: 0,
66
+ height: 0,
67
+ mediaType: 'image',
68
+ imageLink: '',
69
+ title: item.heading ?? '',
70
+ paragraph: item.text ?? '',
71
+ buttonText: item.button_text || false,
72
+ itemsId: 0, // re-indexed at the end of buildAsync
73
+ colorReplacements: [],
74
+ };
75
+ repositionItemByAnchor(content.items, newItem, item.move_before_item_id);
39
76
  }
40
- return;
41
- }
42
- const itemsToMerge = items.filter((item) => item.insert_at === undefined);
43
- const updatedItems = itemsToMerge.map((item, index) => {
44
- const existing = content.items?.[index];
45
- return {
46
- ...(existing || {}),
47
- imageUrl: isValidUrl(item.image_url)
48
- ? item.image_url
49
- : (existing?.imageUrl ?? ''),
50
- imageTitle: existing?.imageTitle ?? '',
51
- width: existing?.width ?? 0,
52
- height: existing?.height ?? 0,
53
- mediaType: existing?.mediaType ?? 'image',
54
- imageLink: existing?.imageLink ?? '',
55
- title: item.heading ?? existing?.title ?? '',
56
- paragraph: item.text ?? existing?.paragraph ?? '',
57
- buttonText: item.button_text ?? existing?.buttonText ?? '',
58
- itemsId: existing?.itemsId ?? index,
59
- colorReplacements: existing?.colorReplacements ?? [],
60
- };
61
- });
62
- if (!replaceAll &&
63
- content.items &&
64
- content.items.length > itemsToMerge.length) {
65
- const remaining = content.items.slice(itemsToMerge.length);
66
- updatedItems.push(...remaining);
67
77
  }
68
- content.items = updatedItems;
69
78
  };
70
79
  export class ImagesSectionBuilder extends BaseSectionBuilder {
71
80
  get sectionValidate() {
@@ -123,66 +132,22 @@ export class ImagesSectionBuilder extends BaseSectionBuilder {
123
132
  ])),
124
133
  };
125
134
  }
126
- if (args.items && Array.isArray(args.items)) {
127
- if (this.mode === 'create') {
128
- applyImageItems(content, args.items);
129
- }
130
- else {
131
- mergeImageItems(content, args.items, !!args._replace_items);
132
- }
133
- }
134
- // Phase 2: Item operations (remove, insert, reorder)
135
- if (args._remove_item_indices?.length && content.items) {
136
- const sorted = [...args._remove_item_indices].sort((a, b) => b - a);
137
- for (const idx of sorted) {
138
- if (idx >= 0 && idx < content.items.length) {
139
- content.items.splice(idx, 1);
140
- }
141
- }
135
+ if (args._replace_items && args.items && Array.isArray(args.items)) {
136
+ // Full replacement: discard existing items and rebuild from the payload.
137
+ applyImageItems(content, args.items);
142
138
  }
143
- if (args.items && Array.isArray(args.items)) {
144
- const insertItems = args.items.filter((item) => item.insert_at !== undefined && item.item_index === undefined);
145
- // Process inserts in reverse order of insert_at to preserve indices
146
- const sortedInserts = [...insertItems].sort((a, b) => (b.insert_at ?? 0) - (a.insert_at ?? 0));
147
- for (const item of sortedInserts) {
148
- if (!isValidUrl(item.image_url))
149
- continue;
150
- // Skip if this image_url already exists — defensive against duplicate
151
- // tool call application
152
- if (content.items?.some((existing) => existing.imageUrl === item.image_url)) {
153
- continue;
154
- }
155
- const pos = Math.max(0, Math.min(item.insert_at ?? 0, content.items?.length ?? 0));
156
- const newItem = {
157
- imageUrl: item.image_url,
158
- imageTitle: '',
159
- width: 0,
160
- height: 0,
161
- mediaType: 'image',
162
- imageLink: '',
163
- title: item.heading ?? '',
164
- paragraph: item.text ?? '',
165
- buttonText: item.button_text || '',
166
- itemsId: pos,
167
- colorReplacements: [],
168
- };
169
- content.items = content.items ?? [];
170
- content.items.splice(pos, 0, newItem);
139
+ else if (this.mode === 'create') {
140
+ if (args.items && Array.isArray(args.items)) {
141
+ applyImageItems(content, args.items);
171
142
  }
172
143
  }
173
- if (args._reorder_items?.length && content.items) {
174
- const newOrder = args._reorder_items;
175
- const unique = new Set(newOrder);
176
- if (unique.size === newOrder.length) {
177
- const reordered = newOrder
178
- .filter((idx) => idx >= 0 && idx < content.items.length)
179
- .map((idx) => content.items[idx]);
180
- if (reordered.length === content.items.length) {
181
- content.items = reordered;
182
- }
183
- }
144
+ else if ((args.items && Array.isArray(args.items)) ||
145
+ args._remove_item_ids?.length) {
146
+ // Update: id-based add / edit / move / remove in a single pass.
147
+ mergeImageItems(content, args.items ?? [], args._remove_item_ids);
184
148
  }
185
- // Re-index itemsId after any item operations
149
+ // Re-index itemsId after all id-based item operations (render concern —
150
+ // separate from the stable `id` used for targeting).
186
151
  if (content.items) {
187
152
  content.items.forEach((item, i) => {
188
153
  item.itemsId = i;