@salla.sa/twilight-components 2.14.589 → 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-BbRL5Spe.js → filepond-DkObEO32.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-poster-DVUBjaQx.js → filepond-plugin-file-poster-wXgeY5cU.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-validate-size-BR7SoIq0.js → filepond-plugin-file-validate-size-CWleid1C.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-validate-type-CEkMfiUA.js → filepond-plugin-file-validate-type-CJLl2hbe.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-edit-p-XQj23Q.js → filepond-plugin-image-edit-Bp6AxXM9.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-exif-orientation-DeRO_gvX.js → filepond-plugin-image-exif-orientation-abWKSX9-.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-preview-Bfm8Swyp.js → filepond-plugin-image-preview-DxprNycS.js} +1 -1
- package/dist/cjs/{index-CfGnMoB7.js → index-DBN0LueR.js} +10 -14
- package/dist/cjs/{intlTelInput-Pqo18emd.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 +35 -6
- 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-DWuMUfiW.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/components/salla-products-list/salla-products-list.js +35 -6
- 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/components/salla-products-list2.js +34 -5
- package/dist/docs.json +22 -9
- package/dist/esm/{filepond-CJqzOtq7.js → filepond-Cstf5r6K.js} +1 -1
- package/dist/esm/{filepond-plugin-file-poster-CPydcEjJ.js → filepond-plugin-file-poster-GibBgGHW.js} +1 -1
- package/dist/esm/{filepond-plugin-file-validate-size-Yy4pNy1Q.js → filepond-plugin-file-validate-size-CLc6zco6.js} +1 -1
- package/dist/esm/{filepond-plugin-file-validate-type-Ck1P2E4e.js → filepond-plugin-file-validate-type-Ca3dc8p0.js} +1 -1
- package/dist/esm/{filepond-plugin-image-edit-DEUmnuGf.js → filepond-plugin-image-edit-CNGBLi7d.js} +1 -1
- package/dist/esm/{filepond-plugin-image-exif-orientation-BPgJye8E.js → filepond-plugin-image-exif-orientation-Dmmu9-UQ.js} +1 -1
- package/dist/esm/{filepond-plugin-image-preview-CqUSx6BO.js → filepond-plugin-image-preview-KX15tx60.js} +1 -1
- package/dist/esm/{index-BnDYuqv2.js → index-UnmOIma5.js} +10 -14
- package/dist/esm/{intlTelInput-Z8OsAFtr.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 +35 -6
- 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-DnAUrfGC.js → vanilla-picker-DMAcm04D.js} +1 -1
- package/dist/twilight/{p-fdb16a84.entry.js → p-06268aeb.entry.js} +1 -1
- package/dist/twilight/{p-cff5dd0b.entry.js → p-0bee12ba.entry.js} +1 -1
- package/dist/twilight/{p-dfdd4c78.entry.js → p-13215fa2.entry.js} +1 -1
- package/dist/twilight/{p-ef5a0e71.entry.js → p-170a29db.entry.js} +1 -1
- package/dist/twilight/{p-179c76df.entry.js → p-1762cfe3.entry.js} +1 -1
- package/dist/twilight/{p-3319b19b.entry.js → p-18b786f4.entry.js} +1 -1
- package/dist/twilight/{p-9932f7a0.entry.js → p-1a7bee69.entry.js} +1 -1
- package/dist/twilight/{p-152cac66.entry.js → p-1aaed68f.entry.js} +1 -1
- package/dist/twilight/{p-1bafeac8.entry.js → p-1dc68727.entry.js} +1 -1
- package/dist/twilight/{p-d38ae081.entry.js → p-2059ac26.entry.js} +1 -1
- package/dist/twilight/{p-45b9c237.entry.js → p-21358994.entry.js} +1 -1
- package/dist/twilight/{p-c895701a.entry.js → p-2308e70c.entry.js} +1 -1
- package/dist/twilight/{p-35dabed0.entry.js → p-27366663.entry.js} +1 -1
- package/dist/twilight/{p-80ead7f0.entry.js → p-2d983198.entry.js} +1 -1
- package/dist/twilight/{p-c5948d07.entry.js → p-2e204cbc.entry.js} +1 -1
- package/dist/twilight/{p-eb0c5e39.entry.js → p-2e971549.entry.js} +1 -1
- package/dist/twilight/{p-721b6019.entry.js → p-2f1c8e60.entry.js} +1 -1
- package/dist/twilight/{p-7eeb788a.entry.js → p-328e352d.entry.js} +1 -1
- package/dist/twilight/{p-53dc0615.entry.js → p-35163279.entry.js} +1 -1
- package/dist/twilight/{p-df933610.entry.js → p-36ac5a06.entry.js} +1 -1
- package/dist/twilight/{p-2cdcd70d.entry.js → p-3845541d.entry.js} +1 -1
- package/dist/twilight/{p-e0b0e90f.entry.js → p-38956156.entry.js} +1 -1
- package/dist/twilight/{p-ad7814d7.entry.js → p-398e864d.entry.js} +1 -1
- package/dist/twilight/{p-c2858bed.entry.js → p-3d535509.entry.js} +1 -1
- package/dist/twilight/{p-827fa966.entry.js → p-3d7fdd11.entry.js} +1 -1
- package/dist/twilight/p-40a7f8c6.entry.js +4 -0
- package/dist/twilight/{p-ba61073e.entry.js → p-426ac7ab.entry.js} +1 -1
- package/dist/twilight/{p-0ecf936e.entry.js → p-42d6eb6b.entry.js} +1 -1
- package/dist/twilight/{p-1c812d90.entry.js → p-46ef2b4e.entry.js} +1 -1
- package/dist/twilight/{p-700f2c8b.entry.js → p-47e519e4.entry.js} +1 -1
- package/dist/twilight/{p-08a8a1ff.entry.js → p-48f9cd45.entry.js} +1 -1
- package/dist/twilight/{p-47d94d77.entry.js → p-49f2ad4a.entry.js} +1 -1
- package/dist/twilight/{p-652ab33d.entry.js → p-4b135194.entry.js} +1 -1
- package/dist/twilight/p-4b175320.entry.js +4 -0
- package/dist/twilight/{p-4d0df0fe.entry.js → p-51d7a9f2.entry.js} +1 -1
- package/dist/twilight/{p-ed7c4f53.entry.js → p-52b99743.entry.js} +1 -1
- package/dist/twilight/{p-11a1c557.entry.js → p-540992c1.entry.js} +1 -1
- package/dist/twilight/{p-1a598e9b.entry.js → p-5c313f74.entry.js} +1 -1
- package/dist/twilight/{p-cdad2ba2.entry.js → p-62842639.entry.js} +1 -1
- package/dist/twilight/{p-f5ccd5d1.entry.js → p-635cf0ae.entry.js} +1 -1
- package/dist/twilight/{p-4e50bc09.entry.js → p-6a68c551.entry.js} +1 -1
- package/dist/twilight/{p-085aac95.entry.js → p-6b277129.entry.js} +1 -1
- package/dist/twilight/{p-81cdd5c5.entry.js → p-6dcfc119.entry.js} +1 -1
- package/dist/twilight/{p-5537738b.entry.js → p-6e7a12dd.entry.js} +1 -1
- package/dist/twilight/{p-018629e9.entry.js → p-700cf9fc.entry.js} +1 -1
- package/dist/twilight/{p-7c9e88d3.entry.js → p-71372771.entry.js} +1 -1
- package/dist/twilight/{p-760d1b6c.entry.js → p-78d1e145.entry.js} +1 -1
- package/dist/twilight/{p-bf7e7835.entry.js → p-7f23b98d.entry.js} +1 -1
- package/dist/twilight/{p-80228706.entry.js → p-7f93ea69.entry.js} +1 -1
- package/dist/twilight/{p-bd559920.entry.js → p-81294230.entry.js} +1 -1
- package/dist/twilight/{p-c0304211.entry.js → p-833fb416.entry.js} +1 -1
- package/dist/twilight/{p-cbeff415.entry.js → p-8e938bfa.entry.js} +1 -1
- package/dist/twilight/{p-ae8b1d8b.entry.js → p-8ec3b9f0.entry.js} +1 -1
- package/dist/twilight/{p-56f0cfcb.entry.js → p-9b01e5c2.entry.js} +1 -1
- package/dist/twilight/{p-85f0dcd7.entry.js → p-9c284764.entry.js} +1 -1
- package/dist/twilight/{p-0efb20f7.entry.js → p-9d0658a0.entry.js} +1 -1
- package/dist/twilight/{p-444e6c0b.entry.js → p-9d680306.entry.js} +1 -1
- package/dist/twilight/{p-CR5J1D0q.js → p-BOWrmsKT.js} +1 -1
- package/dist/twilight/{p-B7eX2j-t.js → p-C3HaKgMG.js} +1 -1
- package/dist/twilight/{p-G2sLbl_C.js → p-CjeoBv5q.js} +1 -1
- package/dist/twilight/{p-DTfPuMCT.js → p-CtQLzZ1X.js} +1 -1
- package/dist/twilight/{p-BXv9XzTR.js → p-DVatlmKV.js} +1 -1
- package/dist/twilight/{p-DV1rxosP.js → p-D_C0F9n5.js} +1 -1
- package/dist/twilight/{p-3jS5Dm-u.js → p-Dq91sWJh.js} +1 -1
- package/dist/twilight/{p-BnDYuqv2.js → p-UnmOIma5.js} +1 -1
- package/dist/twilight/{p-0b53b377.entry.js → p-a0e1d5f7.entry.js} +1 -1
- package/dist/twilight/{p-6f26c1cd.entry.js → p-a192abb7.entry.js} +1 -1
- package/dist/twilight/{p-6ca5c1b6.entry.js → p-a1d0c650.entry.js} +1 -1
- package/dist/twilight/{p-171e4a2f.entry.js → p-a1d251d4.entry.js} +1 -1
- package/dist/twilight/{p-71c70e04.entry.js → p-a32fb61e.entry.js} +1 -1
- package/dist/twilight/{p-cfe6fbe3.entry.js → p-a5c693c1.entry.js} +1 -1
- package/dist/twilight/{p-11b351be.entry.js → p-acd9c538.entry.js} +1 -1
- package/dist/twilight/{p-d38e101f.entry.js → p-adfc54e4.entry.js} +1 -1
- package/dist/twilight/{p-ea8379b8.entry.js → p-b5d6ddd7.entry.js} +1 -1
- package/dist/twilight/{p-e1a8f943.entry.js → p-b70cdf88.entry.js} +1 -1
- package/dist/twilight/{p-8cf71ac9.entry.js → p-b7399739.entry.js} +1 -1
- package/dist/twilight/{p-9bc5c3d5.entry.js → p-bb4a95d9.entry.js} +1 -1
- package/dist/twilight/{p-66ae8f3b.entry.js → p-c1eab306.entry.js} +1 -1
- package/dist/twilight/{p-3b14f3d4.entry.js → p-c1eaf8c3.entry.js} +1 -1
- package/dist/twilight/{p-67e24689.entry.js → p-ca47204f.entry.js} +1 -1
- package/dist/twilight/{p-612fb2e8.entry.js → p-caec4a90.entry.js} +1 -1
- package/dist/twilight/{p-7ccdd251.entry.js → p-ce0c6667.entry.js} +1 -1
- package/dist/twilight/{p-df3feae1.entry.js → p-ce7450a9.entry.js} +1 -1
- package/dist/twilight/{p-4262b4d7.entry.js → p-d11b211f.entry.js} +1 -1
- package/dist/twilight/{p-8e679d21.entry.js → p-d62c6529.entry.js} +1 -1
- package/dist/twilight/{p-8ef3ef7d.entry.js → p-da14ac3f.entry.js} +1 -1
- package/dist/twilight/{p-0f5846fe.entry.js → p-e0ce5d4c.entry.js} +1 -1
- package/dist/twilight/{p-aac26dda.entry.js → p-e1d59b7e.entry.js} +1 -1
- package/dist/twilight/{p-00bc0e76.entry.js → p-e3066c95.entry.js} +1 -1
- package/dist/twilight/{p-1d1315d7.entry.js → p-e34ff202.entry.js} +1 -1
- package/dist/twilight/{p-ff5362e3.entry.js → p-e3c46eea.entry.js} +1 -1
- package/dist/twilight/{p-661755d7.entry.js → p-e6de2f79.entry.js} +1 -1
- package/dist/twilight/{p-331fe074.entry.js → p-ea07afaa.entry.js} +1 -1
- package/dist/twilight/{p-f70bf6b0.entry.js → p-efb67446.entry.js} +1 -1
- package/dist/twilight/p-efb9f953.entry.js +4 -0
- package/dist/twilight/{p-343a9ec3.entry.js → p-f08ff103.entry.js} +1 -1
- package/dist/twilight/{p-ba5f879e.entry.js → p-f5605df6.entry.js} +1 -1
- package/dist/twilight/{p-9fd926a2.entry.js → p-f69a3e84.entry.js} +1 -1
- package/dist/twilight/{p-0e1f8e61.entry.js → p-f7b65338.entry.js} +1 -1
- package/dist/twilight/{p-dcaaa19e.entry.js → p-f92f10c7.entry.js} +1 -1
- package/dist/twilight/{p-e49ce881.entry.js → p-fa3c4bcf.entry.js} +1 -1
- package/dist/twilight/{p-3771c21e.entry.js → p-fb8395de.entry.js} +1 -1
- package/dist/twilight/{p-3d08b459.entry.js → p-ff65be76.entry.js} +1 -1
- package/dist/twilight/{p-DpkirgB4.js → p-iC3SM6Ji.js} +1 -1
- package/dist/twilight/{p-8Y4Ctfsa.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/components/salla-products-list/salla-products-list.d.ts +4 -0
- package/dist/types/components.d.ts +2 -2
- 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-483551e7.entry.js +0 -4
- package/dist/twilight/p-648a4d8d.entry.js +0 -4
- package/dist/twilight/p-afc3d17b.entry.js +0 -4
- package/dist/twilight/p-bdf47e97.entry.js +0 -4
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Crafted with ❤ by Salla
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Multi-stage promotion progress.
|
|
6
|
+
*
|
|
7
|
+
* Pure, component-agnostic helpers for turning a shopper's current cart value
|
|
8
|
+
* plus a list of stage thresholds into "which stage is Locked / Current /
|
|
9
|
+
* Completed, and how full is each segment".
|
|
10
|
+
*
|
|
11
|
+
* Deliberately NOT shared with `salla-tiered-offer`'s `TierUtils`:
|
|
12
|
+
* - `TierUtils.findCurrentTierIndex` is 1-based with `0` overloaded to mean
|
|
13
|
+
* "no tier reached", which cannot express "stage 0 is reached".
|
|
14
|
+
* - It takes `TieredOfferTier[]`, a shape a conditional-offer `Discount` does
|
|
15
|
+
* not share a single field with.
|
|
16
|
+
* - It assumes its caller already sorted; the conditional-offer payload is
|
|
17
|
+
* emitted UNSORTED by the backend.
|
|
18
|
+
*
|
|
19
|
+
* So this module takes plain numbers, is 0-based, uses an explicit
|
|
20
|
+
* `NO_STAGE_REACHED` sentinel, and sorts defensively on every call.
|
|
21
|
+
*
|
|
22
|
+
* ONE deliberate exception to "pure": `getCurrencyDecimals()` reads the store's money
|
|
23
|
+
* precision off `salla.config` so that `getRemainingToStage()` can round AT THE SOURCE
|
|
24
|
+
* and every consumer — including the `products_count` path that never touches
|
|
25
|
+
* `salla.money()` — benefits without each of them remembering to. It is fully guarded and
|
|
26
|
+
* falls back to `DEFAULT_CURRENCY_DECIMALS`, so a missing SDK degrades rather than throws;
|
|
27
|
+
* callers that want it hermetic can pass the measure and stub `salla.config`.
|
|
28
|
+
*
|
|
29
|
+
* ---------------------------------------------------------------------------
|
|
30
|
+
* WHO DECIDES A TIER IS UNLOCKED
|
|
31
|
+
* ---------------------------------------------------------------------------
|
|
32
|
+
* The ENGINE does, and it now says so: every `details.discounts[]` entry carries
|
|
33
|
+
* `is_applied`. Threshold arithmetic here is a PROJECTION ("how far to go"), never a
|
|
34
|
+
* CLAIM about what the engine granted, because the engine's formula
|
|
35
|
+
*
|
|
36
|
+
* ($subtotal - $alreadyAppliedDiscount) - $excludedItemsAmount >= min_spend
|
|
37
|
+
*
|
|
38
|
+
* uses two terms that are not in the payload at all. On beta a gift tier at `min_spend`
|
|
39
|
+
* 300 with `current_value` 300 rendered as unlocked while the engine had refused it (the
|
|
40
|
+
* gift's own price is in that tier's excluded set), so no arithmetic over shipped keys
|
|
41
|
+
* could ever have agreed with it.
|
|
42
|
+
*
|
|
43
|
+
* BUT `is_applied` IS NOT "THE SHOPPER IS PAST THIS TIER". It answers a narrower
|
|
44
|
+
* question, and reading it as the broader one is what produced the second beta report.
|
|
45
|
+
* `CartOfferDiscount::apply()` walks the tiers, keeps the LAST one whose conditions pass
|
|
46
|
+
* and calls `discountItem()` ONCE after the loop, so a cart offer grants EXACTLY ONE
|
|
47
|
+
* reward — the best qualifying tier. Every earlier tier is therefore `is_applied: false`
|
|
48
|
+
* even for a shopper who sailed past it: its reward is SUPERSEDED, not out of reach. A
|
|
49
|
+
* cart at `current_value` 510 came back as `false / false / true` and the badge row read
|
|
50
|
+
* LOCKED / LOCKED / CHECKED, which is unreadable as a progress stepper.
|
|
51
|
+
*
|
|
52
|
+
* So there are TWO questions and this module answers both, separately:
|
|
53
|
+
* - "which tier gave the reward" — the single winner, `appliedIndex`.
|
|
54
|
+
* - "is the shopper past this tier" — cumulative, `met`, which is what the ✓, the
|
|
55
|
+
* strikethrough, the lock and the "N of M" count render from.
|
|
56
|
+
*
|
|
57
|
+
* The passed tiers carry a ✓ AND a strikethrough while the tier ahead carries a lock.
|
|
58
|
+
* Struck-through means superseded, so a ✓ means THRESHOLD PASSED, not REWARD GRANTED.
|
|
59
|
+
*
|
|
60
|
+
* Hence the split, and it is load-bearing:
|
|
61
|
+
* - `met` / `metCount` / `nextUnmetIndex` / `allMet` are the ENGINE-DERIVED verdict.
|
|
62
|
+
* Render from these.
|
|
63
|
+
* - `appliedIndex` names the one tier that actually paid out. Diagnostics only so far.
|
|
64
|
+
* - `lastReachedIndex` / `currentIndex` / `isComplete` / `fills` / `overallPercentage`
|
|
65
|
+
* are pure threshold projections. Use them for distance and bar geometry only.
|
|
66
|
+
*
|
|
67
|
+
* `is_applied` is TRI-STATE, because twilight and the Store package deploy independently:
|
|
68
|
+
* absent means "this backend does not report it yet" and falls back to the arithmetic,
|
|
69
|
+
* while `false` is a real verdict and outranks the arithmetic. See `readAppliedFlag`.
|
|
70
|
+
*
|
|
71
|
+
* `salla-tiered-offer` keeps its own copy of the defensive ascending sort and the
|
|
72
|
+
* completed/total ratio; that duplication is deliberate and not migrated here.
|
|
73
|
+
*/
|
|
74
|
+
/** Explicit sentinel: no stage has been reached yet (or there is no next stage). */
|
|
75
|
+
export const NO_STAGE_REACHED = -1;
|
|
76
|
+
export var OfferStageState;
|
|
77
|
+
(function (OfferStageState) {
|
|
78
|
+
OfferStageState["LOCKED"] = "locked";
|
|
79
|
+
OfferStageState["CURRENT"] = "current";
|
|
80
|
+
OfferStageState["COMPLETED"] = "completed";
|
|
81
|
+
})(OfferStageState || (OfferStageState = {}));
|
|
82
|
+
function parseNumeric(value) {
|
|
83
|
+
const parsed = typeof value === 'number' ? value : parseFloat(value);
|
|
84
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read one tier's `is_applied` as a tri-state.
|
|
88
|
+
*
|
|
89
|
+
* `true`/`false` booleans are what `CartOffersTransformer` casts and emits. The numeric
|
|
90
|
+
* and string spellings are accepted too because the `s-source: mobile` pipeline already
|
|
91
|
+
* stringifies numeric fields in this very payload (`end_value`), and reading a `0` there
|
|
92
|
+
* as "absent" would silently restore the bug this exists to fix.
|
|
93
|
+
*
|
|
94
|
+
* EVERYTHING ELSE — key missing, `undefined`, `null`, `''`, an object — is ABSENT, not
|
|
95
|
+
* `false`: a value we cannot interpret is not the engine saying no, and must degrade to
|
|
96
|
+
* the arithmetic fallback rather than lock a tier the shopper has genuinely earned.
|
|
97
|
+
*/
|
|
98
|
+
export function readAppliedFlag(value) {
|
|
99
|
+
if (typeof value === 'boolean')
|
|
100
|
+
return value;
|
|
101
|
+
if (value === 1 || value === 0)
|
|
102
|
+
return value === 1;
|
|
103
|
+
if (value === 'true' || value === '1')
|
|
104
|
+
return true;
|
|
105
|
+
if (value === 'false' || value === '0')
|
|
106
|
+
return false;
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Coerce a cart VALUE. Falling back to 0 is correct here: an unreadable
|
|
111
|
+
* `current_value`/`start_value` means "the shopper has made no progress".
|
|
112
|
+
*/
|
|
113
|
+
function toFiniteNumber(value) {
|
|
114
|
+
return parseNumeric(value) ?? 0;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Whether a THRESHOLD is usable.
|
|
118
|
+
*
|
|
119
|
+
* Thresholds get the opposite policy to values: an unparseable threshold is DROPPED,
|
|
120
|
+
* never coerced to 0, because a 0 threshold is satisfied by an empty cart and would
|
|
121
|
+
* render as an already-COMPLETED stage — reintroducing exactly the synthetic-zeroth-
|
|
122
|
+
* checkpoint bug this component was rewritten to remove.
|
|
123
|
+
*
|
|
124
|
+
* Callers that keep their own parallel array of stage objects MUST filter it with this
|
|
125
|
+
* predicate so their index alignment with `progress.thresholds` survives the drop.
|
|
126
|
+
*/
|
|
127
|
+
export function isUsableThreshold(value) {
|
|
128
|
+
return parseNumeric(value) !== null;
|
|
129
|
+
}
|
|
130
|
+
function clamp(value, min, max) {
|
|
131
|
+
return Math.max(min, Math.min(value, max));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Fill of one segment as a 0..100 percentage.
|
|
135
|
+
* A zero-width (or inverted) segment — which duplicate thresholds produce — has
|
|
136
|
+
* no gradient to show, so it is all-or-nothing rather than a division by zero.
|
|
137
|
+
*/
|
|
138
|
+
function segmentFill(value, from, to) {
|
|
139
|
+
if (to <= from) {
|
|
140
|
+
return value >= to ? 100 : 0;
|
|
141
|
+
}
|
|
142
|
+
return clamp(((value - from) / (to - from)) * 100, 0, 100);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Stable ascending sort of arbitrary items by a numeric threshold accessor.
|
|
146
|
+
*
|
|
147
|
+
* Ties are broken by `getIdentity` when supplied and readable on BOTH sides — the tier
|
|
148
|
+
* `id` (0 = base tier, 1..N = configured options in the order the engine walks them), so
|
|
149
|
+
* two tiers sharing a `min_spend` order by what they ARE rather than by where they
|
|
150
|
+
* happened to land in an unsorted payload. Without it (or when either id is unreadable)
|
|
151
|
+
* ties keep their original relative order, which is the previous behaviour exactly.
|
|
152
|
+
*/
|
|
153
|
+
export function sortByThreshold(items, getThreshold, getIdentity) {
|
|
154
|
+
return (items ?? [])
|
|
155
|
+
.map((item, index) => ({
|
|
156
|
+
item,
|
|
157
|
+
index,
|
|
158
|
+
threshold: toFiniteNumber(getThreshold(item)),
|
|
159
|
+
identity: getIdentity ? parseNumeric(getIdentity(item)) : null,
|
|
160
|
+
}))
|
|
161
|
+
.sort((a, b) => {
|
|
162
|
+
if (a.threshold !== b.threshold)
|
|
163
|
+
return a.threshold - b.threshold;
|
|
164
|
+
if (a.identity !== null && b.identity !== null && a.identity !== b.identity) {
|
|
165
|
+
return a.identity - b.identity;
|
|
166
|
+
}
|
|
167
|
+
return a.index - b.index;
|
|
168
|
+
})
|
|
169
|
+
.map(({ item }) => item);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Derive the full stage picture from the server's `current_value`.
|
|
173
|
+
*
|
|
174
|
+
* @param currentValue the server-computed cart value — never recomputed client-side.
|
|
175
|
+
* @param rawThresholds each stage's `min_spend`, in any order.
|
|
176
|
+
* @param rawStartValue the baseline the first segment fills from (`details.start_value`).
|
|
177
|
+
* @param rawApplied each stage's `is_applied`, POSITIONALLY ALIGNED WITH `rawThresholds`
|
|
178
|
+
* before any sorting or dropping happens here. Omit it and every tier reads as
|
|
179
|
+
* absent, i.e. the threshold behaviour, unchanged.
|
|
180
|
+
* @param rawTierValues each stage's OWN `current_value`, aligned the same way. A tier
|
|
181
|
+
* without a readable one is measured by `currentValue`, so omitting it — an older
|
|
182
|
+
* backend — changes nothing. See `OfferStageProgress.values`.
|
|
183
|
+
*/
|
|
184
|
+
export function computeOfferStageProgress(currentValue, rawThresholds, rawStartValue = 0, rawApplied = [], rawTierValues = []) {
|
|
185
|
+
// Unparseable thresholds are dropped, not zeroed — see `isUsableThreshold`.
|
|
186
|
+
//
|
|
187
|
+
// The flag travels WITH its threshold through both the drop and the sort. Sorting two
|
|
188
|
+
// parallel arrays separately is how an `is_applied` ends up on the wrong tier, and a
|
|
189
|
+
// misattributed verdict is worse than no verdict at all. The tier's own value rides
|
|
190
|
+
// along for the same reason.
|
|
191
|
+
const entries = (rawThresholds ?? [])
|
|
192
|
+
.map((threshold, index) => ({
|
|
193
|
+
index,
|
|
194
|
+
threshold: parseNumeric(threshold),
|
|
195
|
+
applied: readAppliedFlag(rawApplied?.[index]),
|
|
196
|
+
tierValue: parseNumeric(rawTierValues?.[index]),
|
|
197
|
+
}))
|
|
198
|
+
.filter((entry) => entry.threshold !== null)
|
|
199
|
+
.sort((a, b) => a.threshold - b.threshold || a.index - b.index);
|
|
200
|
+
const thresholds = entries.map(entry => entry.threshold);
|
|
201
|
+
const applied = entries.map(entry => entry.applied);
|
|
202
|
+
const hasEngineVerdict = applied.some(flag => flag !== undefined);
|
|
203
|
+
const startValue = toFiniteNumber(rawStartValue);
|
|
204
|
+
if (!thresholds.length) {
|
|
205
|
+
return {
|
|
206
|
+
thresholds,
|
|
207
|
+
met: [],
|
|
208
|
+
applied,
|
|
209
|
+
appliedIndex: NO_STAGE_REACHED,
|
|
210
|
+
hasEngineVerdict,
|
|
211
|
+
metCount: 0,
|
|
212
|
+
nextUnmetIndex: NO_STAGE_REACHED,
|
|
213
|
+
allMet: false,
|
|
214
|
+
lastReachedIndex: NO_STAGE_REACHED,
|
|
215
|
+
currentIndex: NO_STAGE_REACHED,
|
|
216
|
+
isComplete: false,
|
|
217
|
+
value: Math.max(0, toFiniteNumber(currentValue)),
|
|
218
|
+
values: [],
|
|
219
|
+
startValue,
|
|
220
|
+
fills: [],
|
|
221
|
+
overallPercentage: 0,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const finalThreshold = thresholds[thresholds.length - 1];
|
|
225
|
+
// THE CLAMP: a stale or overshooting `current_value` must never produce a
|
|
226
|
+
// >100% bar or an out-of-range stage index.
|
|
227
|
+
const value = clamp(toFiniteNumber(currentValue), 0, Math.max(0, finalThreshold));
|
|
228
|
+
// Each tier against ITS OWN progress where the backend sent one — see `values`. A tier
|
|
229
|
+
// value that is missing or unreadable is not "no progress": it is "not reported", and
|
|
230
|
+
// falls back to the offer-level value rather than to 0.
|
|
231
|
+
const values = entries.map(({ tierValue }) => tierValue === null ? value : clamp(tierValue, 0, Math.max(0, finalThreshold)));
|
|
232
|
+
let lastReachedIndex = NO_STAGE_REACHED;
|
|
233
|
+
for (let index = 0; index < thresholds.length; index++) {
|
|
234
|
+
if (values[index] >= thresholds[index]) {
|
|
235
|
+
lastReachedIndex = index;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const isComplete = lastReachedIndex === thresholds.length - 1;
|
|
239
|
+
const currentIndex = isComplete ? NO_STAGE_REACHED : lastReachedIndex + 1;
|
|
240
|
+
const fills = thresholds.map((threshold, index) => segmentFill(values[index], index === 0 ? startValue : thresholds[index - 1], threshold));
|
|
241
|
+
const overallPercentage = isComplete
|
|
242
|
+
? 100
|
|
243
|
+
: clamp(((lastReachedIndex + 1 + (fills[currentIndex] ?? 0) / 100) / thresholds.length) * 100, 0, 100);
|
|
244
|
+
// ---------------------------------------------------------------------------------
|
|
245
|
+
// THE DECISION — three cases, in this order.
|
|
246
|
+
//
|
|
247
|
+
// (1) THE ENGINE GRANTED A TIER. `appliedIndex` is the HIGHEST tier flagged `true`, and
|
|
248
|
+
// every tier up to and including it is met. A cart offer pays out ONCE, at the best
|
|
249
|
+
// qualifying tier, so the earlier tiers are `false` because they were SUPERSEDED —
|
|
250
|
+
// the shopper is past them. Closing the prefix is what stops a lock rendering below
|
|
251
|
+
// a ✓ (the beta screenshot: `false / false / true` at `current_value` 510).
|
|
252
|
+
//
|
|
253
|
+
// HIGHEST, not first, and read AFTER the ascending sort: the payload arrives
|
|
254
|
+
// unsorted, so "the last tier the engine kept" is only "the furthest tier reached"
|
|
255
|
+
// once the rows are in threshold order. A NON-CONTIGUOUS set — `[true, false, true]`
|
|
256
|
+
// — should be impossible for a single-winner engine, but if one ever arrives the
|
|
257
|
+
// highest wins and the whole prefix reads met. That is deliberate: the alternative
|
|
258
|
+
// is the un-steppable row this fix exists to remove, and a superset of ✓ merely
|
|
259
|
+
// over-credits a tier the shopper has demonstrably paid past.
|
|
260
|
+
//
|
|
261
|
+
// (2) THE ENGINE SPOKE AND GRANTED NOTHING. Everything is locked, whatever the
|
|
262
|
+
// thresholds say. This is the OTHER beta cart — `current_value` 510, zero discount,
|
|
263
|
+
// every tier `false` — and it is the case `is_applied` was introduced for. The
|
|
264
|
+
// arithmetic MUST NOT be consulted here or the bug comes straight back.
|
|
265
|
+
//
|
|
266
|
+
// (3) THIS BACKEND REPORTS NOTHING (`hasEngineVerdict` false — no tier carried a
|
|
267
|
+
// readable flag). Only then does the cumulative threshold comparison run, exactly
|
|
268
|
+
// as before `is_applied` shipped. Twilight and the Store package deploy
|
|
269
|
+
// independently, so this path is live, not theoretical.
|
|
270
|
+
//
|
|
271
|
+
// Cases (1) and (2) are split on the WINNER, not on each tier's own flag, so `??` vs
|
|
272
|
+
// `||` no longer decides a single row on its own. The tri-state still decides which
|
|
273
|
+
// CASE we are in: an unreadable flag is absent (case 3), an explicit `false` is an
|
|
274
|
+
// answer that keeps us in cases 1-2. A mixed payload — some tiers flagged, some not,
|
|
275
|
+
// which no current backend emits — is treated as having spoken: only a `true` can name
|
|
276
|
+
// the winner, so an unflagged tier can never be one.
|
|
277
|
+
// ---------------------------------------------------------------------------------
|
|
278
|
+
let appliedIndex = NO_STAGE_REACHED;
|
|
279
|
+
for (let index = 0; index < applied.length; index++) {
|
|
280
|
+
if (applied[index] === true) {
|
|
281
|
+
appliedIndex = index;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Case (3) closes the prefix at `lastReachedIndex` rather than comparing tier by tier.
|
|
285
|
+
// With one value for every tier the two are the same statement; with per-tier values
|
|
286
|
+
// it is what keeps `met` a prefix, on the same single-winner reasoning as case (1).
|
|
287
|
+
const met = hasEngineVerdict
|
|
288
|
+
? thresholds.map((_, index) => index <= appliedIndex)
|
|
289
|
+
: thresholds.map((_, index) => index <= lastReachedIndex);
|
|
290
|
+
const metCount = met.filter(Boolean).length;
|
|
291
|
+
const nextUnmetIndex = met.findIndex(isMet => !isMet); // -1 === NO_STAGE_REACHED
|
|
292
|
+
const allMet = nextUnmetIndex === NO_STAGE_REACHED;
|
|
293
|
+
return {
|
|
294
|
+
thresholds,
|
|
295
|
+
met,
|
|
296
|
+
applied,
|
|
297
|
+
appliedIndex,
|
|
298
|
+
hasEngineVerdict,
|
|
299
|
+
metCount,
|
|
300
|
+
nextUnmetIndex,
|
|
301
|
+
allMet,
|
|
302
|
+
lastReachedIndex,
|
|
303
|
+
currentIndex,
|
|
304
|
+
isComplete,
|
|
305
|
+
value,
|
|
306
|
+
values,
|
|
307
|
+
startValue,
|
|
308
|
+
fills,
|
|
309
|
+
overallPercentage,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Locked / Current / Completed for one 0-based stage index — from `met`, so a tier the
|
|
314
|
+
* engine withheld outright reads LOCKED even with its threshold met, and every tier the
|
|
315
|
+
* shopper has passed on the way to a granted one reads COMPLETED.
|
|
316
|
+
*
|
|
317
|
+
* - Completed: `met[index]` — index at or below the winner.
|
|
318
|
+
* - Current: exactly `nextUnmetIndex`, the first tier ABOVE the winner.
|
|
319
|
+
* - Locked: every other unmet stage, and any out-of-range index.
|
|
320
|
+
*/
|
|
321
|
+
export function getStageState(index, progress) {
|
|
322
|
+
if (index >= 0 && progress.met[index]) {
|
|
323
|
+
return OfferStageState.COMPLETED;
|
|
324
|
+
}
|
|
325
|
+
if (index === progress.nextUnmetIndex && index !== NO_STAGE_REACHED) {
|
|
326
|
+
return OfferStageState.CURRENT;
|
|
327
|
+
}
|
|
328
|
+
return OfferStageState.LOCKED;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* How the offer MEASURES its thresholds, which is also what its remainder rounds to.
|
|
332
|
+
*
|
|
333
|
+
* Mirrors `OfferType` in `salla-conditional-offer/interfaces.ts` without importing it —
|
|
334
|
+
* this module stays free of the component's payload types (see the header) — so a caller
|
|
335
|
+
* maps `details.based_on` onto it at the call site.
|
|
336
|
+
*/
|
|
337
|
+
export var OfferStageMeasure;
|
|
338
|
+
(function (OfferStageMeasure) {
|
|
339
|
+
/** `order_amount` — a money value, rounded to the store's currency precision. */
|
|
340
|
+
OfferStageMeasure["CURRENCY"] = "currency";
|
|
341
|
+
/** `products_count` — whole products, so the remainder is a whole number. */
|
|
342
|
+
OfferStageMeasure["COUNT"] = "count";
|
|
343
|
+
})(OfferStageMeasure || (OfferStageMeasure = {}));
|
|
344
|
+
/**
|
|
345
|
+
* The precision to fall back to when the store publishes none.
|
|
346
|
+
*
|
|
347
|
+
* 2 is the platform default (SAR/USD/AED); a store on a 3-decimal currency (KWD, BHD)
|
|
348
|
+
* that reports its precision gets 3, and only a store that reports NOTHING readable
|
|
349
|
+
* lands here.
|
|
350
|
+
*/
|
|
351
|
+
export const DEFAULT_CURRENCY_DECIMALS = 2;
|
|
352
|
+
/**
|
|
353
|
+
* Hard ceiling on the precision a store can ask for.
|
|
354
|
+
*
|
|
355
|
+
* The value comes off `salla.config`, i.e. off the wire, and `Math.pow(10, n)` with a
|
|
356
|
+
* large `n` reintroduces the very float noise this rounding exists to remove (and with
|
|
357
|
+
* an absurd `n`, an `Infinity` factor and a `NaN` remainder). 4 matches the cap
|
|
358
|
+
* `salla-cart-summary-card.roundToCurrency()` already applies to the same problem.
|
|
359
|
+
*/
|
|
360
|
+
const MAX_CURRENCY_DECIMALS = 4;
|
|
361
|
+
/**
|
|
362
|
+
* Read one candidate precision, or `null` when it is not a usable one.
|
|
363
|
+
*
|
|
364
|
+
* Strict on purpose. `salla.config.get()` returns THE KEY ITSELF for a key the store did
|
|
365
|
+
* not publish (`config.ts:50`), so an unpublished `store.currency.decimals` arrives as
|
|
366
|
+
* the string `"store.currency.decimals"` — truthy, non-null, and fatal to a `?? 2`
|
|
367
|
+
* fallback, which is why this parses and range-checks rather than nullish-coalescing.
|
|
368
|
+
*/
|
|
369
|
+
function readDecimals(raw) {
|
|
370
|
+
const parsed = typeof raw === 'number' ? raw : parseFloat(raw);
|
|
371
|
+
if (!Number.isFinite(parsed))
|
|
372
|
+
return null;
|
|
373
|
+
const decimals = Math.trunc(parsed);
|
|
374
|
+
return decimals >= 0 && decimals <= MAX_CURRENCY_DECIMALS ? decimals : null;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* The store's money precision — THE ONE IMPURITY IN THIS MODULE (see the header note).
|
|
378
|
+
*
|
|
379
|
+
* Asked in the order the platform answers it:
|
|
380
|
+
* 1. `salla.config.currency()` — the ACTIVE currency's own precision, which is the only
|
|
381
|
+
* source that can differ per currency (a store displaying KWD wants 3 while its SAR
|
|
382
|
+
* prices want 2).
|
|
383
|
+
* 2. `salla.config.get('store.currency.decimals')` — the store-level key
|
|
384
|
+
* `salla-bought-together.tsx:142` already rounds its basket total by.
|
|
385
|
+
* 3. `DEFAULT_CURRENCY_DECIMALS`.
|
|
386
|
+
*
|
|
387
|
+
* Every step is guarded: this runs inside `render()`, where an SDK that is not on the
|
|
388
|
+
* page yet (or a `config` that throws) must degrade to the default, never take the banner
|
|
389
|
+
* down.
|
|
390
|
+
*/
|
|
391
|
+
export function getCurrencyDecimals() {
|
|
392
|
+
try {
|
|
393
|
+
const sdk = typeof salla !== 'undefined' ? salla : undefined;
|
|
394
|
+
const fromCurrency = readDecimals(sdk?.config?.currency?.()?.decimals);
|
|
395
|
+
if (fromCurrency !== null)
|
|
396
|
+
return fromCurrency;
|
|
397
|
+
const fromConfig = readDecimals(sdk?.config?.get?.('store.currency.decimals'));
|
|
398
|
+
if (fromConfig !== null)
|
|
399
|
+
return fromConfig;
|
|
400
|
+
}
|
|
401
|
+
catch (_) {
|
|
402
|
+
// No SDK, or a config that threw — the default is the answer.
|
|
403
|
+
}
|
|
404
|
+
return DEFAULT_CURRENCY_DECIMALS;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Float noise allowed when rounding a remainder UP, in units of the display precision.
|
|
408
|
+
* `300 - 289.6` is `10.399999999999977`, which must still read as exactly 10.40 rather
|
|
409
|
+
* than tip over to 10.41.
|
|
410
|
+
*/
|
|
411
|
+
const CEIL_TOLERANCE = 1e-6;
|
|
412
|
+
/**
|
|
413
|
+
* How much more of the cart value is needed to reach the given stage, rounded UP at the
|
|
414
|
+
* display precision. Never negative.
|
|
415
|
+
*
|
|
416
|
+
* `threshold - value` is raw IEEE-754 subtraction over two server-sent floats, so
|
|
417
|
+
* `300 - 289.599993` is `10.400007000000012`. Nothing downstream cleans that up:
|
|
418
|
+
* `salla.money()` only swaps digits and appends a symbol (`base/src/helpers/numbers.ts:74-84`).
|
|
419
|
+
* Rounding here, at the source, covers every consumer, including the `products_count`
|
|
420
|
+
* path that never reaches `salla.money`.
|
|
421
|
+
*
|
|
422
|
+
* Rounding goes UP, never to the nearest unit: the displayed amount must be enough to
|
|
423
|
+
* unlock the tier. Rounding 10.400007 to 10.40 asks for an amount that leaves the cart
|
|
424
|
+
* at 299.999993, still below the threshold. Float noise is guarded (see `CEIL_TOLERANCE`)
|
|
425
|
+
* so an exact 10.40 stays 10.40.
|
|
426
|
+
*
|
|
427
|
+
* A count remainder is a WHOLE number, because `salla.lang.choice('blocks.header.products_count', 10.4)`
|
|
428
|
+
* matches no plural interval and a shopper cannot add 0.4 of a product.
|
|
429
|
+
*
|
|
430
|
+
* Only the derived remainder is rounded. `progress.values` and `progress.thresholds` are
|
|
431
|
+
* server facts and stay as sent: rounding them would move the unlocked/locked boundary,
|
|
432
|
+
* which is the engine's call.
|
|
433
|
+
*
|
|
434
|
+
* A positive remainder never becomes 0: `getGuidanceMessage` drops its line on
|
|
435
|
+
* `remaining <= 0`, so a genuinely unmet tier must still ask for at least one unit of the
|
|
436
|
+
* precision (0.01 for a 2-decimal currency, 1 for a count). Exact zero, a met tier,
|
|
437
|
+
* still returns 0.
|
|
438
|
+
*/
|
|
439
|
+
export function getRemainingToStage(index, progress, measure = OfferStageMeasure.CURRENCY) {
|
|
440
|
+
const threshold = progress.thresholds[index];
|
|
441
|
+
if (threshold === undefined) {
|
|
442
|
+
return 0;
|
|
443
|
+
}
|
|
444
|
+
// The distance to a tier is measured in THAT tier's own progress — see `values`.
|
|
445
|
+
const raw = Math.max(0, threshold - (progress.values?.[index] ?? progress.value));
|
|
446
|
+
if (raw === 0)
|
|
447
|
+
return 0;
|
|
448
|
+
const decimals = measure === OfferStageMeasure.COUNT ? 0 : getCurrencyDecimals();
|
|
449
|
+
const factor = Math.pow(10, decimals);
|
|
450
|
+
const rounded = Math.ceil(raw * factor - CEIL_TOLERANCE) / factor;
|
|
451
|
+
// Still owed something, but only float noise above zero: ask for one unit.
|
|
452
|
+
return rounded > 0 ? rounded : 1 / factor;
|
|
453
|
+
}
|