@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
|
@@ -1,20 +1,666 @@
|
|
|
1
1
|
import { type Offer } from "./interfaces";
|
|
2
|
-
import type
|
|
2
|
+
import { type CartFreeGiftItem } from "../../shared/cart-free-gifts";
|
|
3
3
|
export declare class SallaConditionalOffer {
|
|
4
4
|
offer: Offer;
|
|
5
|
-
products: Record<string, Pick<Product, 'image' | 'url' | 'name' | 'price'>>;
|
|
6
5
|
isLoading: boolean;
|
|
7
6
|
canRender: boolean;
|
|
7
|
+
/** Free-gift cart LINES belonging to this offer — the "ادارة العرض" card. */
|
|
8
|
+
gifts: CartFreeGiftItem[];
|
|
9
|
+
/**
|
|
10
|
+
* How many LINES the cart holds — `null` while that is genuinely UNKNOWN.
|
|
11
|
+
*
|
|
12
|
+
* TRI-STATE on purpose, and the whole of the empty-cart fix rests on it. `0` renders
|
|
13
|
+
* NOTHING; `null` renders normally. Collapsing the two would hide the banner on every
|
|
14
|
+
* populated cart for as long as the first `details()` request is in flight, which is
|
|
15
|
+
* the flicker this must not trade one bug for.
|
|
16
|
+
*/
|
|
17
|
+
cartItemCount: number | null;
|
|
18
|
+
/** The tooltip is open while hovered OR while pinned by a click (the mobile path). */
|
|
19
|
+
isTooltipHovered: boolean;
|
|
20
|
+
isTooltipPinned: boolean;
|
|
21
|
+
/** "ادارة العرض" — open by default, the gifts card starts expanded. */
|
|
22
|
+
isGiftsOpen: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The ⓘ TRIGGER's own wrapper — the popover's positioning context, and the element
|
|
25
|
+
* "inside vs outside" is measured against.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately NOT the heading. The heading spans the offer TITLE as well, so
|
|
28
|
+
* anchoring there put the popover's edge at the start of the text and left the caret
|
|
29
|
+
* pointing at a word instead of at the icon that opened it.
|
|
30
|
+
*/
|
|
31
|
+
private infoAnchorEl?;
|
|
32
|
+
private infoTriggerEl?;
|
|
33
|
+
/** The popover itself, measured (never re-rendered) by `positionTooltip`. */
|
|
34
|
+
private tooltipEl?;
|
|
35
|
+
/**
|
|
36
|
+
* What makes this instance's `id`s its OWN. The banner can legitimately mount more than
|
|
37
|
+
* once on a page, and with fixed ids the second instance's `aria-describedby` /
|
|
38
|
+
* `aria-controls` resolve to the FIRST one's popover and gifts card.
|
|
39
|
+
*/
|
|
40
|
+
private instanceId;
|
|
41
|
+
/**
|
|
42
|
+
* Monotonic generation counter guarding `refresh()`. Two cart mutations in quick succession issue
|
|
43
|
+
* two `offers()` requests, and nothing guarantees they SETTLE in the order they were
|
|
44
|
+
* sent — without this, a slow first response can land after a fast second one and
|
|
45
|
+
* render a `current_value` that is already stale.
|
|
46
|
+
*/
|
|
47
|
+
private refreshGeneration;
|
|
48
|
+
/** The same guard for the independent gift-lines lookup. */
|
|
49
|
+
private giftsGeneration;
|
|
50
|
+
/**
|
|
51
|
+
* The open coalescing window: the timer that will run the ONE refresh pass a burst of
|
|
52
|
+
* `cart::updated` events is entitled to. `null` whenever no burst is in flight.
|
|
53
|
+
*
|
|
54
|
+
* The generation guards above are untouched by this and still do their own job — the
|
|
55
|
+
* window collapses events that have not been ASKED about yet, they discard answers to
|
|
56
|
+
* questions that have since been superseded, and a burst that straddles the window
|
|
57
|
+
* (an event arriving while a pass is already in flight) needs both.
|
|
58
|
+
*/
|
|
59
|
+
private cartRefreshTimer;
|
|
60
|
+
/**
|
|
61
|
+
* "A burst has been seen and its pass has not run yet."
|
|
62
|
+
*
|
|
63
|
+
* Survives a disconnect on purpose, exactly as `skeletonDeadline` does: a theme that
|
|
64
|
+
* MOVES the host in the DOM mid-burst (disconnect + reconnect, same instance) would
|
|
65
|
+
* otherwise drop the trailing pass and rest on pre-burst data forever, since nothing
|
|
66
|
+
* re-runs `refresh()` but another `cart::updated`. `connectedCallback` re-arms it.
|
|
67
|
+
*/
|
|
68
|
+
private cartRefreshPending;
|
|
69
|
+
/**
|
|
70
|
+
* Set once `load()` has asked for `cart::updated`, and never cleared: it is what lets
|
|
71
|
+
* `connectedCallback` tell a RECONNECT (put the listener back) from the first connect,
|
|
72
|
+
* which runs before `componentWillLoad` and must leave the subscription to it.
|
|
73
|
+
*/
|
|
74
|
+
private wantsCartUpdates;
|
|
75
|
+
/** Whether `cartUpdatedHandler` is on `cart::updated` RIGHT NOW — the double-registration guard. */
|
|
76
|
+
private isListeningToCartUpdates;
|
|
77
|
+
/** The armed cart-lookup retry; `null` whenever none is. See `CART_LOOKUP_RETRY_DELAYS_MS`. */
|
|
78
|
+
private cartLookupRetryTimer;
|
|
79
|
+
/** How many of those retries this episode has SPENT — the bound. */
|
|
80
|
+
private cartLookupRetries;
|
|
81
|
+
/** "A retry is owed" — survives a disconnect for the same reason `cartRefreshPending` does. */
|
|
82
|
+
private cartLookupRetryPending;
|
|
83
|
+
/**
|
|
84
|
+
* Set by the ONE branch of `refresh()` that returns without asking for the offer: the
|
|
85
|
+
* cart was reported empty.
|
|
86
|
+
*
|
|
87
|
+
* That branch is right whenever the report is, and unrecoverable whenever it is not —
|
|
88
|
+
* nothing re-runs `refresh()` except `cart::updated`, which a shopper who never
|
|
89
|
+
* touches the cart never emits. So the branch now leaves this behind, and the cart
|
|
90
|
+
* lookup it kicks off clears it by re-running `refresh()` if the count comes back
|
|
91
|
+
* `>= 1`. See both sites; the latch is what keeps that to a recovery rather than a
|
|
92
|
+
* second offer request on every load.
|
|
93
|
+
*/
|
|
94
|
+
private deferredByEmptyCart;
|
|
95
|
+
/** Set by `disconnectedCallback`, so a late response cannot set state on a dead host. */
|
|
96
|
+
private isDisconnected;
|
|
97
|
+
/** Backstop timer that guarantees the skeleton is never a resting state. */
|
|
98
|
+
private skeletonWatchdog;
|
|
99
|
+
/**
|
|
100
|
+
* WHEN the current skeleton must be gone by — the authority, with `skeletonWatchdog`
|
|
101
|
+
* demoted to a mere handle.
|
|
102
|
+
*
|
|
103
|
+
* A timer handle answers "is a timer pending", which is not the question. The question
|
|
104
|
+
* is "is this skeleton past its deadline", and the two come apart precisely when it
|
|
105
|
+
* matters: a handle that was cleared, consumed, or never armed reads exactly like one
|
|
106
|
+
* that is still counting down. Keeping the deadline as a timestamp means the watchdog
|
|
107
|
+
* can always be REBUILT from state — which is what `ensureSkeletonIsWatched` does, on
|
|
108
|
+
* every render, so a skeleton on screen can never be one nothing is watching.
|
|
109
|
+
*
|
|
110
|
+
* `0` means "not loading"; anything else is an absolute `Date.now()` deadline.
|
|
111
|
+
*/
|
|
112
|
+
private skeletonDeadline;
|
|
113
|
+
/** The host element — the thing that gets MOVED; see `relocateIntoItemsColumn`. */
|
|
114
|
+
hostEl: HTMLElement;
|
|
115
|
+
/** Latched once the host sits in the items column, so it can never be moved twice. */
|
|
116
|
+
private hasRelocated;
|
|
117
|
+
/** `componentDidRender` fires BEFORE the first `componentDidLoad`; this orders them. */
|
|
118
|
+
private hasLoaded;
|
|
119
|
+
/** Observer waiting for a column that the parser has not emitted yet. */
|
|
120
|
+
private relocationObserver;
|
|
121
|
+
/** Deadline that bounds that wait. */
|
|
122
|
+
private relocationWatchTimer;
|
|
123
|
+
/** Set when the wait expired: stop re-arming, keep the cheap per-render retry. */
|
|
124
|
+
private relocationWatchExpired;
|
|
125
|
+
/** "No column found" is said ONCE per instance, not on every render. */
|
|
126
|
+
private relocationLogged;
|
|
127
|
+
/**
|
|
128
|
+
* SYNCHRONOUS on purpose — an `async` version of this hook is what made the whole
|
|
129
|
+
* component invisible.
|
|
130
|
+
*
|
|
131
|
+
* Stencil awaits whatever `componentWillLoad` returns BEFORE it calls `render()` for
|
|
132
|
+
* the first time (`dispatchHooks` → `enqueue(maybePromise, () => updateComponent(...))`).
|
|
133
|
+
* An `async componentWillLoad` chaining round trips inside that promise leaves the host
|
|
134
|
+
* element literally empty for the whole chain, and FOREVER if the last of them never
|
|
135
|
+
* settles.
|
|
136
|
+
*
|
|
137
|
+
* Same posture as `salla-cart-summary-card` ("Sync: returning a promise would make
|
|
138
|
+
* Stencil defer first paint"): paint now, fill in as the data lands.
|
|
139
|
+
*/
|
|
8
140
|
componentWillLoad(): void;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Logging that CANNOT throw.
|
|
143
|
+
*
|
|
144
|
+
* `salla.logger.warn` is not safe to call during init: `base/src/logger.ts` opens every
|
|
145
|
+
* call with `if (!salla.config.isDebug()) return;`, so it raises a TypeError whenever
|
|
146
|
+
* `salla.config` has not been installed yet — precisely the window `componentWillLoad`
|
|
147
|
+
* runs in. Every recovery path in this component used to log BEFORE it recovered, so a
|
|
148
|
+
* single throw in here silently skipped the recovery it was announcing.
|
|
149
|
+
*/
|
|
150
|
+
private safeLog;
|
|
151
|
+
/**
|
|
152
|
+
* Cleared on RECONNECT, not just set on disconnect. `componentWillLoad` runs once per
|
|
153
|
+
* instance, so an element that a theme moves in the DOM (disconnect + reconnect keeps
|
|
154
|
+
* the same instance) would otherwise stay permanently latched as "dead" and stop
|
|
155
|
+
* updating on every later `cart::updated`.
|
|
156
|
+
*/
|
|
157
|
+
connectedCallback(): void;
|
|
158
|
+
disconnectedCallback(): void;
|
|
159
|
+
componentDidLoad(): void;
|
|
160
|
+
/**
|
|
161
|
+
* Whether the host currently puts anything on the page.
|
|
162
|
+
*
|
|
163
|
+
* Gates the relocation: a host that renders nothing contributes no layout and must not
|
|
164
|
+
* be moved around the document either. It becomes true on the render that first paints
|
|
165
|
+
* real content, and `componentDidRender` runs inside the same frame as that render —
|
|
166
|
+
* before the browser paints it — so the move still lands ahead of the first visible
|
|
167
|
+
* pixel and there is no jump.
|
|
168
|
+
*/
|
|
169
|
+
private isShowingSomething;
|
|
170
|
+
/**
|
|
171
|
+
* Every later render re-attempts the move. Free once it has happened (one boolean
|
|
172
|
+
* read), and it is what covers the ordinary late-arrival case without any timer: the
|
|
173
|
+
* skeleton paints while the row is still being parsed, and the render that replaces it
|
|
174
|
+
* with the offer runs against a complete DOM.
|
|
175
|
+
*/
|
|
176
|
+
componentDidRender(): void;
|
|
177
|
+
/**
|
|
178
|
+
* Move the host into the cart-items column.
|
|
179
|
+
*
|
|
180
|
+
* The theme mounts this component ABOVE the two-column row (`cart.twig`), so it spans
|
|
181
|
+
* the items column AND the order summary; the design puts it above the ITEMS only,
|
|
182
|
+
* with the summary starting level with it. Theme changes are out of scope, so the
|
|
183
|
+
* component places itself.
|
|
184
|
+
*
|
|
185
|
+
* MOVING rather than re-sizing is deliberate. A moved host inherits whatever width the
|
|
186
|
+
* column has — at every breakpoint, in RTL and LTR, and on a resize — and on mobile,
|
|
187
|
+
* where the column is the only column, the move is a no-op visually, which is exactly
|
|
188
|
+
* the mobile layout. A hard-coded `calc(100% - 384px)` is raed's number and would
|
|
189
|
+
* be wrong in any other theme.
|
|
190
|
+
*
|
|
191
|
+
* NEVER throws, and never leaves the component unrendered: a cart whose column cannot
|
|
192
|
+
* be identified keeps the full-width banner. That is a degraded LAYOUT, not a degraded
|
|
193
|
+
* component.
|
|
194
|
+
*/
|
|
195
|
+
private relocateIntoItemsColumn;
|
|
196
|
+
/**
|
|
197
|
+
* The cart-items column, found WITHOUT depending on any single theme's class names.
|
|
198
|
+
*
|
|
199
|
+
* From the host OUTWARDS: at each level look at the elements that FOLLOW it — the row
|
|
200
|
+
* is always a later sibling, never an ancestor — and inside each of them look for a
|
|
201
|
+
* cart ITEM row. The column is then that row's own child which contains the item, i.e.
|
|
202
|
+
* precisely the element whose width the banner should inherit.
|
|
203
|
+
*
|
|
204
|
+
* Scoping to following siblings is what stops an unrelated `form[id^="item-"]` (a
|
|
205
|
+
* mini-cart drawer, a footer widget) from being mistaken for the cart.
|
|
206
|
+
*/
|
|
207
|
+
private findItemsColumn;
|
|
208
|
+
private getFollowingSiblings;
|
|
209
|
+
/** The child of `row` that holds the cart items, if `row` is the two-column row. */
|
|
210
|
+
private findColumnWithin;
|
|
211
|
+
/**
|
|
212
|
+
* Walk up from `node` to the DIRECT CHILD of `ancestor` that contains it.
|
|
213
|
+
*
|
|
214
|
+
* Not "the parent of the item rows": a theme that nests them (`col > ul > li > form`)
|
|
215
|
+
* would hand back the `<ul>`, and a banner is not a list item. The row's own child is
|
|
216
|
+
* the column in every shape.
|
|
217
|
+
*/
|
|
218
|
+
private climbToChildOf;
|
|
219
|
+
private matches;
|
|
220
|
+
/**
|
|
221
|
+
* Wait for a column that does not exist YET, then stop waiting.
|
|
222
|
+
*
|
|
223
|
+
* Bounded on both sides: the observer is disconnected the moment the move succeeds, and
|
|
224
|
+
* a deadline retires it if it never does. Where there is no `MutationObserver` (older
|
|
225
|
+
* browsers, and the spec DOM) this degrades to the per-render retry in
|
|
226
|
+
* `componentDidRender`, which is the path that matters anyway.
|
|
227
|
+
*/
|
|
228
|
+
private watchForItemsColumn;
|
|
229
|
+
private stopWatchingForItemsColumn;
|
|
230
|
+
/** Said ONCE per instance: the full-width banner is a layout note, not a per-render alarm. */
|
|
231
|
+
private logNoItemsColumn;
|
|
232
|
+
/**
|
|
233
|
+
* Guarantee that a raised skeleton has a live deadline behind it — idempotent, cheap,
|
|
234
|
+
* and called from EVERY path that can leave one on screen (`startLoading`,
|
|
235
|
+
* `connectedCallback`, and `componentDidRender`).
|
|
236
|
+
*
|
|
237
|
+
* This is the structural answer to "a skeleton with nothing watching it". The old shape
|
|
238
|
+
* armed the timer only inside `startLoading()`, and only when the HANDLE happened to be
|
|
239
|
+
* null — so any path that raised `isLoading` without going through that guard, or that
|
|
240
|
+
* left a stale-looking handle behind, produced a shimmer that nothing would ever end.
|
|
241
|
+
* Re-checking on every render means the invariant is restored by the very act of
|
|
242
|
+
* painting the skeleton, whatever raised it.
|
|
243
|
+
*
|
|
244
|
+
* The deadline is set ONCE per loading episode and never pushed out, so a stream of
|
|
245
|
+
* `cart::updated` events cannot extend a skeleton indefinitely.
|
|
246
|
+
*/
|
|
247
|
+
private ensureSkeletonIsWatched;
|
|
248
|
+
private clearSkeletonWatchdog;
|
|
249
|
+
/**
|
|
250
|
+
* The ONLY way INTO the skeleton. Raising it and arming the watchdog that ends it are
|
|
251
|
+
* deliberately the SAME operation — keeping them apart is what let `refresh()` show a
|
|
252
|
+
* skeleton that nothing was watching. The existing deadline is kept rather than
|
|
253
|
+
* refreshed, so a stream of `cart::updated` events cannot push it out indefinitely.
|
|
254
|
+
*/
|
|
255
|
+
private startLoading;
|
|
256
|
+
/**
|
|
257
|
+
* The ONLY way out of the skeleton, so the watchdog can never outlive it. Every
|
|
258
|
+
* terminal state — loaded, no-eligible-offer, error — goes through here.
|
|
259
|
+
*/
|
|
260
|
+
private stopLoading;
|
|
261
|
+
/**
|
|
262
|
+
* The async half of the load, deliberately NOT awaited by `componentWillLoad` so it
|
|
263
|
+
* can never gate the first paint. Every state it sets is `@State`, so each step
|
|
264
|
+
* re-renders on its own.
|
|
265
|
+
*
|
|
266
|
+
* NOTHING here is allowed to disable the component permanently.
|
|
267
|
+
*/
|
|
268
|
+
private load;
|
|
269
|
+
/**
|
|
270
|
+
* `salla.onReady()`, bounded.
|
|
271
|
+
*
|
|
272
|
+
* The previous shape caught a REJECTED bootstrap but could not catch a never-settling
|
|
273
|
+
* one, even though its comment claimed to — and an unbounded await here is the single
|
|
274
|
+
* point at which the whole component can hang with nothing logged. Racing a timer
|
|
275
|
+
* turns "the SDK never signals ready" from fatal into "we ask a few seconds early",
|
|
276
|
+
* which `refresh()` already handles non-terminally and retries on `cart::updated`.
|
|
277
|
+
*
|
|
278
|
+
* Never rejects, and absorbs a synchronous throw from `onReady` itself.
|
|
279
|
+
*/
|
|
280
|
+
private waitForSdk;
|
|
281
|
+
/**
|
|
282
|
+
* The `cart::updated` payload is ignored for everything EXCEPT the item count — it may
|
|
283
|
+
* legitimately arrive with no `items` at all (`api.cart.latest` is not in
|
|
284
|
+
* `CartPresenter::$routesWithItems`). Refetch and take the server's `current_value`.
|
|
285
|
+
*
|
|
286
|
+
* `count` is the one field that is safe to read here, and reading it is what fixes the
|
|
287
|
+
* live removal path. `SallaCartEvent.updated()` dispatches `{ ...data.cart }`
|
|
288
|
+
* (twilight/src/event/cart.js:114) and writes `count` from that same object into
|
|
289
|
+
* `cart.summary` one line above it, so the field is first-class — unlike `items`, which
|
|
290
|
+
* genuinely is not. Taking it SYNCHRONOUSLY, before any request goes out, is the point:
|
|
291
|
+
* see `readCartCountFromEvent`.
|
|
292
|
+
*
|
|
293
|
+
* Two independent refetches fan out from here and are NOT chained: the offer payload
|
|
294
|
+
* (thresholds + reward labels) and the gift cart LINES. The gift lookup is shared
|
|
295
|
+
* process-wide (`shared/cart-free-gifts`), so the item-offer badges, the summary card
|
|
296
|
+
* and this banner still cost ONE `details()` request between them per mutation.
|
|
297
|
+
*
|
|
298
|
+
* Both of those refetches are COALESCED — see `CART_UPDATE_COALESCE_MS`. Only the
|
|
299
|
+
* count read below is per-event.
|
|
300
|
+
*
|
|
301
|
+
* ONE reference per instance rather than an inline closure, because `salla.event.off`
|
|
302
|
+
* can only remove the exact function `on` was given.
|
|
303
|
+
*/
|
|
304
|
+
private cartUpdatedHandler;
|
|
305
|
+
/**
|
|
306
|
+
* Put `cartUpdatedHandler` on `cart::updated` — at most ONCE, however many paths ask
|
|
307
|
+
* (`load()`, the SDK-ready retry, a reconnect).
|
|
308
|
+
*
|
|
309
|
+
* Never throws, and reports whether the listener is on: `salla.event.on` can throw
|
|
310
|
+
* this early (see `load()`), and the caller decides what a failure is worth.
|
|
311
|
+
*/
|
|
312
|
+
private subscribeToCartUpdates;
|
|
313
|
+
/** The other half of `subscribeToCartUpdates` — see `disconnectedCallback`. */
|
|
314
|
+
private unsubscribeFromCartUpdates;
|
|
315
|
+
/**
|
|
316
|
+
* The second attempt for a subscription that threw before the SDK was up.
|
|
317
|
+
*
|
|
318
|
+
* Hung off `salla.onReady()` ITSELF, not off `waitForSdk()`: that wait gives up after
|
|
319
|
+
* `ON_READY_TIMEOUT_MS` so that it cannot hold the first paint, and an SDK that comes
|
|
320
|
+
* up later than that is exactly the one this is for. Floating for the same reason —
|
|
321
|
+
* nothing in `load()` awaits it, so a bootstrap that never settles costs the listener
|
|
322
|
+
* and nothing else.
|
|
323
|
+
*/
|
|
324
|
+
private subscribeWhenSdkIsReady;
|
|
325
|
+
/**
|
|
326
|
+
* Open (or extend) the coalescing window, so a burst of `cart::updated` events buys ONE
|
|
327
|
+
* refresh pass instead of two server round trips each. See `CART_UPDATE_COALESCE_MS`.
|
|
328
|
+
*
|
|
329
|
+
* Restarting the timer on every event is what makes this trailing-edge: the pass runs
|
|
330
|
+
* `CART_UPDATE_COALESCE_MS` after the LAST event, never after the first, so the burst
|
|
331
|
+
* can never settle on a cart state the shopper has already moved past.
|
|
332
|
+
*/
|
|
333
|
+
private scheduleCartRefresh;
|
|
334
|
+
/**
|
|
335
|
+
* The one pass a burst is entitled to — byte for byte what the `cart::updated` handler
|
|
336
|
+
* used to do inline, minus the count read that now happens per event.
|
|
337
|
+
*
|
|
338
|
+
* The disconnect guard comes FIRST and leaves the flag ALONE, so a host that went away
|
|
339
|
+
* between the last event and the end of its window stays owed the pass rather than
|
|
340
|
+
* silently forfeiting it. Unreachable in practice — `disconnectedCallback` clears the
|
|
341
|
+
* timer — but the two must not disagree about who owes what.
|
|
342
|
+
*
|
|
343
|
+
* Past that, the flag is cleared BEFORE the work, not after: an event that arrives
|
|
344
|
+
* while this pass is in flight must be able to open a NEW window (and it does —
|
|
345
|
+
* `scheduleCartRefresh` sets the flag again), and clearing afterwards would swallow it.
|
|
346
|
+
*/
|
|
347
|
+
private runCartRefresh;
|
|
348
|
+
/**
|
|
349
|
+
* Take the cart's item count straight off the `cart::updated` payload.
|
|
350
|
+
*
|
|
351
|
+
* THIS IS THE FIX FOR THE LIVE REMOVAL PATH, and it exists because the request-based
|
|
352
|
+
* count cannot answer there. When the shopper deletes their last line the cart id goes
|
|
353
|
+
* away, and BOTH cart endpoints then reject rather than reporting an empty cart:
|
|
354
|
+
* `details()` resolves its id through `getCurrentCartId(false, …, allowCreate=false)`,
|
|
355
|
+
* which returns `null` for a guest with no stored cart id, and throws `CartNotCreated`
|
|
356
|
+
* (twilight/src/api/cart.js:192-199); `offers()` throws through the same door
|
|
357
|
+
* (cart.js:869-874). `refreshCartFreeGifts()` has meanwhile invalidated the shared
|
|
358
|
+
* cache, so the count resolves to `null` — "unknown" — and an unknown count renders the
|
|
359
|
+
* banner. That is the beta regression: a cart that is empty, reported as unknowable.
|
|
360
|
+
*
|
|
361
|
+
* The payload has none of that dependency. It is delivered synchronously, by the same
|
|
362
|
+
* event that told us the cart changed, and it cannot fail, hang, or lose a race.
|
|
363
|
+
*
|
|
364
|
+
* Two sources, in order: the payload's own `count`, then the `cart.summary.count` the
|
|
365
|
+
* same emitter wrote a line earlier. Both are strict — see `toCartCount` — so a payload
|
|
366
|
+
* that states nothing leaves the previous value untouched rather than reading as empty.
|
|
367
|
+
*/
|
|
368
|
+
private readCartCountFromEvent;
|
|
369
|
+
/**
|
|
370
|
+
* The SECONDARY count, for a `cart::updated` that carried no usable `count` of its own.
|
|
371
|
+
*
|
|
372
|
+
* `SallaCartEvent.updated()` writes `cart.summary.count` from `data.cart`
|
|
373
|
+
* (twilight/src/event/cart.js:102-110) SYNCHRONOUSLY, immediately before it dispatches
|
|
374
|
+
* the event — so inside a `cart::updated` handler this value is as fresh as the payload
|
|
375
|
+
* and comes from the same object. `mobileBridge._cart` falls back to exactly this, for
|
|
376
|
+
* exactly this reason.
|
|
377
|
+
*
|
|
378
|
+
* ONLY safe from inside that handler, which is why it is private to
|
|
379
|
+
* `readCartCountFromEvent` and never consulted at mount: on a cold load the same key
|
|
380
|
+
* can hold a count left over from a previous session, and trusting it there would hide
|
|
381
|
+
* the banner on a cart that is not empty.
|
|
382
|
+
*
|
|
383
|
+
* Read through the public `salla.storage` facade; wrapped, because storage is not
|
|
384
|
+
* guaranteed to be installed and a throw here must cost nothing.
|
|
385
|
+
*/
|
|
386
|
+
private readCartCountFromStorage;
|
|
387
|
+
/**
|
|
388
|
+
* Coerce a reported count, STRICTLY. Only a finite, non-negative number is an answer;
|
|
389
|
+
* `undefined`, `null`, a string and a negative all mean "not stated", which leaves the
|
|
390
|
+
* previous value alone — the degenerate `{ fromLatestFetched }` payload
|
|
391
|
+
* (event/cart.js:85) must never read as an empty cart.
|
|
392
|
+
*/
|
|
393
|
+
private toCartCount;
|
|
394
|
+
private getFreeLabel;
|
|
395
|
+
/**
|
|
396
|
+
* Read a `blocks.cart_offer.*` translation.
|
|
397
|
+
*
|
|
398
|
+
* `salla.lang.get` returns the KEY VERBATIM when a translation is missing, which would
|
|
399
|
+
* put "blocks.cart_offer.congrats" in front of a shopper. A missing key reads as its
|
|
400
|
+
* `EN_FALLBACKS` entry instead, with the same `:placeholder` substitution.
|
|
401
|
+
*/
|
|
402
|
+
private trans;
|
|
403
|
+
/**
|
|
404
|
+
* The ONLY eligibility question worth asking: does this store have the feature?
|
|
405
|
+
*
|
|
406
|
+
* `salla.config.get` is a plain object read and cannot throw, but the whole thing is
|
|
407
|
+
* still wrapped: this predicate is called from a lifecycle hook, and "the SDK is not
|
|
408
|
+
* on the page yet" must degrade to "not eligible *yet*", never to an exception.
|
|
409
|
+
*/
|
|
410
|
+
private isEnabled;
|
|
411
|
+
/**
|
|
412
|
+
* ONE banner, even when several cart offers are live the FIRST conditional
|
|
413
|
+
* offer carrying at least one checkpoint wins and the rest are ignored. Merging them
|
|
414
|
+
* would produce a badge row whose tiers belong to different promotions.
|
|
415
|
+
*/
|
|
416
|
+
private fetchOffer;
|
|
417
|
+
/**
|
|
418
|
+
* The single entry point for "ask the server what this cart is entitled to".
|
|
419
|
+
*
|
|
420
|
+
* Runs on the initial load AND on every `cart::updated`, and is IDEMPOTENT and
|
|
421
|
+
* REVERSIBLE in both directions — `canRender` is derived from this pass alone, so a
|
|
422
|
+
* cart that becomes eligible after starting out ineligible turns the component back
|
|
423
|
+
* on. No state set here is a one-way door.
|
|
424
|
+
*/
|
|
425
|
+
private refresh;
|
|
426
|
+
/**
|
|
427
|
+
* The single gate on rendering anything at all: an AFFIRMATIVE "the cart holds at
|
|
428
|
+
* least one item".
|
|
429
|
+
*
|
|
430
|
+
* Deliberately not `!== 0` and not a truthy test. `cartItemCount` is `null` until
|
|
431
|
+
* something answers, and `null` must read as "do not render" — the default is hidden,
|
|
432
|
+
* and only a real, non-negative count of 1 or more opens the gate.
|
|
433
|
+
*/
|
|
434
|
+
private hasCartItems;
|
|
435
|
+
/**
|
|
436
|
+
* Whether this pass's response should be discarded: a newer `refresh()` has since
|
|
437
|
+
* been issued (so this one describes an older cart), or the host is gone. Answers
|
|
438
|
+
* "whose data wins" ONLY — never "should the skeleton stay up"; see the `finally`
|
|
439
|
+
* in `refresh()`.
|
|
440
|
+
*/
|
|
441
|
+
private isSuperseded;
|
|
442
|
+
/**
|
|
443
|
+
* The two things this component needs from the CART itself: how many lines it holds,
|
|
444
|
+
* and which of them are this offer's free gifts.
|
|
445
|
+
*
|
|
446
|
+
* ONE lookup for both. `getCartItemCount` and `getCartFreeGiftItems` are two
|
|
447
|
+
* projections of the same shared `details()` response and share its cache, its
|
|
448
|
+
* in-flight promise and its generation guard, so asking for both costs exactly one
|
|
449
|
+
* request — and, more importantly, the count and the gift lines can never describe
|
|
450
|
+
* two different cart states.
|
|
451
|
+
*
|
|
452
|
+
* Never rejects: a failing lookup is a missing "ادارة العرض" card and an UNKNOWN
|
|
453
|
+
* count, not a missing promotion and not an empty cart.
|
|
454
|
+
*/
|
|
455
|
+
private loadCart;
|
|
456
|
+
/**
|
|
457
|
+
* Arm the next cart-lookup retry, unless one is already armed or the episode has spent
|
|
458
|
+
* its attempts. See `CART_LOOKUP_RETRY_DELAYS_MS`.
|
|
459
|
+
*
|
|
460
|
+
* The attempt is SPENT when the timer fires, not when it is armed, so a timer a
|
|
461
|
+
* disconnect cleared is re-armed by `connectedCallback` without costing one.
|
|
462
|
+
*/
|
|
463
|
+
private scheduleCartLookupRetry;
|
|
464
|
+
/**
|
|
465
|
+
* End the retry episode: a lookup answered, or the cart changed under it. The count
|
|
466
|
+
* goes back to zero so a LATER failure gets its own full set of attempts.
|
|
467
|
+
*/
|
|
468
|
+
private clearCartLookupRetry;
|
|
469
|
+
/**
|
|
470
|
+
* Keep only the gift lines this offer granted.
|
|
471
|
+
*
|
|
472
|
+
* `CartItemTransformer::getFreeGiftData` emits `offer_id` OPTIMUS-ENCODED, exactly
|
|
473
|
+
* like `CartOffersTransformer` emits `id`, so the two join directly. When the offer id
|
|
474
|
+
* is unreadable we fall back to every gift line rather than hiding the card: only one
|
|
475
|
+
* cart offer can be applied to a cart at a time, so an unmatched gift is far more
|
|
476
|
+
* likely to be an id-shape surprise than another promotion's product.
|
|
477
|
+
*/
|
|
478
|
+
private selectOwnGifts;
|
|
479
|
+
/**
|
|
480
|
+
* Stages in ascending threshold order — the payload itself is UNSORTED.
|
|
481
|
+
*
|
|
482
|
+
* LOAD-BEARING: this filter and sort must mirror what `computeOfferStageProgress`
|
|
483
|
+
* does internally, because every renderer pairs `stages[i]` with
|
|
484
|
+
* `progress.thresholds[i]`. Both drop unusable thresholds via `isUsableThreshold` and
|
|
485
|
+
* both sort ascending and stably, so the two arrays stay index-aligned.
|
|
486
|
+
*/
|
|
487
|
+
private getStages;
|
|
488
|
+
/**
|
|
489
|
+
* The tier picture: the ENGINE'S verdict per tier plus the threshold projection.
|
|
490
|
+
*
|
|
491
|
+
* `is_applied` is passed positionally from THIS array, so the flag can never be
|
|
492
|
+
* attributed to the wrong tier — `stages[i]`, `progress.thresholds[i]`,
|
|
493
|
+
* `progress.met[i]` and `progress.applied[i]` are one row. Alignment matters more
|
|
494
|
+
* than ever now: the winner's INDEX decides where the ✓ run stops.
|
|
495
|
+
*
|
|
496
|
+
* Each tier's OWN `current_value` rides along the same way. It is what the distance in
|
|
497
|
+
* the guidance line is measured against, and a tier that carries none — every tier, on
|
|
498
|
+
* an older backend — is measured by the offer-level value exactly as before.
|
|
499
|
+
*/
|
|
500
|
+
private getProgress;
|
|
501
|
+
/**
|
|
502
|
+
* A tier's render key. `id` is the stable identity across refetches; the array
|
|
503
|
+
* position is the last resort for a backend that does not send one.
|
|
504
|
+
*/
|
|
505
|
+
private tierKey;
|
|
506
|
+
/**
|
|
507
|
+
* How this offer MEASURES its thresholds, as `offer-stages` names it .
|
|
508
|
+
*
|
|
509
|
+
* The remainder is rounded to the measure — whole products for `products_count`,
|
|
510
|
+
* store currency precision for everything else — so `formatRequirement` is handed a
|
|
511
|
+
* value its formatter can actually render. A `products_count` offer is the one that
|
|
512
|
+
* MUST be told apart: `salla.lang.choice` matches a fraction against no plural
|
|
513
|
+
* interval and answers "no products".
|
|
514
|
+
*/
|
|
515
|
+
private stageMeasure;
|
|
516
|
+
/**
|
|
517
|
+
* Format a cart value (a threshold, or a remaining amount/quantity) the way the
|
|
518
|
+
* offer is measured: money for `order_amount`, a pluralised item count for
|
|
519
|
+
* `products_count`. May return HTML (`salla.money`), so callers use `innerHTML`.
|
|
520
|
+
*/
|
|
521
|
+
private formatRequirement;
|
|
522
|
+
/**
|
|
523
|
+
* The shopper-facing reward text, SERVER-RENDERED.
|
|
524
|
+
*
|
|
525
|
+
* Never re-derived here. `reward_label` already carries the type-specific wording,
|
|
526
|
+
* the currency the value was converted to, and the merchant's own theme-translation
|
|
527
|
+
* overrides. Empty (free_shipping, unknown types) means "render nothing", which is
|
|
528
|
+
* why every caller has a reward-less sentence to fall back to.
|
|
529
|
+
*/
|
|
530
|
+
private getRewardText;
|
|
531
|
+
/**
|
|
532
|
+
* Guidance toward the first tier ABOVE the one the engine granted. Empty once
|
|
533
|
+
* that is the last tier (then the congratulations line renders instead).
|
|
534
|
+
*
|
|
535
|
+
* The TARGET comes from the verdict (`nextUnmetIndex`), the DISTANCE from the
|
|
536
|
+
* arithmetic — which is legitimate here, because "add :remaining" is a projection
|
|
537
|
+
* about the shopper's cart, not a claim about what the engine did. It is measured in
|
|
538
|
+
* the TARGET tier's own `current_value` when the backend sends one (see `getProgress`).
|
|
539
|
+
*
|
|
540
|
+
* Without that the two can disagree: the engine can withhold a tier whose threshold is
|
|
541
|
+
* already met, because its own excluded-product set is not in the payload. Then the distance
|
|
542
|
+
* is 0 and "Add SAR 0 and get a free gift" is false in both halves, so the line is
|
|
543
|
+
* dropped rather than rendered — and we do NOT skip ahead to a farther tier, which
|
|
544
|
+
* would advertise a reward past a nearer one the shopper cannot have. Nothing is the
|
|
545
|
+
* honest answer; the tooltip still shows the tier as locked.
|
|
546
|
+
*/
|
|
547
|
+
private getGuidanceMessage;
|
|
548
|
+
/** One tooltip row's sentence: "spend X, get Y", both halves from the offer payload. */
|
|
549
|
+
private getTierSentence;
|
|
550
|
+
/**
|
|
551
|
+
* The reward-TYPE icon shown in a tooltip row's circle.
|
|
552
|
+
*
|
|
553
|
+
* `fixed` is the awkward one: the design draws the Saudi Riyal glyph, but a store
|
|
554
|
+
* selling in USD must not. `sicon-sar` is used on exactly the same condition
|
|
555
|
+
* `salla.money()` uses to swap the currency for that glyph
|
|
556
|
+
* (`base/src/helpers/numbers.ts:78-86`), and a currency-neutral banknote otherwise.
|
|
557
|
+
*/
|
|
558
|
+
private getRewardIcon;
|
|
559
|
+
private usesSarSymbol;
|
|
560
|
+
private get isTooltipOpen();
|
|
561
|
+
private toggleTooltip;
|
|
562
|
+
private closeTooltip;
|
|
563
|
+
/**
|
|
564
|
+
* WHY THIS IS NOT `salla-tooltip`.
|
|
565
|
+
*
|
|
566
|
+
* It is a fair question — the component exists, it is shared, and this component used
|
|
567
|
+
* to depend on it (master renders a `<salla-tooltip theme="dark">` product preview off
|
|
568
|
+
* the badge avatars). That usage was hover-only, non-interactive and string-sized, and
|
|
569
|
+
* it is exactly the shape `salla-tooltip` is built for. The ⓘ popover this design asks
|
|
570
|
+
* for is not, and the gap is not one prop wide:
|
|
571
|
+
*
|
|
572
|
+
* - VISIBILITY IS PRIVATE TO IT. `show` is `@State`, flipped only by the component's
|
|
573
|
+
* own `mouseover`/`mouseout` window listeners (salla-tooltip.tsx:26-38). There is no
|
|
574
|
+
* `open` prop and no way for an owner to pin it, so the CLICK path — which is the
|
|
575
|
+
* ONLY way this popover opens on a touch device — cannot be expressed at all.
|
|
576
|
+
* - IT IS DELIBERATELY UNREACHABLE. `pointer-events: none` (salla-tooltip.scss:20).
|
|
577
|
+
* A pinned panel the shopper cannot hover or scroll is not a mobile path.
|
|
578
|
+
* - NO DISMISSAL MODEL. Escape and outside-click do not exist there, because with
|
|
579
|
+
* hover-only visibility there is nothing to dismiss.
|
|
580
|
+
* - NO POSITIONING TO REUSE. It is pinned by two hand-tuned magic percentages —
|
|
581
|
+
* `translateX(-63%)` and `translateX(-72%)` under `:dir(ltr)` — with a caret nailed
|
|
582
|
+
* to `left: 50%` and never re-aimed. There is no viewport clamp, so the exact
|
|
583
|
+
* behaviour this method exists for is the one piece it does not have.
|
|
584
|
+
* - IT IS SIZED FOR A STRING. `max-width: 150px` / `width: 190px` default,
|
|
585
|
+
* `max-width: 330px` dark. This panel is a title, a description and one row per
|
|
586
|
+
* tier.
|
|
587
|
+
* - IT ANCHORS BY GLOBAL ID. `document.getElementById(targetId)` at `componentDidLoad`
|
|
588
|
+
* (salla-tooltip.tsx:40-43). The ⓘ trigger has no stable document-unique id, and the
|
|
589
|
+
* banner can legitimately mount more than once on a page.
|
|
590
|
+
*
|
|
591
|
+
* Closing all six is not "a prop, not a hack" — it is a controlled-visibility API, a
|
|
592
|
+
* dismissal model, an anchoring change and a positioning engine, i.e. a rewrite of all
|
|
593
|
+
* 55 lines of it. And `salla-tooltip` is SHARED: `salla-comment-item` and `salla-wallet`
|
|
594
|
+
* both pass a plain `text` + `targetId` and depend on precisely the hover-only,
|
|
595
|
+
* `pointer-events: none`, magic-percentage behaviour above, with a single spec between
|
|
596
|
+
* them that asserts its exact rendered HTML. Rewriting it under them to serve this
|
|
597
|
+
* banner trades a scoped 90 lines here for a silent regression surface there.
|
|
598
|
+
*
|
|
599
|
+
* So the bespoke implementation stays, and stays SMALL: everything below is the clamp
|
|
600
|
+
* and the caret. The resting position is plain CSS.
|
|
601
|
+
*
|
|
602
|
+
* Keep the popover pointing at the ⓘ icon AND inside the viewport.
|
|
603
|
+
*
|
|
604
|
+
* The CSS already centres it on the trigger — that is the resting answer, and it is
|
|
605
|
+
* correct in RTL and LTR alike because centring is symmetric, which is exactly why the
|
|
606
|
+
* rules are written with physical `left`/`translateX` rather than `inset-inline-*`.
|
|
607
|
+
* Anything logical would have to be mirrored again here, in a coordinate space
|
|
608
|
+
* (`getBoundingClientRect`) that is physical no matter what the document direction is.
|
|
609
|
+
*
|
|
610
|
+
* All this adds on top is the CLAMP: at narrow widths a popover centred on an icon
|
|
611
|
+
* that sits near the edge of the screen would hang off it, so the box is nudged back
|
|
612
|
+
* inside and the caret is shifted by the same amount in the opposite direction, so it
|
|
613
|
+
* stays over the icon while the box moves out from under it.
|
|
614
|
+
*
|
|
615
|
+
* Written as CSS CUSTOM PROPERTIES on the element, never as `@State`: this runs from
|
|
616
|
+
* `componentDidRender`, and anything that re-entered render() from here would loop.
|
|
617
|
+
*/
|
|
618
|
+
private positionTooltip;
|
|
619
|
+
private setTooltipOffsets;
|
|
620
|
+
/**
|
|
621
|
+
* A resize changes which edge the popover is up against, so the clamp has to be
|
|
622
|
+
* re-derived. Cheap: `positionTooltip` returns on its first line unless the popover is
|
|
623
|
+
* actually open.
|
|
624
|
+
*/
|
|
625
|
+
handleWindowResize(): void;
|
|
626
|
+
handleDocumentKeydown(event: KeyboardEvent): void;
|
|
627
|
+
/**
|
|
628
|
+
* Outside click dismisses a PINNED tooltip. `composedPath` rather than
|
|
629
|
+
* `event.target`, so a click on a slotted/nested element still resolves to the
|
|
630
|
+
* anchor; `contains` is the fallback for the (test) environments that lack it.
|
|
631
|
+
*/
|
|
632
|
+
handleDocumentClick(event: MouseEvent): void;
|
|
633
|
+
/**
|
|
634
|
+
* The `id` of one of this instance's own elements. EVERY id and every reference to it
|
|
635
|
+
* (`aria-describedby`, `aria-controls`) goes through here, so the two cannot drift.
|
|
636
|
+
*/
|
|
637
|
+
private elementId;
|
|
638
|
+
/**
|
|
639
|
+
* The badge row: one small circle per tier, joined by short connectors.
|
|
640
|
+
*
|
|
641
|
+
* Tier count is VARIABLE, so the row is a nowrap flex line that scrolls
|
|
642
|
+
* horizontally inside a bounded width instead of squeezing the message beside it.
|
|
643
|
+
* RTL correctness comes from the markup: a plain flex row inherits the document's
|
|
644
|
+
* `dir`, so tier 1 lands on the leading edge and the scroll origin follows it in both
|
|
645
|
+
* directions. Do not add physical left/right offsets here.
|
|
646
|
+
*/
|
|
647
|
+
private renderBadges;
|
|
648
|
+
/**
|
|
649
|
+
* Spec §4 — the ⓘ popover: title, the merchant's offer message, then one row per tier.
|
|
650
|
+
*
|
|
651
|
+
* The prose is `details.rules`, NEVER `offer.description` — see `readOfferRules`.
|
|
652
|
+
*/
|
|
12
653
|
private renderTooltip;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
654
|
+
/**
|
|
655
|
+
* "ادارة العرض". Rendered TWICE with complementary responsive visibility rather than
|
|
656
|
+
* moved with `order:`: desktop wants it inline with the banner row, mobile wants a
|
|
657
|
+
* full-width bar BELOW the gifts card, and those are different places
|
|
658
|
+
* in the flow, not a different order within one flex line.
|
|
659
|
+
*/
|
|
660
|
+
private renderGiftsToggle;
|
|
661
|
+
/** Spec §5 — the gifts card: one read-only row per gift line. */
|
|
662
|
+
private renderGiftsCard;
|
|
663
|
+
private renderGiftRow;
|
|
18
664
|
private getLoadingSkeletonView;
|
|
19
665
|
render(): any;
|
|
20
666
|
}
|