@salla.sa/twilight-components 2.14.590 → 2.14.591
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.
- package/dist/cjs/{filepond-Dt4Nihdd.js → filepond-DkObEO32.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-poster-DwwSoJTP.js → filepond-plugin-file-poster-wXgeY5cU.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-validate-size-DsVYh3H7.js → filepond-plugin-file-validate-size-CWleid1C.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-validate-type-D5SqBZyw.js → filepond-plugin-file-validate-type-CJLl2hbe.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-edit-CTTRxQHm.js → filepond-plugin-image-edit-Bp6AxXM9.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-exif-orientation-CN52oBp4.js → filepond-plugin-image-exif-orientation-abWKSX9-.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-preview-DgSWBJVB.js → filepond-plugin-image-preview-DxprNycS.js} +1 -1
- package/dist/cjs/{index-CgZ8Pldm.js → index-DBN0LueR.js} +10 -14
- package/dist/cjs/{intlTelInput-DBBilzGk.js → intlTelInput-B6H18Td2.js} +1 -1
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/salla-accordion-body_2.cjs.entry.js +1 -1
- package/dist/cjs/salla-accordion_6.cjs.entry.js +1 -1
- package/dist/cjs/salla-add-product-button_5.cjs.entry.js +1 -1
- package/dist/cjs/salla-advertisement.cjs.entry.js +1 -1
- package/dist/cjs/salla-alert_2.cjs.entry.js +1 -1
- package/dist/cjs/salla-app-install-alert.cjs.entry.js +1 -1
- package/dist/cjs/salla-applied-filters.cjs.entry.js +1 -1
- package/dist/cjs/salla-apps-icons.cjs.entry.js +1 -1
- package/dist/cjs/salla-attachment-view_9.cjs.entry.js +10 -10
- package/dist/cjs/salla-badge.cjs.entry.js +1 -1
- package/dist/cjs/salla-basket-gap.cjs.entry.js +1 -1
- package/dist/cjs/salla-bullet-delivery_3.cjs.entry.js +1 -1
- package/dist/cjs/salla-cart-coupons.cjs.entry.js +1 -1
- package/dist/cjs/salla-cart-item-offers_2.cjs.entry.js +1 -1
- package/dist/cjs/salla-cart-summary-card.cjs.entry.js +1 -1
- package/dist/cjs/salla-cashback-banner.cjs.entry.js +1 -1
- package/dist/cjs/salla-comment-factors.cjs.entry.js +1 -1
- package/dist/cjs/salla-comment-form_9.cjs.entry.js +1 -1
- package/dist/cjs/salla-conditional-offer.cjs.entry.js +2664 -106
- package/dist/cjs/salla-contacts.cjs.entry.js +1 -1
- package/dist/cjs/salla-cookies-bar.cjs.entry.js +1 -1
- package/dist/cjs/salla-count-down.cjs.entry.js +1 -1
- package/dist/cjs/salla-custom-fields.cjs.entry.js +1 -1
- package/dist/cjs/salla-delivery-promise.cjs.entry.js +1 -1
- package/dist/cjs/salla-edit-order-button.cjs.entry.js +1 -1
- package/dist/cjs/salla-filters-widget.cjs.entry.js +1 -1
- package/dist/cjs/salla-filters.cjs.entry.js +1 -1
- package/dist/cjs/salla-fulfillment-methods.cjs.entry.js +1 -1
- package/dist/cjs/{salla-gift-card-redeem.cjs.entry.js → salla-gift-card-redeem_2.cjs.entry.js} +36 -1
- package/dist/cjs/salla-gifting.cjs.entry.js +1 -1
- package/dist/cjs/salla-hook.cjs.entry.js +1 -1
- package/dist/cjs/salla-infinite-scroll.cjs.entry.js +1 -1
- package/dist/cjs/salla-installment.cjs.entry.js +1 -1
- package/dist/cjs/salla-list-tile.cjs.entry.js +1 -1
- package/dist/cjs/salla-localization-modal.cjs.entry.js +1 -1
- package/dist/cjs/salla-login-modal.cjs.entry.js +1 -1
- package/dist/cjs/salla-loyalty-banner_10.cjs.entry.js +1 -1
- package/dist/cjs/salla-loyalty-exchange-product-card.cjs.entry.js +1 -1
- package/dist/cjs/salla-loyalty-points-banner.cjs.entry.js +1 -1
- package/dist/cjs/salla-loyalty-points-history.cjs.entry.js +1 -1
- package/dist/cjs/salla-loyalty-prize-item.cjs.entry.js +1 -1
- package/dist/cjs/salla-loyalty.cjs.entry.js +1 -1
- package/dist/cjs/salla-maintenance-alert.cjs.entry.js +1 -1
- package/dist/cjs/salla-map_3.cjs.entry.js +1 -1
- package/dist/cjs/salla-menu.cjs.entry.js +1 -1
- package/dist/cjs/salla-metadata.cjs.entry.js +1 -1
- package/dist/cjs/salla-multiple-bundle-product-cart_2.cjs.entry.js +1 -1
- package/dist/cjs/salla-multiple-bundle-product-options-modal_2.cjs.entry.js +1 -1
- package/dist/cjs/salla-multiple-bundle-product.cjs.entry.js +1 -1
- package/dist/cjs/salla-next-order-coupon.cjs.entry.js +1 -1
- package/dist/cjs/salla-notification-item_2.cjs.entry.js +1 -1
- package/dist/cjs/salla-offer-modal.cjs.entry.js +1 -1
- package/dist/cjs/salla-offer.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-branch.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-cancel.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-details-multiple-bundle-product.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-details-options.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-details.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-edit-item.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-edit-product-card.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-edit.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-options-modal.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-shipments.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-summary.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-totals-card.cjs.entry.js +1 -1
- package/dist/cjs/salla-orders.cjs.entry.js +1 -1
- package/dist/cjs/salla-payments.cjs.entry.js +1 -1
- package/dist/cjs/salla-placeholder.cjs.entry.js +1 -1
- package/dist/cjs/salla-price-range.cjs.entry.js +1 -1
- package/dist/cjs/salla-product-card-embed.cjs.entry.js +1 -1
- package/dist/cjs/salla-product-card.cjs.entry.js +1 -1
- package/dist/cjs/salla-product-size-guide.cjs.entry.js +1 -1
- package/dist/cjs/salla-products-list.cjs.entry.js +1 -1
- package/dist/cjs/salla-products-slider.cjs.entry.js +1 -1
- package/dist/cjs/salla-quick-order.cjs.entry.js +1 -1
- package/dist/cjs/salla-rating-modal-v2.cjs.entry.js +1 -1
- package/dist/cjs/salla-rating-modal.cjs.entry.js +1 -1
- package/dist/cjs/salla-review-factors-tags.cjs.entry.js +1 -1
- package/dist/cjs/salla-review-order-item.cjs.entry.js +1 -1
- package/dist/cjs/salla-scopes.cjs.entry.js +1 -1
- package/dist/cjs/salla-search-product-card.cjs.entry.js +1 -1
- package/dist/cjs/salla-search.cjs.entry.js +1 -1
- package/dist/cjs/salla-skeleton.cjs.entry.js +1 -1
- package/dist/cjs/salla-social-share.cjs.entry.js +1 -1
- package/dist/cjs/salla-social.cjs.entry.js +1 -1
- package/dist/cjs/salla-tab-content_3.cjs.entry.js +1 -1
- package/dist/cjs/salla-tiered-offer.cjs.entry.js +1 -1
- package/dist/cjs/salla-trust-badges.cjs.entry.js +1 -1
- package/dist/cjs/salla-user-menu.cjs.entry.js +1 -1
- package/dist/cjs/salla-user-profile.cjs.entry.js +1 -1
- package/dist/cjs/salla-user-settings.cjs.entry.js +1 -1
- package/dist/cjs/salla-verify.cjs.entry.js +1 -1
- package/dist/cjs/salla-wallet.cjs.entry.js +1 -1
- package/dist/cjs/salla-wishlist-actions.cjs.entry.js +1 -1
- package/dist/cjs/salla-wishlist-summary.cjs.entry.js +1 -1
- package/dist/cjs/twilight.cjs.js +2 -2
- package/dist/cjs/{vanilla-picker-DMOjok7F.js → vanilla-picker-Cm71HMBs.js} +1 -1
- package/dist/collection/components/salla-conditional-offer/cart-update-window.js +34 -0
- package/dist/collection/components/salla-conditional-offer/interfaces.js +1 -0
- package/dist/collection/components/salla-conditional-offer/salla-conditional-offer.css +456 -191
- package/dist/collection/components/salla-conditional-offer/salla-conditional-offer.js +1790 -106
- package/dist/collection/shared/cart-free-gifts.js +448 -0
- package/dist/collection/shared/offer-stages.js +453 -0
- package/dist/components/index.js +2 -2
- package/dist/components/salla-conditional-offer.js +2672 -116
- package/dist/docs.json +21 -8
- package/dist/esm/{filepond-wzh5HaPr.js → filepond-Cstf5r6K.js} +1 -1
- package/dist/esm/{filepond-plugin-file-poster-DInGlcq6.js → filepond-plugin-file-poster-GibBgGHW.js} +1 -1
- package/dist/esm/{filepond-plugin-file-validate-size-BaLOl1L7.js → filepond-plugin-file-validate-size-CLc6zco6.js} +1 -1
- package/dist/esm/{filepond-plugin-file-validate-type-D_DVE5xS.js → filepond-plugin-file-validate-type-Ca3dc8p0.js} +1 -1
- package/dist/esm/{filepond-plugin-image-edit-B9vWgHyi.js → filepond-plugin-image-edit-CNGBLi7d.js} +1 -1
- package/dist/esm/{filepond-plugin-image-exif-orientation-lJrZF6xG.js → filepond-plugin-image-exif-orientation-Dmmu9-UQ.js} +1 -1
- package/dist/esm/{filepond-plugin-image-preview-Bbk7m1Yt.js → filepond-plugin-image-preview-KX15tx60.js} +1 -1
- package/dist/esm/{index-klFskvc_.js → index-UnmOIma5.js} +10 -14
- package/dist/esm/{intlTelInput-pS6pc-iA.js → intlTelInput-C-F-rxZ3.js} +1 -1
- package/dist/esm/loader.js +3 -3
- package/dist/esm/salla-accordion-body_2.entry.js +1 -1
- package/dist/esm/salla-accordion_6.entry.js +1 -1
- package/dist/esm/salla-add-product-button_5.entry.js +1 -1
- package/dist/esm/salla-advertisement.entry.js +1 -1
- package/dist/esm/salla-alert_2.entry.js +1 -1
- package/dist/esm/salla-app-install-alert.entry.js +1 -1
- package/dist/esm/salla-applied-filters.entry.js +1 -1
- package/dist/esm/salla-apps-icons.entry.js +1 -1
- package/dist/esm/salla-attachment-view_9.entry.js +10 -10
- package/dist/esm/salla-badge.entry.js +1 -1
- package/dist/esm/salla-basket-gap.entry.js +1 -1
- package/dist/esm/salla-bullet-delivery_3.entry.js +1 -1
- package/dist/esm/salla-cart-coupons.entry.js +1 -1
- package/dist/esm/salla-cart-item-offers_2.entry.js +1 -1
- package/dist/esm/salla-cart-summary-card.entry.js +1 -1
- package/dist/esm/salla-cashback-banner.entry.js +1 -1
- package/dist/esm/salla-comment-factors.entry.js +1 -1
- package/dist/esm/salla-comment-form_9.entry.js +1 -1
- package/dist/esm/salla-conditional-offer.entry.js +2664 -106
- package/dist/esm/salla-contacts.entry.js +1 -1
- package/dist/esm/salla-cookies-bar.entry.js +1 -1
- package/dist/esm/salla-count-down.entry.js +1 -1
- package/dist/esm/salla-custom-fields.entry.js +1 -1
- package/dist/esm/salla-delivery-promise.entry.js +1 -1
- package/dist/esm/salla-edit-order-button.entry.js +1 -1
- package/dist/esm/salla-filters-widget.entry.js +1 -1
- package/dist/esm/salla-filters.entry.js +1 -1
- package/dist/esm/salla-fulfillment-methods.entry.js +1 -1
- package/dist/esm/{salla-gift-card-redeem.entry.js → salla-gift-card-redeem_2.entry.js} +36 -2
- package/dist/esm/salla-gifting.entry.js +1 -1
- package/dist/esm/salla-hook.entry.js +1 -1
- package/dist/esm/salla-infinite-scroll.entry.js +1 -1
- package/dist/esm/salla-installment.entry.js +1 -1
- package/dist/esm/salla-list-tile.entry.js +1 -1
- package/dist/esm/salla-localization-modal.entry.js +1 -1
- package/dist/esm/salla-login-modal.entry.js +1 -1
- package/dist/esm/salla-loyalty-banner_10.entry.js +1 -1
- package/dist/esm/salla-loyalty-exchange-product-card.entry.js +1 -1
- package/dist/esm/salla-loyalty-points-banner.entry.js +1 -1
- package/dist/esm/salla-loyalty-points-history.entry.js +1 -1
- package/dist/esm/salla-loyalty-prize-item.entry.js +1 -1
- package/dist/esm/salla-loyalty.entry.js +1 -1
- package/dist/esm/salla-maintenance-alert.entry.js +1 -1
- package/dist/esm/salla-map_3.entry.js +1 -1
- package/dist/esm/salla-menu.entry.js +1 -1
- package/dist/esm/salla-metadata.entry.js +1 -1
- package/dist/esm/salla-multiple-bundle-product-cart_2.entry.js +1 -1
- package/dist/esm/salla-multiple-bundle-product-options-modal_2.entry.js +1 -1
- package/dist/esm/salla-multiple-bundle-product.entry.js +1 -1
- package/dist/esm/salla-next-order-coupon.entry.js +1 -1
- package/dist/esm/salla-notification-item_2.entry.js +1 -1
- package/dist/esm/salla-offer-modal.entry.js +1 -1
- package/dist/esm/salla-offer.entry.js +1 -1
- package/dist/esm/salla-order-branch.entry.js +1 -1
- package/dist/esm/salla-order-cancel.entry.js +1 -1
- package/dist/esm/salla-order-details-multiple-bundle-product.entry.js +1 -1
- package/dist/esm/salla-order-details-options.entry.js +1 -1
- package/dist/esm/salla-order-details.entry.js +1 -1
- package/dist/esm/salla-order-edit-item.entry.js +1 -1
- package/dist/esm/salla-order-edit-product-card.entry.js +1 -1
- package/dist/esm/salla-order-edit.entry.js +1 -1
- package/dist/esm/salla-order-options-modal.entry.js +1 -1
- package/dist/esm/salla-order-shipments.entry.js +1 -1
- package/dist/esm/salla-order-summary.entry.js +1 -1
- package/dist/esm/salla-order-totals-card.entry.js +1 -1
- package/dist/esm/salla-orders.entry.js +1 -1
- package/dist/esm/salla-payments.entry.js +1 -1
- package/dist/esm/salla-placeholder.entry.js +1 -1
- package/dist/esm/salla-price-range.entry.js +1 -1
- package/dist/esm/salla-product-card-embed.entry.js +1 -1
- package/dist/esm/salla-product-card.entry.js +1 -1
- package/dist/esm/salla-product-size-guide.entry.js +1 -1
- package/dist/esm/salla-products-list.entry.js +1 -1
- package/dist/esm/salla-products-slider.entry.js +1 -1
- package/dist/esm/salla-quick-order.entry.js +1 -1
- package/dist/esm/salla-rating-modal-v2.entry.js +1 -1
- package/dist/esm/salla-rating-modal.entry.js +1 -1
- package/dist/esm/salla-review-factors-tags.entry.js +1 -1
- package/dist/esm/salla-review-order-item.entry.js +1 -1
- package/dist/esm/salla-scopes.entry.js +1 -1
- package/dist/esm/salla-search-product-card.entry.js +1 -1
- package/dist/esm/salla-search.entry.js +1 -1
- package/dist/esm/salla-skeleton.entry.js +1 -1
- package/dist/esm/salla-social-share.entry.js +1 -1
- package/dist/esm/salla-social.entry.js +1 -1
- package/dist/esm/salla-tab-content_3.entry.js +1 -1
- package/dist/esm/salla-tiered-offer.entry.js +1 -1
- package/dist/esm/salla-trust-badges.entry.js +1 -1
- package/dist/esm/salla-user-menu.entry.js +1 -1
- package/dist/esm/salla-user-profile.entry.js +1 -1
- package/dist/esm/salla-user-settings.entry.js +1 -1
- package/dist/esm/salla-verify.entry.js +1 -1
- package/dist/esm/salla-wallet.entry.js +1 -1
- package/dist/esm/salla-wishlist-actions.entry.js +1 -1
- package/dist/esm/salla-wishlist-summary.entry.js +1 -1
- package/dist/esm/twilight.js +3 -3
- package/dist/esm/{vanilla-picker-BhmFKXIB.js → vanilla-picker-DMAcm04D.js} +1 -1
- package/dist/twilight/{p-ddb65e87.entry.js → p-06268aeb.entry.js} +1 -1
- package/dist/twilight/{p-6ea4f06c.entry.js → p-0bee12ba.entry.js} +1 -1
- package/dist/twilight/{p-6b767b19.entry.js → p-13215fa2.entry.js} +1 -1
- package/dist/twilight/{p-53f8972b.entry.js → p-170a29db.entry.js} +1 -1
- package/dist/twilight/{p-74ba0473.entry.js → p-1762cfe3.entry.js} +1 -1
- package/dist/twilight/{p-7836ad45.entry.js → p-18b786f4.entry.js} +1 -1
- package/dist/twilight/{p-1d8c6adc.entry.js → p-1a7bee69.entry.js} +1 -1
- package/dist/twilight/{p-21f2167e.entry.js → p-1aaed68f.entry.js} +1 -1
- package/dist/twilight/{p-a64f2fff.entry.js → p-1dc68727.entry.js} +1 -1
- package/dist/twilight/{p-655c7f90.entry.js → p-2059ac26.entry.js} +1 -1
- package/dist/twilight/{p-9897606c.entry.js → p-21358994.entry.js} +1 -1
- package/dist/twilight/{p-a0869ee6.entry.js → p-2308e70c.entry.js} +1 -1
- package/dist/twilight/{p-d35c3f7d.entry.js → p-27366663.entry.js} +1 -1
- package/dist/twilight/{p-da05ae96.entry.js → p-2d983198.entry.js} +1 -1
- package/dist/twilight/{p-bd1dbd4b.entry.js → p-2e204cbc.entry.js} +1 -1
- package/dist/twilight/{p-09300a8d.entry.js → p-2e971549.entry.js} +1 -1
- package/dist/twilight/{p-efeae895.entry.js → p-2f1c8e60.entry.js} +1 -1
- package/dist/twilight/{p-23e45bd7.entry.js → p-328e352d.entry.js} +1 -1
- package/dist/twilight/{p-f407afee.entry.js → p-35163279.entry.js} +1 -1
- package/dist/twilight/{p-3acbfe00.entry.js → p-36ac5a06.entry.js} +1 -1
- package/dist/twilight/{p-d4f3905f.entry.js → p-3845541d.entry.js} +1 -1
- package/dist/twilight/{p-917ce4f9.entry.js → p-38956156.entry.js} +1 -1
- package/dist/twilight/{p-6ea8b4a8.entry.js → p-398e864d.entry.js} +1 -1
- package/dist/twilight/{p-48f26c20.entry.js → p-3d535509.entry.js} +1 -1
- package/dist/twilight/{p-3ba09a8e.entry.js → p-3d7fdd11.entry.js} +1 -1
- package/dist/twilight/p-40a7f8c6.entry.js +4 -0
- package/dist/twilight/{p-dd775629.entry.js → p-426ac7ab.entry.js} +1 -1
- package/dist/twilight/{p-294f6c8e.entry.js → p-42d6eb6b.entry.js} +1 -1
- package/dist/twilight/{p-0746fded.entry.js → p-46ef2b4e.entry.js} +1 -1
- package/dist/twilight/{p-0446f8e3.entry.js → p-47e519e4.entry.js} +1 -1
- package/dist/twilight/{p-799a0589.entry.js → p-48f9cd45.entry.js} +1 -1
- package/dist/twilight/{p-027b5233.entry.js → p-49f2ad4a.entry.js} +1 -1
- package/dist/twilight/{p-f757ba41.entry.js → p-4b135194.entry.js} +1 -1
- package/dist/twilight/p-4b175320.entry.js +4 -0
- package/dist/twilight/{p-a0f2b1b7.entry.js → p-51d7a9f2.entry.js} +1 -1
- package/dist/twilight/{p-0b10b315.entry.js → p-52b99743.entry.js} +1 -1
- package/dist/twilight/{p-788ffe88.entry.js → p-540992c1.entry.js} +1 -1
- package/dist/twilight/{p-9950bd4f.entry.js → p-5c313f74.entry.js} +1 -1
- package/dist/twilight/{p-d566b402.entry.js → p-62842639.entry.js} +1 -1
- package/dist/twilight/{p-0ccf7bde.entry.js → p-635cf0ae.entry.js} +1 -1
- package/dist/twilight/{p-6e45ec66.entry.js → p-6a68c551.entry.js} +1 -1
- package/dist/twilight/{p-cb47fc80.entry.js → p-6b277129.entry.js} +1 -1
- package/dist/twilight/{p-b98b2cda.entry.js → p-6dcfc119.entry.js} +1 -1
- package/dist/twilight/{p-78ff082d.entry.js → p-6e7a12dd.entry.js} +1 -1
- package/dist/twilight/{p-a0f0a176.entry.js → p-700cf9fc.entry.js} +1 -1
- package/dist/twilight/{p-62b8c2cc.entry.js → p-71372771.entry.js} +1 -1
- package/dist/twilight/{p-945eec73.entry.js → p-78d1e145.entry.js} +1 -1
- package/dist/twilight/{p-85f185cc.entry.js → p-7f23b98d.entry.js} +1 -1
- package/dist/twilight/{p-c273ff37.entry.js → p-7f93ea69.entry.js} +1 -1
- package/dist/twilight/{p-6fcf17d4.entry.js → p-81294230.entry.js} +1 -1
- package/dist/twilight/{p-e28f4afc.entry.js → p-833fb416.entry.js} +1 -1
- package/dist/twilight/{p-3de1d01b.entry.js → p-8e938bfa.entry.js} +1 -1
- package/dist/twilight/{p-36f4e851.entry.js → p-8ec3b9f0.entry.js} +1 -1
- package/dist/twilight/{p-0de54240.entry.js → p-9b01e5c2.entry.js} +1 -1
- package/dist/twilight/{p-0156dfe8.entry.js → p-9c284764.entry.js} +1 -1
- package/dist/twilight/{p-8fbf8044.entry.js → p-9d0658a0.entry.js} +1 -1
- package/dist/twilight/{p-8c7614fb.entry.js → p-9d680306.entry.js} +1 -1
- package/dist/twilight/{p-7foc7ca2.js → p-BOWrmsKT.js} +1 -1
- package/dist/twilight/{p-rKbQj-u0.js → p-C3HaKgMG.js} +1 -1
- package/dist/twilight/{p-4asjbsmx.js → p-CjeoBv5q.js} +1 -1
- package/dist/twilight/{p-PcxCrM8A.js → p-CtQLzZ1X.js} +1 -1
- package/dist/twilight/{p-BcMiEHs_.js → p-DVatlmKV.js} +1 -1
- package/dist/twilight/{p-ClbVN5Nv.js → p-D_C0F9n5.js} +1 -1
- package/dist/twilight/{p-HZVqfCuE.js → p-Dq91sWJh.js} +1 -1
- package/dist/twilight/{p-klFskvc_.js → p-UnmOIma5.js} +1 -1
- package/dist/twilight/{p-6536466c.entry.js → p-a0e1d5f7.entry.js} +1 -1
- package/dist/twilight/{p-089f0d34.entry.js → p-a192abb7.entry.js} +1 -1
- package/dist/twilight/{p-0f951af1.entry.js → p-a1d0c650.entry.js} +1 -1
- package/dist/twilight/{p-317048c4.entry.js → p-a1d251d4.entry.js} +1 -1
- package/dist/twilight/{p-d524d26e.entry.js → p-a32fb61e.entry.js} +1 -1
- package/dist/twilight/{p-864959d8.entry.js → p-a5c693c1.entry.js} +1 -1
- package/dist/twilight/{p-52d1d467.entry.js → p-acd9c538.entry.js} +1 -1
- package/dist/twilight/{p-a7a64c45.entry.js → p-adfc54e4.entry.js} +1 -1
- package/dist/twilight/{p-1a875a94.entry.js → p-b5d6ddd7.entry.js} +1 -1
- package/dist/twilight/{p-009048d0.entry.js → p-b70cdf88.entry.js} +1 -1
- package/dist/twilight/{p-31b7fbdc.entry.js → p-b7399739.entry.js} +1 -1
- package/dist/twilight/{p-7ddae4df.entry.js → p-bb4a95d9.entry.js} +1 -1
- package/dist/twilight/{p-ce0a4802.entry.js → p-c1eab306.entry.js} +1 -1
- package/dist/twilight/{p-45a13d19.entry.js → p-c1eaf8c3.entry.js} +1 -1
- package/dist/twilight/{p-0fca7ee0.entry.js → p-ca47204f.entry.js} +1 -1
- package/dist/twilight/{p-39325cfc.entry.js → p-caec4a90.entry.js} +1 -1
- package/dist/twilight/{p-a97a0636.entry.js → p-ce0c6667.entry.js} +1 -1
- package/dist/twilight/{p-a1d43865.entry.js → p-ce7450a9.entry.js} +1 -1
- package/dist/twilight/{p-34b91d42.entry.js → p-d11b211f.entry.js} +1 -1
- package/dist/twilight/{p-8bc3a449.entry.js → p-d62c6529.entry.js} +1 -1
- package/dist/twilight/{p-482fee31.entry.js → p-da14ac3f.entry.js} +1 -1
- package/dist/twilight/{p-a4b9f482.entry.js → p-e0ce5d4c.entry.js} +1 -1
- package/dist/twilight/{p-56ce31f2.entry.js → p-e1d59b7e.entry.js} +1 -1
- package/dist/twilight/{p-7495249f.entry.js → p-e3066c95.entry.js} +1 -1
- package/dist/twilight/{p-32ea6fff.entry.js → p-e34ff202.entry.js} +1 -1
- package/dist/twilight/{p-c62b3757.entry.js → p-e3c46eea.entry.js} +1 -1
- package/dist/twilight/{p-2667efd6.entry.js → p-e6de2f79.entry.js} +1 -1
- package/dist/twilight/{p-d266e9ad.entry.js → p-ea07afaa.entry.js} +1 -1
- package/dist/twilight/{p-45cc68ce.entry.js → p-efb67446.entry.js} +1 -1
- package/dist/twilight/{p-55f765dc.entry.js → p-efb9f953.entry.js} +1 -1
- package/dist/twilight/{p-d21e8092.entry.js → p-f08ff103.entry.js} +1 -1
- package/dist/twilight/{p-23ccf145.entry.js → p-f5605df6.entry.js} +1 -1
- package/dist/twilight/{p-1a1f04ac.entry.js → p-f69a3e84.entry.js} +1 -1
- package/dist/twilight/{p-c33ed919.entry.js → p-f7b65338.entry.js} +1 -1
- package/dist/twilight/{p-5b65fffc.entry.js → p-f92f10c7.entry.js} +1 -1
- package/dist/twilight/{p-f6bae25a.entry.js → p-fa3c4bcf.entry.js} +1 -1
- package/dist/twilight/{p-94eddc5c.entry.js → p-fb8395de.entry.js} +1 -1
- package/dist/twilight/{p-1988457b.entry.js → p-ff65be76.entry.js} +1 -1
- package/dist/twilight/p-iC3SM6Ji.js +9 -0
- package/dist/twilight/{p-BHXx247M.js → p-xX3a9YdQ.js} +1 -1
- package/dist/twilight/twilight.esm.js +1 -1
- package/dist/types/components/salla-conditional-offer/cart-update-window.d.ts +31 -0
- package/dist/types/components/salla-conditional-offer/interfaces.d.ts +70 -13
- package/dist/types/components/salla-conditional-offer/salla-conditional-offer.d.ts +656 -10
- package/dist/types/shared/cart-free-gifts.d.ts +141 -0
- package/dist/types/shared/offer-stages.d.ts +288 -0
- package/package.json +5 -5
- package/dist/cjs/salla-tooltip.cjs.entry.js +0 -42
- package/dist/esm/salla-tooltip.entry.js +0 -40
- package/dist/twilight/p-0e206ed7.entry.js +0 -4
- package/dist/twilight/p-8f5929a9.entry.js +0 -4
- package/dist/twilight/p-Ba21M-o3.js +0 -9
- package/dist/twilight/p-e47f5ec9.entry.js +0 -4
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Crafted with ❤ by Salla
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Shared free-gift lookup for the cart.
|
|
6
|
+
*
|
|
7
|
+
* The gift feature is twilight-only: no theme passes `added-by-offer` /
|
|
8
|
+
* `free-gift-quantity` attributes. `salla-conditional-offer` resolves the gift LINES it
|
|
9
|
+
* lists (read-only, as information) and the cart line count from here, never from theme
|
|
10
|
+
* attributes.
|
|
11
|
+
*
|
|
12
|
+
* Data source: `salla.api.cart.details()` — it hits the `api.cart.show`
|
|
13
|
+
* route, which IS in `CartPresenter::$routesWithItems`, so the response
|
|
14
|
+
* carries `data.cart.items[]` with the per-item keys `added_by_offer`
|
|
15
|
+
* (bool), `offer_id` (int|null) and `free_gift_quantity` (int) set by
|
|
16
|
+
* `CartItemTransformer.php`. Those three names are a pinned
|
|
17
|
+
* contract — do not rename them here or on the components that consume them.
|
|
18
|
+
*
|
|
19
|
+
* We deliberately never read these fields off a `cart::updated` payload:
|
|
20
|
+
* that event fires from `latest()` too, and `api.cart.latest` is NOT in
|
|
21
|
+
* `$routesWithItems`, so a `cart::updated` payload legitimately carries no
|
|
22
|
+
* `items` at all. Callers must fetch on mount and call
|
|
23
|
+
* `refreshCartFreeGifts()` on the event — this module never derives from
|
|
24
|
+
* event payloads.
|
|
25
|
+
*
|
|
26
|
+
* The banner asks for the gift lines and the cart line count on the same tick (initial
|
|
27
|
+
* mount, and again on every `cart::updated`). Concurrent callers share a
|
|
28
|
+
* single in-flight promise, mirroring `_pendingOffersRequest` in
|
|
29
|
+
* `twilight/packages/twilight/src/api/cart.js:831-846`.
|
|
30
|
+
*/
|
|
31
|
+
const EMPTY_GIFT_ITEMS = Object.freeze([]);
|
|
32
|
+
/**
|
|
33
|
+
* The gift LINES from the last parsed response. `null` is the single "nothing has loaded
|
|
34
|
+
* yet" flag shared with `countCache`, which keeps the two from ever disagreeing about
|
|
35
|
+
* which response they came from.
|
|
36
|
+
*/
|
|
37
|
+
let itemsCache = null;
|
|
38
|
+
/**
|
|
39
|
+
* The cart's line count, published in the SAME pass as `itemsCache`. Only meaningful
|
|
40
|
+
* while `itemsCache` is non-null.
|
|
41
|
+
*/
|
|
42
|
+
let countCache = null;
|
|
43
|
+
/**
|
|
44
|
+
* Whether what sits in the caches above came from a response we actually PARSED,
|
|
45
|
+
* as opposed to the graceful fallback a rejected lookup publishes.
|
|
46
|
+
*
|
|
47
|
+
* THIS IS THE CACHE'S WARM/COLD FLAG, and it is separate from the values on purpose.
|
|
48
|
+
*
|
|
49
|
+
* A failed lookup still has to hand its in-flight callers something usable, so the
|
|
50
|
+
* `.catch` below publishes `gifts: []` / `count: null`. But `[]` is TRUTHY, so a cache
|
|
51
|
+
* populated that way used to read as WARM: every later `getCartItemCount()` /
|
|
52
|
+
* `getCartFreeGiftItems()` short-circuited on it and returned the FAILURE's values
|
|
53
|
+
* without ever re-asking. One rejected `details()` therefore pinned the count at
|
|
54
|
+
* "unknown" (or, for `CartNotCreated`, at a hard `0`) for the whole page view, and
|
|
55
|
+
* `salla-conditional-offer` — which renders nothing until it is POSITIVELY told the cart
|
|
56
|
+
* holds items — vanished on a populated cart with a live offer. Nothing corrected it:
|
|
57
|
+
* only `refreshCartFreeGifts()` clears the cache, and that runs from `cart::updated`,
|
|
58
|
+
* which a shopper who does not touch the cart never emits.
|
|
59
|
+
*
|
|
60
|
+
* So a failure now leaves the cache COLD: its fallback values are still handed to the
|
|
61
|
+
* callers already waiting, and the next caller re-asks. Concurrent callers still share
|
|
62
|
+
* one request through `pendingRequest`, so the retry costs one lookup per wave, not one
|
|
63
|
+
* per consumer — and the cheapest failure of all, `CartNotCreated`, never reaches the
|
|
64
|
+
* network at all (`details()` rejects before it requests).
|
|
65
|
+
*/
|
|
66
|
+
let isAuthoritative = false;
|
|
67
|
+
let pendingRequest = null;
|
|
68
|
+
// De-dupes concurrent `refreshCartFreeGifts()` callers — see that function.
|
|
69
|
+
let pendingRefresh = null;
|
|
70
|
+
// Bumped by `refreshCartFreeGifts()`. A fetch only gets to publish into
|
|
71
|
+
// the caches if the generation it started under is still current when it
|
|
72
|
+
// resolves — otherwise a request that was already in flight when the cart
|
|
73
|
+
// mutated would win the race and cache pre-mutation data as if it were
|
|
74
|
+
// fresh. Mirrors `salla.event.on('cart::updated', () => { Salla.api.cart._pendingOffersRequest = null; })`
|
|
75
|
+
// in `twilight/packages/twilight/src/api/cart.js:81`, which abandons its
|
|
76
|
+
// in-flight promise on cart update so a post-mutation caller gets a fresh
|
|
77
|
+
// request — the same idea, expressed as a generation check instead of
|
|
78
|
+
// nulling the promise (nulling it here would defeat the same-tick dedupe
|
|
79
|
+
// that `refreshCartFreeGifts()` relies on).
|
|
80
|
+
let generation = 0;
|
|
81
|
+
/**
|
|
82
|
+
* The cart identity the caches (and any in-flight request) belong to. `undefined` until
|
|
83
|
+
* the first lookup. See `syncCartIdentity`.
|
|
84
|
+
*/
|
|
85
|
+
let cacheIdentity;
|
|
86
|
+
/**
|
|
87
|
+
* The SDK's own name for "this visitor has no cart at all".
|
|
88
|
+
*
|
|
89
|
+
* `salla.api.cart.details()` resolves its id through
|
|
90
|
+
* `getCurrentCartId(false, …, allowCreate = false)`, which returns `null` for a guest with
|
|
91
|
+
* no stored cart id rather than creating one server-side; `details()` then throws this
|
|
92
|
+
* (twilight/src/api/cart.js:192-199). It is a STATEMENT ABOUT THE CART, not a failure to
|
|
93
|
+
* reach the server — no cart means no items — so it is the one rejection that resolves to
|
|
94
|
+
* a hard 0 instead of "unknown".
|
|
95
|
+
*
|
|
96
|
+
* Matched on `name`, never on message text: the wording is not a contract, the name is.
|
|
97
|
+
*/
|
|
98
|
+
export const CART_NOT_CREATED = 'CartNotCreated';
|
|
99
|
+
export function isCartNotCreated(error) {
|
|
100
|
+
return error?.name === CART_NOT_CREATED;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The cart id as the SDK knows it right now, synchronously, or `null` when there is none.
|
|
104
|
+
* Same sources `salla.api.cart.getStoredCartId()` reads (the in-memory latest cart, then
|
|
105
|
+
* storage), with a storage fallback for an SDK that predates it.
|
|
106
|
+
*/
|
|
107
|
+
function readCartIdentity() {
|
|
108
|
+
try {
|
|
109
|
+
const id = salla.api?.cart?.getStoredCartId
|
|
110
|
+
? salla.api.cart.getStoredCartId()
|
|
111
|
+
: salla.storage?.get?.('cart.id');
|
|
112
|
+
return id === undefined || id === null || id === '' ? null : String(id);
|
|
113
|
+
}
|
|
114
|
+
catch (_) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Drops everything cached for a previous cart when the cart id has changed.
|
|
120
|
+
*
|
|
121
|
+
* The caches are otherwise only invalidated by `refreshCartFreeGifts()`, i.e. by
|
|
122
|
+
* `cart::updated`. A silent cross-store login writes the merged cart id directly and
|
|
123
|
+
* emits nothing, so without this a mounted banner would keep serving the previous
|
|
124
|
+
* cart's line count and gifts. Bumping `generation` also stops a request that is still
|
|
125
|
+
* in flight for the old cart from publishing, and the in-flight slot is released so the
|
|
126
|
+
* caller asks again under the new identity.
|
|
127
|
+
*/
|
|
128
|
+
function syncCartIdentity() {
|
|
129
|
+
const current = readCartIdentity();
|
|
130
|
+
if (cacheIdentity !== undefined && cacheIdentity !== current) {
|
|
131
|
+
generation++;
|
|
132
|
+
itemsCache = null;
|
|
133
|
+
countCache = null;
|
|
134
|
+
isAuthoritative = false;
|
|
135
|
+
pendingRequest = null;
|
|
136
|
+
}
|
|
137
|
+
cacheIdentity = current;
|
|
138
|
+
}
|
|
139
|
+
function toNumber(v) {
|
|
140
|
+
if (v === null || v === undefined)
|
|
141
|
+
return 0;
|
|
142
|
+
if (typeof v === 'number')
|
|
143
|
+
return isFinite(v) ? v : 0;
|
|
144
|
+
if (typeof v === 'string')
|
|
145
|
+
return parseFloat(v) || 0;
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
function toNullableNumber(v) {
|
|
149
|
+
if (v === null || v === undefined || v === '')
|
|
150
|
+
return null;
|
|
151
|
+
const n = toNumber(v);
|
|
152
|
+
return isFinite(n) ? n : null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* `details()`'s envelope nests items under `data.cart.items`.
|
|
156
|
+
*
|
|
157
|
+
* Returns `null` — NOT `[]` — when the response carried no items array at all. The two
|
|
158
|
+
* are different facts: `[]` is the server saying "this cart is empty", `null` is "this
|
|
159
|
+
* response cannot answer the question". `getCartItemCount()` is built on that
|
|
160
|
+
* distinction; see `extractCartCount`.
|
|
161
|
+
*/
|
|
162
|
+
function findItemsArray(res) {
|
|
163
|
+
const items = res?.data?.cart?.items ?? res?.data?.items ?? res?.cart?.items ?? res?.items;
|
|
164
|
+
return Array.isArray(items) ? items : null;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* How many LINES the cart holds, or `null` when the response could not say.
|
|
168
|
+
*
|
|
169
|
+
* `null` is load-bearing and must never be collapsed into `0`: a consumer that hides
|
|
170
|
+
* itself on an empty cart has to be able to tell "the cart is empty" apart from "the
|
|
171
|
+
* cart has not loaded yet", or it flickers (or vanishes) on a populated cart while the
|
|
172
|
+
* first `details()` request is still in flight.
|
|
173
|
+
*
|
|
174
|
+
* The items array is the PRIMARY signal because `api.cart.show` is in
|
|
175
|
+
* `CartPresenter::$routesWithItems` and therefore always emits it. `cart.count` is only
|
|
176
|
+
* consulted when the array is missing entirely, and it is a QUANTITY total rather than a
|
|
177
|
+
* line count — close enough for the only question anyone asks of it (is this zero?), and
|
|
178
|
+
* never reached on a well-formed response.
|
|
179
|
+
*/
|
|
180
|
+
function extractCartCount(res, items) {
|
|
181
|
+
if (items !== null)
|
|
182
|
+
return items.length;
|
|
183
|
+
const raw = res?.data?.cart?.count ?? res?.data?.count ?? res?.cart?.count ?? res?.count;
|
|
184
|
+
const count = toNullableNumber(raw);
|
|
185
|
+
return count !== null && count >= 0 ? count : null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The price a gift line would have cost.
|
|
189
|
+
*
|
|
190
|
+
* Tries the keys in the order `CartItemTransformer` fills them, and accepts both the
|
|
191
|
+
* `{amount, currency}` shape a serialized `Money` produces and a bare number/string.
|
|
192
|
+
* A zero is treated as "nothing usable": the gift's own charged price IS zero, so a 0
|
|
193
|
+
* here means we read the wrong key, and rendering a struck-through "0" would be worse
|
|
194
|
+
* than rendering no price at all.
|
|
195
|
+
*/
|
|
196
|
+
function extractOriginalPrice(item) {
|
|
197
|
+
for (const raw of [item?.original_price, item?.product_price, item?.price]) {
|
|
198
|
+
const value = raw && typeof raw === 'object' ? raw.amount : raw;
|
|
199
|
+
const amount = toNullableNumber(value);
|
|
200
|
+
if (amount !== null && amount > 0)
|
|
201
|
+
return amount;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* How many units of this line the offer granted, or `0` when the answer is "none".
|
|
207
|
+
*
|
|
208
|
+
* The two absent-ish shapes are NOT the same fact and must not collapse:
|
|
209
|
+
*
|
|
210
|
+
* • the key is MISSING (`undefined`/`null`/`''`) — an older payload, or a
|
|
211
|
+
* transformer that did not fill it. Nothing contradicts "the whole line is
|
|
212
|
+
* the gift" for an unknown line quantity, so fall back to the line.
|
|
213
|
+
* • the key is PRESENT and `0` — the server stating there are no free units on
|
|
214
|
+
* this line. That is an answer, and it must be believed.
|
|
215
|
+
*/
|
|
216
|
+
function resolveFreeQuantity(item, lineQuantity) {
|
|
217
|
+
const raw = item?.free_gift_quantity;
|
|
218
|
+
const stated = raw !== undefined && raw !== null && raw !== '';
|
|
219
|
+
if (stated)
|
|
220
|
+
return toNumber(raw);
|
|
221
|
+
return lineQuantity > 0 ? lineQuantity : 0;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The gift LINES, in cart order. Only lines the server flagged `added_by_offer` with at
|
|
225
|
+
* least one granted free unit qualify.
|
|
226
|
+
*/
|
|
227
|
+
function buildGiftItems(items) {
|
|
228
|
+
const gifts = [];
|
|
229
|
+
for (const item of items) {
|
|
230
|
+
if (item?.id === undefined || item?.id === null)
|
|
231
|
+
continue;
|
|
232
|
+
if (!item.added_by_offer)
|
|
233
|
+
continue;
|
|
234
|
+
const lineQuantity = toNumber(item.quantity);
|
|
235
|
+
const freeQuantity = resolveFreeQuantity(item, lineQuantity);
|
|
236
|
+
// No free units ⇒ not a gift LINE, whatever the flag says. Listing it anyway
|
|
237
|
+
// would put a line the shopper PAID for in full into the gifts card, labelled
|
|
238
|
+
// "free" — the card states what the offer granted, so a non-positive free count
|
|
239
|
+
// means it granted nothing here.
|
|
240
|
+
if (freeQuantity <= 0)
|
|
241
|
+
continue;
|
|
242
|
+
gifts.push({
|
|
243
|
+
id: String(item.id),
|
|
244
|
+
offerId: toNullableNumber(item.offer_id),
|
|
245
|
+
quantity: freeQuantity,
|
|
246
|
+
freeQuantity,
|
|
247
|
+
lineQuantity,
|
|
248
|
+
name: String(item.product_name ?? item.name ?? ''),
|
|
249
|
+
imageUrl: String(item.product_image?.url ?? item.product_image ?? item.image?.url ?? ''),
|
|
250
|
+
imageAlt: String(item.product_image?.alt ?? item.image?.alt ?? item.product_name ?? ''),
|
|
251
|
+
url: String(item.url ?? ''),
|
|
252
|
+
originalPrice: extractOriginalPrice(item),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return gifts;
|
|
256
|
+
}
|
|
257
|
+
function fetchAndCache() {
|
|
258
|
+
if (pendingRequest)
|
|
259
|
+
return pendingRequest;
|
|
260
|
+
// Snapshot the generation this request started under — see the
|
|
261
|
+
// `generation` doc comment above for why.
|
|
262
|
+
const startedAtGeneration = generation;
|
|
263
|
+
const request = Promise.resolve()
|
|
264
|
+
.then(() => salla.api?.cart?.details?.())
|
|
265
|
+
.then((res) => {
|
|
266
|
+
const found = findItemsArray(res);
|
|
267
|
+
const items = found ?? [];
|
|
268
|
+
// Both projections come out of the SAME response, in one pass.
|
|
269
|
+
return {
|
|
270
|
+
gifts: buildGiftItems(items),
|
|
271
|
+
count: extractCartCount(res, found),
|
|
272
|
+
// The response was reached and parsed — these values are the cart, so the cache
|
|
273
|
+
// they land in may be short-circuited on. See `isAuthoritative`.
|
|
274
|
+
ok: true,
|
|
275
|
+
};
|
|
276
|
+
})
|
|
277
|
+
// A failed fetch shouldn't throw into a badge/lock render path — fail
|
|
278
|
+
// closed to "no gift info" (or whatever we already knew) rather than
|
|
279
|
+
// breaking the rest of the cart UI.
|
|
280
|
+
//
|
|
281
|
+
// The count splits the failures in two. `CartNotCreated` is the SDK
|
|
282
|
+
// stating there is no cart, which is a definitive EMPTY and resolves to
|
|
283
|
+
// 0 — without it, the ordinary first-time guest sits on "unknown"
|
|
284
|
+
// forever, because no request will ever succeed and no `cart::updated`
|
|
285
|
+
// is coming to correct it. Everything else (network, 5xx) says nothing
|
|
286
|
+
// about the contents and still fails OPEN to `null`, so a flaky
|
|
287
|
+
// connection can never claim the cart is empty.
|
|
288
|
+
.catch((error) => ({
|
|
289
|
+
gifts: itemsCache ?? [],
|
|
290
|
+
count: isCartNotCreated(error) ? 0 : countCache,
|
|
291
|
+
/**
|
|
292
|
+
* NOT authoritative, `CartNotCreated` included.
|
|
293
|
+
*
|
|
294
|
+
* `0` stays the right ANSWER for it — no cart means no items, and a first-time
|
|
295
|
+
* guest must be told "empty" rather than sitting on "unknown" — but it is no longer
|
|
296
|
+
* a FINAL one. The very first cart lookup on a page is fired before
|
|
297
|
+
* `salla.onReady()` has settled, so it can be answered by an SDK that has not yet
|
|
298
|
+
* installed the cart id (or, earlier still, not yet installed `salla.config`, whose
|
|
299
|
+
* `isGuest()` the id resolution calls unguarded). Both spell a cart that is merely
|
|
300
|
+
* unknown as one that is empty. Leaving the cache cold lets the next lookup — from
|
|
301
|
+
* a bootstrapped SDK — say so, and on a cart that genuinely is empty the retry
|
|
302
|
+
* rejects again without a request.
|
|
303
|
+
*/
|
|
304
|
+
ok: false,
|
|
305
|
+
}))
|
|
306
|
+
.then(({ gifts, count, ok }) => {
|
|
307
|
+
// Only publish if nothing invalidated us while we were in flight.
|
|
308
|
+
// Otherwise this is pre-mutation data racing a newer, authoritative
|
|
309
|
+
// request — publishing it would make the lookups short-circuit on stale
|
|
310
|
+
// data until the NEXT mutation happens to come along.
|
|
311
|
+
if (startedAtGeneration === generation) {
|
|
312
|
+
itemsCache = gifts;
|
|
313
|
+
countCache = count;
|
|
314
|
+
isAuthoritative = ok;
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
.finally(() => {
|
|
318
|
+
// Only release OUR slot: an identity change may already have replaced it.
|
|
319
|
+
if (pendingRequest === request)
|
|
320
|
+
pendingRequest = null;
|
|
321
|
+
});
|
|
322
|
+
pendingRequest = request;
|
|
323
|
+
return request;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Every free-gift LINE currently in the cart, in cart order.
|
|
327
|
+
*
|
|
328
|
+
* Shares the cache, the in-flight promise and the generation guard with
|
|
329
|
+
* `getCartItemCount()` — the banner asking for both still issues exactly ONE
|
|
330
|
+
* `details()` request per cart mutation.
|
|
331
|
+
*/
|
|
332
|
+
export function getCartFreeGiftItems() {
|
|
333
|
+
syncCartIdentity();
|
|
334
|
+
// `isAuthoritative`, not `itemsCache` alone — a failed lookup leaves `itemsCache` a
|
|
335
|
+
// truthy `[]`, which is not an answer. See `isAuthoritative`.
|
|
336
|
+
if (isAuthoritative && itemsCache) {
|
|
337
|
+
return Promise.resolve(itemsCache);
|
|
338
|
+
}
|
|
339
|
+
return fetchAndCache().then(() => itemsCache ?? EMPTY_GIFT_ITEMS);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Whether the cached cart picture came from a response that was actually reached and
|
|
343
|
+
* parsed, rather than from the fallback a rejected lookup publishes.
|
|
344
|
+
*
|
|
345
|
+
* For the ONE consumer that has to tell "the cart is empty" apart from "the lookup could
|
|
346
|
+
* not say, and guessed empty": `salla-conditional-offer` stops before it even asks for the
|
|
347
|
+
* offer when the count is `0`, which is unrecoverable if that `0` was a guess. Everything
|
|
348
|
+
* else should just read the values — they are already the best available answer.
|
|
349
|
+
*
|
|
350
|
+
* Synchronous and side-effect free: it reports on what is cached NOW, so a caller that
|
|
351
|
+
* wants a fresh answer must still await one of the lookups above.
|
|
352
|
+
*/
|
|
353
|
+
export function hasAuthoritativeCartData() {
|
|
354
|
+
syncCartIdentity();
|
|
355
|
+
return isAuthoritative;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* How many LINES the cart holds — `null` while that is genuinely UNKNOWN (nothing has
|
|
359
|
+
* loaded yet, or the lookup failed), never `0` for either.
|
|
360
|
+
*
|
|
361
|
+
* The distinction is the whole point: `salla-conditional-offer` hides its banner on an
|
|
362
|
+
* empty cart, and a `0` that really meant "still loading" would make the banner flicker
|
|
363
|
+
* or disappear on a populated one. Shares the cache, the in-flight promise and the
|
|
364
|
+
* generation guard with `getCartFreeGiftItems()`, so asking for the count costs no
|
|
365
|
+
* request of its own.
|
|
366
|
+
*/
|
|
367
|
+
export function getCartItemCount() {
|
|
368
|
+
syncCartIdentity();
|
|
369
|
+
// THE ONE THAT HID THE BANNER. `itemsCache` alone read a failed lookup's `[]` as a warm
|
|
370
|
+
// cache and handed back its `null`/`0` count for ever — see `isAuthoritative`.
|
|
371
|
+
if (isAuthoritative && itemsCache) {
|
|
372
|
+
return Promise.resolve(countCache);
|
|
373
|
+
}
|
|
374
|
+
return fetchAndCache().then(() => countCache);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Invalidates the cache and re-fetches — call this from a `cart::updated`
|
|
378
|
+
* handler, ignoring the event payload entirely.
|
|
379
|
+
*
|
|
380
|
+
* Bumps `generation` first so that any request already in flight (started
|
|
381
|
+
* before this invalidation) can no longer publish into `cache` when it
|
|
382
|
+
* settles — mirrors `salla.event.on('cart::updated', () => { Salla.api.cart._pendingOffersRequest = null; })`
|
|
383
|
+
* in `twilight/packages/twilight/src/api/cart.js:81`, which abandons its
|
|
384
|
+
* own in-flight promise on cart update for the same reason.
|
|
385
|
+
*
|
|
386
|
+
* Then chains a fresh fetch BEHIND whatever was already in flight (rather
|
|
387
|
+
* than firing a second request in parallel): once that prior request
|
|
388
|
+
* settles — its result now guaranteed to be discarded by the generation
|
|
389
|
+
* check above — `fetchAndCache()` starts a request under the new
|
|
390
|
+
* generation, which is free to publish.
|
|
391
|
+
*
|
|
392
|
+
* `pendingRefresh` de-dupes concurrent callers of THIS function the same
|
|
393
|
+
* way `pendingRequest` de-dupes concurrent fetches: N components all
|
|
394
|
+
* invalidating within the same synchronous `cart::updated` dispatch share
|
|
395
|
+
* one refresh chain and therefore, ultimately, one fresh HTTP request.
|
|
396
|
+
*
|
|
397
|
+
* That sharing window is deliberately NARROW — it closes the instant the
|
|
398
|
+
* fresh request goes out, NOT when it comes back. Holding it open for the
|
|
399
|
+
* whole round trip silently coalesced two DIFFERENT cart mutations into one
|
|
400
|
+
* refresh: the second `cart::updated` got handed the first one's promise, so
|
|
401
|
+
* it never bumped `generation` and never issued a request, and the response
|
|
402
|
+
* that eventually published had been asked for BEFORE the second mutation
|
|
403
|
+
* happened. The cart then sat on pre-mutation gift data — a merged line
|
|
404
|
+
* showing yesterday's paid quantity beside yesterday's corrected total —
|
|
405
|
+
* until some later mutation happened to come along. A shopper clicking "+"
|
|
406
|
+
* twice inside one round trip is all it took.
|
|
407
|
+
*/
|
|
408
|
+
export function refreshCartFreeGifts() {
|
|
409
|
+
if (pendingRefresh)
|
|
410
|
+
return pendingRefresh;
|
|
411
|
+
generation++;
|
|
412
|
+
itemsCache = null;
|
|
413
|
+
countCache = null;
|
|
414
|
+
isAuthoritative = false;
|
|
415
|
+
// This invalidation already covers whatever id the cart has now.
|
|
416
|
+
cacheIdentity = readCartIdentity();
|
|
417
|
+
const priorRequest = pendingRequest ?? Promise.resolve();
|
|
418
|
+
const startFreshRequest = () => {
|
|
419
|
+
// Sharing ends here: everything that invalidated up to this point is
|
|
420
|
+
// described by the request about to go out, and everything after it is not.
|
|
421
|
+
if (pendingRefresh === refresh)
|
|
422
|
+
pendingRefresh = null;
|
|
423
|
+
return fetchAndCache();
|
|
424
|
+
};
|
|
425
|
+
// `.then(f, f)` — a prior request that REJECTED still has to be waited out
|
|
426
|
+
// before the replacement is issued, or the two overlap.
|
|
427
|
+
const refresh = priorRequest
|
|
428
|
+
.then(startFreshRequest, startFreshRequest)
|
|
429
|
+
.finally(() => {
|
|
430
|
+
// Only ever clear OUR slot. A later refresh may already own it (it can
|
|
431
|
+
// only have taken it after `startFreshRequest` released it), and nulling
|
|
432
|
+
// that one would un-dedupe its same-tick callers.
|
|
433
|
+
if (pendingRefresh === refresh)
|
|
434
|
+
pendingRefresh = null;
|
|
435
|
+
});
|
|
436
|
+
pendingRefresh = refresh;
|
|
437
|
+
return refresh;
|
|
438
|
+
}
|
|
439
|
+
/** @internal exported for tests only — resets module-level state between specs. */
|
|
440
|
+
export function __resetCartFreeGiftsForTests() {
|
|
441
|
+
itemsCache = null;
|
|
442
|
+
countCache = null;
|
|
443
|
+
isAuthoritative = false;
|
|
444
|
+
pendingRequest = null;
|
|
445
|
+
pendingRefresh = null;
|
|
446
|
+
generation = 0;
|
|
447
|
+
cacheIdentity = undefined;
|
|
448
|
+
}
|