JezK
Edit File: sc-order-bump2.js.map
{"file":"sc-order-bump2.js","mappings":";;;;;;;;;;AAAA,MAAM,cAAc,GAAG,mqNAAmqN,CAAC;AAC3rN,0BAAe,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBC4Cf,UAAE;;;;;;;;sBAOF,UAAE;;;;;kBAIJ,UAAE;;;;;;;;;;;;;;;;;2IAgBoF,UAAE;;;;;;;;;;gBAaxF,eAAO,CAAC,UAAE,4NAIT,UAAE;;;;;;;gBAcH,eAAO,CAAC,UAAE,+EAIT,eAAO;;YAEN,UAAE;;;;;;;;YAiBF,eAAO,CAAC,UAAE,wXAUsB,UAAE;;;;;;;YAiBlC,eAAO,CAAC,UAAE,wXAUsB,UAAE;;;;;;;;;;oYA4BtB,eAAO;;YAEjB,UAAE,27BAmBU,eAAO;;YAEjB,UAAE;;;;8FAiBe,UAAE,oCAAoC,UAAE;;;;;;;;;;;;;;;;uMAuCjD,eAAO;;YAEjB,UAAE,qsDA4Bc,eAAO;;YAEjB,UAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","names":[],"sources":["src/components/controllers/checkout-form/sc-order-bump/sc-order-bump.scss?tag=sc-order-bump&encapsulation=shadow","src/components/controllers/checkout-form/sc-order-bump/sc-order-bump.tsx"],"sourcesContent":[":host {\n display: block;\n}\n\n// ============================================================\n// MODERN DESIGN\n// ============================================================\n\n// Override sc-choice internals: no padding, rounded corners,\n// neutral border always. Suppress checked/focus borders and pointer cursor.\n:host([data-design='modern']) sc-choice::part(base) {\n padding: 0;\n overflow: hidden;\n border-radius: var(--sc-order-bump-border-radius, 12px);\n // !important is needed to override sc-choice's checked/focus styles,\n // but it blocks consumer overrides via ::part(choice__base).\n border-color: var(--sc-order-bump-border-color, #e0e0e0) !important;\n box-shadow: none !important;\n outline: none !important;\n cursor: default;\n}\n\n:host([data-design='modern']) sc-choice::part(content) {\n cursor: default;\n}\n\n// Note: sc-choice parts are exported as choice__base and choice__content\n// to avoid naming conflicts with sc-order-bump's own parts.\n\n:host([data-design='modern']) {\n .bump {\n display: flex;\n align-items: center;\n gap: var(--sc-order-bump-gap, 1em);\n padding: var(--sc-order-bump-padding, 0.75em 1em 0.75em 0.75em);\n\n &__text {\n display: grid;\n gap: 5px;\n flex: 1;\n min-width: 0;\n }\n &__title {\n font-size: var(--sc-order-bump-title-size, 15px);\n font-weight: 600;\n line-height: 1.3;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-word;\n }\n &__cta {\n font-size: var(--sc-order-bump-cta-size, 12px);\n font-weight: 600;\n line-height: 1.3;\n display: var(--sc-order-bump-cta-display, -webkit-box);\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-word;\n }\n &__tag {\n display: var(--sc-order-bump-tag-display, inline-block);\n background: var(--sc-color-primary-500);\n color: var(--sc-color-white);\n border-radius: 999px;\n padding: var(--sc-spacing-x-small);\n font-size: var(--sc-font-size-x-small);\n }\n\n &__description {\n display: var(--sc-order-bump-description-display, block);\n font-size: var(--sc-order-bump-description-size, 13px);\n color: var(--sc-order-bump-description-color, #6b7280);\n opacity: 0.8;\n line-height: 1.3;\n font-weight: normal;\n p {\n margin: 0;\n }\n }\n\n &__image {\n width: var(--sc-order-bump-image-size, 72px);\n height: var(--sc-order-bump-image-size, 72px);\n flex: 0 0 var(--sc-order-bump-image-size, 72px);\n object-fit: cover;\n border-radius: var(--sc-order-bump-image-radius, 8px);\n border: 1px solid var(--sc-color-gray-200);\n display: var(--sc-order-bump-image-display, block);\n }\n\n &__price {\n &--has-discount {\n display: flex;\n align-items: baseline;\n gap: 4px;\n .bump__original-price {\n text-decoration: line-through;\n color: var(--sc-color-gray-500);\n font-size: var(--sc-order-bump-price-size, 14px);\n font-weight: normal;\n }\n }\n .bump__new-price {\n font-size: var(--sc-order-bump-price-size, 14px);\n font-weight: 500;\n color: var(--sc-color-gray-700);\n }\n .bump__interval {\n color: var(--sc-color-gray-500);\n }\n }\n\n &__button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--sc-order-bump-button-radius, 74.6%);\n border: 1px solid var(--sc-order-bump-button-border-color, #d1d5db);\n background-color: transparent;\n color: var(--sc-color-gray-500);\n flex-shrink: 0;\n line-height: 1;\n font-size: var(--sc-order-bump-button-size, 18px);\n padding: 0.5em;\n cursor: pointer;\n transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;\n\n sc-icon {\n font-size: 1em;\n }\n\n &--checked {\n background-color: var(--sc-color-primary-500);\n border-color: var(--sc-color-primary-500);\n color: var(--sc-color-white, #fff);\n }\n\n &--loading {\n border-color: var(--sc-color-primary-500);\n\n sc-spinner {\n --spinner-size: 1em;\n --indicator-color: var(--sc-color-gray-500);\n }\n }\n }\n\n &__amount {\n display: var(--sc-order-bump-amount-display, flex);\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n }\n }\n}\n\n// ============================================================\n// CLASSIC DESIGN\n// ============================================================\n\n:host([data-design='classic']) {\n .bump {\n display: grid;\n gap: 1em;\n\n &__text {\n display: grid;\n gap: 0.25em;\n }\n &__tag {\n background: var(--sc-color-primary-500);\n color: var(--sc-color-white);\n border-radius: var(--sc-input-border-radius-medium);\n padding: var(--sc-spacing-x-small);\n font-size: var(--sc-font-size-x-small);\n }\n\n &__product {\n display: flex;\n align-items: center;\n gap: var(--sc-choice-padding, 1.3em 1.1em);\n line-height: var(--sc-line-height-dense);\n &--wrapper {\n container-type: inline-size;\n }\n @container (max-width: 325px) {\n flex-direction: column;\n align-items: start;\n }\n }\n\n &__title {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-word;\n }\n\n &__product-title {\n font-weight: var(--sc-font-weight-semibold);\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: break-word;\n }\n\n &__product-description {\n color: var(--sc-input-label-color);\n }\n\n &__image {\n width: var(--sc-product-line-item-image-size, 4em);\n height: var(--sc-product-line-item-image-size, 4em);\n flex: 0 0 var(--sc-product-line-item-image-size, 4em);\n object-fit: cover;\n border-radius: 4px;\n border: 1px solid var(--sc-color-gray-200);\n display: block;\n box-shadow: var(--sc-input-box-shadow);\n }\n\n &__price {\n &--has-discount {\n display: flex;\n align-items: baseline;\n gap: var(--sc-spacing-x-small);\n .bump__original-price {\n text-decoration: line-through;\n color: var(--sc-color-gray-500);\n font-size: var(--sc-font-size-small);\n }\n }\n .bump__new-price {\n font-size: var(--sc-font-size-large);\n color: var(--sc-color-gray-700);\n }\n .bump__interval {\n color: var(--sc-color-gray-500);\n }\n }\n\n &__amount {\n display: flex;\n align-items: center;\n gap: var(--sc-spacing-x-small);\n flex-wrap: wrap;\n margin-top: var(--sc-spacing-xx-small);\n }\n }\n}\n","import { Component, Element, h, Prop, State } from '@stencil/core';\nimport { sprintf, __, _x } from '@wordpress/i18n';\nimport { speak } from '@wordpress/a11y';\nimport { intervalString } from '../../../../functions/price';\nimport { state as checkoutState } from '@store/checkout';\n\nimport { Bump, Price, Product } from '../../../../types';\nimport { addCheckoutLineItem, removeCheckoutLineItem, trackOrderBump } from '@store/checkout/mutations';\n\n@Component({\n tag: 'sc-order-bump',\n styleUrl: 'sc-order-bump.scss',\n shadow: true,\n})\nexport class ScOrderBump {\n @Element() el: HTMLElement;\n\n /** The bump */\n @Prop() bump: Bump;\n\n /** Should we show the controls (classic design) */\n @Prop({ reflect: true }) showControl: boolean;\n\n /** Loading state */\n @State() loading: boolean = false;\n\n /** Cached design mode */\n private isModern: boolean = true;\n\n /** The bump line item */\n lineItem() {\n return checkoutState?.checkout?.line_items?.data?.find(item => item?.bump === this.bump?.id);\n }\n\n /** Update the line item. */\n async updateLineItem() {\n if (this.loading) return;\n\n const price = (this.bump.price as Price)?.id || (this.bump?.price as string);\n const lineItem = this.lineItem();\n\n this.loading = true;\n try {\n if (lineItem) {\n await removeCheckoutLineItem(lineItem.id);\n speak(__('Order bump Removed.', 'surecart'));\n } else {\n await addCheckoutLineItem({\n bump: this.bump?.id,\n price,\n quantity: 1,\n });\n speak(__('Order bump applied.', 'surecart'));\n }\n } catch (e) {\n console.error('[sc-order-bump] updateLineItem failed:', e);\n speak(__('Something went wrong. Please try again.', 'surecart'));\n } finally {\n this.loading = false;\n }\n }\n\n componentWillLoad() {\n this.isModern = !!(window as any).scData?.modern_order_bump;\n this.el.setAttribute('data-design', this.isModern ? 'modern' : 'classic');\n }\n\n componentDidLoad() {\n trackOrderBump(this.bump?.id);\n }\n\n renderInterval() {\n const interval = intervalString(this.bump?.price as Price, { labels: { interval: '/', period: __('for', 'surecart') } });\n if (!interval.trim().length) return null;\n return <span class=\"bump__interval\">{interval}</span>;\n }\n\n renderDiscount() {\n if (!!this.bump?.amount_off) {\n return (\n <div\n class=\"bump__tag\"\n part=\"tag\"\n aria-label={\n /** translators: %1$s: amount off, %2$s: currency */\n sprintf(__('You save %1$s%2$s.', 'surecart'), this.bump?.amount_off, (this.bump?.price as Price).currency)\n }\n >\n <span aria-hidden=\"true\">\n {_x('Save', 'Save money', 'surecart')} {this.bump?.amount_off_display_amount}\n </span>\n </div>\n );\n }\n\n if (!!this.bump?.percent_off) {\n const percent = `${this.bump.percent_off}%`;\n return (\n <div\n class=\"bump__tag\"\n part=\"tag\"\n aria-label={\n /** translators: %s is the discount percentage (e.g. \"10%\"). */\n sprintf(__('You save %s.', 'surecart'), percent)\n }\n >\n <span aria-hidden=\"true\">\n {sprintf(\n /** translators: %s is the discount percentage (e.g. \"10%\"). */\n _x('Save %s', 'Save money', 'surecart'),\n percent,\n )}\n </span>\n </div>\n );\n }\n }\n\n /** Modern price (no slot) */\n renderModernPrice() {\n return (\n <div class={{ 'bump__price': true, 'bump__price--has-discount': !!this.bump?.percent_off || !!this.bump?.amount_off }} part=\"price\">\n {!!(this.bump?.percent_off || this.bump?.amount_off) && (\n <span\n aria-label={\n /** translators: %s: old price */\n sprintf(__('Originally priced at %s.', 'surecart'), this.bump?.subtotal_display_amount)\n }\n class=\"bump__original-price\"\n >\n {this.bump?.subtotal_display_amount}\n </span>\n )}\n\n <span>\n <span aria-hidden=\"true\">\n {this.bump?.total_amount === 0 && __('Free', 'surecart')}\n {this.bump?.total_amount > 0 && <span class=\"bump__new-price\">{this.bump?.total_display_amount}</span>}\n {this.renderInterval()}\n </span>\n </span>\n </div>\n );\n }\n\n /** Classic price (with slot=\"description\") */\n renderClassicPrice() {\n return (\n <div slot=\"description\" class={{ 'bump__price': true, 'bump__price--has-discount': !!this.bump?.percent_off || !!this.bump?.amount_off }} part=\"price\">\n {!!(this.bump?.percent_off || this.bump?.amount_off) && (\n <span\n aria-label={\n /** translators: %s: old price */\n sprintf(__('Originally priced at %s.', 'surecart'), this.bump?.subtotal_display_amount)\n }\n class=\"bump__original-price\"\n >\n {this.bump?.subtotal_display_amount}\n </span>\n )}\n\n <span>\n <span aria-hidden=\"true\">\n {this.bump?.total_amount === 0 && __('Free', 'surecart')}\n {this.bump?.total_amount > 0 && <span class=\"bump__new-price\">{this.bump?.total_display_amount}</span>}\n {this.renderInterval()}\n </span>\n </span>\n </div>\n );\n }\n\n /** Modern design: rounded button, inline image, no checkbox */\n renderModern() {\n const product = (this.bump?.price as Price)?.product as Product;\n const lineItem = this.lineItem();\n return (\n <sc-choice value={this.bump?.id} type=\"checkbox\" showControl={false} checked={!!lineItem} exportparts=\"base:choice__base, content:choice__content\">\n <div\n part=\"base-content\"\n class=\"bump\"\n onClick={e => e.stopPropagation()}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') e.stopPropagation();\n }}\n >\n {!!product?.line_item_image?.src && <img {...(product?.line_item_image as any)} class=\"bump__image\" part=\"image\" />}\n <div class=\"bump__text\" part=\"text\">\n <div\n class=\"bump__title\"\n part=\"title\"\n aria-label={sprintf(\n /* translators: %s: order bump name */\n __('Product: %s.', 'surecart'),\n this.bump?.name || product?.name,\n )}\n >\n <span aria-hidden=\"true\">{this.bump?.name || product?.name}</span>\n </div>\n {!!this.bump?.metadata?.cta && (\n <div class=\"bump__cta\" part=\"cta\">\n <span>{this.bump?.metadata?.cta}</span>\n </div>\n )}\n <div class=\"bump__amount\" part=\"amount\">\n {this.renderModernPrice()}\n {this.renderDiscount()}\n </div>\n {!!this.bump?.metadata?.description && (\n <div\n class=\"bump__description\"\n part=\"description\"\n aria-label={sprintf(\n /* translators: %s: Product description */\n __('Product description: %s.', 'surecart'),\n this.bump?.rendered_description,\n )}\n >\n <span aria-hidden=\"true\" innerHTML={this.bump?.rendered_description}></span>\n </div>\n )}\n </div>\n <button\n type=\"button\"\n class={{\n 'bump__button': true,\n 'bump__button--checked': !!lineItem,\n 'bump__button--loading': this.loading,\n }}\n part=\"button\"\n onClick={() => this.updateLineItem()}\n aria-label={lineItem ? __('Remove from order', 'surecart') : __('Add to order', 'surecart')}\n aria-pressed={!!lineItem}\n aria-busy={this.loading}\n >\n {this.loading ? <sc-spinner /> : <sc-icon name={lineItem ? 'check' : 'plus'} />}\n </button>\n </div>\n </sc-choice>\n );\n }\n\n /** Classic design: checkbox control, footer with divider + image + description */\n renderClassic() {\n const product = (this.bump?.price as Price)?.product as Product;\n return (\n <sc-choice\n value={this.bump?.id}\n type=\"checkbox\"\n showControl={this.showControl}\n checked={!!this.lineItem()}\n disabled={this.loading}\n onClick={e => {\n e.preventDefault();\n e.stopImmediatePropagation();\n this.updateLineItem();\n }}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopImmediatePropagation();\n this.updateLineItem();\n }\n }}\n exportparts=\"base, control, checked-icon, title\"\n >\n <div part=\"base-content\" class=\"bump\">\n <div class=\"bump__text\">\n <div\n class=\"bump__title\"\n aria-label={sprintf(\n /* translators: %s: order bump name */\n __('Product: %s.', 'surecart'),\n this.bump?.metadata?.cta || this.bump?.name || product?.name,\n )}\n >\n <span aria-hidden=\"true\">{this.bump?.metadata?.cta || this.bump?.name || product?.name}</span>\n </div>\n <div class=\"bump__amount\">\n {this.renderClassicPrice()}\n {this.renderDiscount()}\n </div>\n </div>\n </div>\n\n {this.bump?.metadata?.description && (\n <div slot=\"footer\" class=\"bump__product--wrapper\">\n <sc-divider style={{ '--spacing': 'var(--sc-spacing-medium)' }}></sc-divider>\n <div class=\"bump__product\">\n {!!product?.line_item_image?.src && <img {...(product?.line_item_image as any)} class=\"bump__image\" />}\n <div class=\"bump__product-text\">\n {!!this.bump?.metadata?.cta && (\n <div class=\"bump__product-title\" aria-hidden=\"true\">\n {this.bump.name || product?.name}\n </div>\n )}\n\n {!!this.bump?.metadata?.description && (\n <div\n class=\"bump__product-description\"\n aria-label={sprintf(\n /* translators: %s: Product description */\n __('Product description: %s.', 'surecart'),\n this.bump?.rendered_description,\n )}\n >\n <span aria-hidden=\"true\" innerHTML={this.bump?.rendered_description}></span>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n </sc-choice>\n );\n }\n\n render() {\n return this.isModern ? this.renderModern() : this.renderClassic();\n }\n}\n"],"version":3}