JezK
Edit File: sc-cancel-discount_4.cjs.entry.js.map
{"file":"sc-cancel-discount.sc-cancel-survey.sc-subscription-cancel.sc-textarea.entry.cjs.js","mappings":"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","names":["apiFetch","addQueryArgs","h"],"sources":["src/components/controllers/dashboard/sc-cancel-discount/functions.ts","src/components/controllers/dashboard/sc-cancel-discount/sc-cancel-discount.scss?tag=sc-cancel-discount&encapsulation=shadow","src/components/controllers/dashboard/sc-cancel-discount/sc-cancel-discount.tsx","src/components/controllers/dashboard/sc-cancel-survey/sc-cancel-survey.scss?tag=sc-cancel-survey&encapsulation=shadow","src/components/controllers/dashboard/sc-cancel-survey/sc-cancel-survey.tsx","src/components/controllers/dashboard/subscription-cancel/sc-subscription-cancel.scss?tag=sc-subscription-cancel&encapsulation=shadow","src/components/controllers/dashboard/subscription-cancel/sc-subscription-cancel.tsx","src/components/ui/sc-textarea/sc-textarea.css?tag=sc-textarea&encapsulation=shadow","src/components/ui/sc-textarea/sc-textarea.tsx"],"sourcesContent":["import { getHumanDiscount } from '../../../../functions/price';\nimport { Coupon, SubscriptionProtocol } from '../../../../types';\nimport { __ } from '@wordpress/i18n';\n\n/**\n * Replace the {{ name }} in a string with a new value\n */\nexport const replaceAmount = (string, replace, name = 'amount') => {\n return string.replaceAll('{{' + name + '}}', replace).replaceAll('{{ ' + name + ' }}', replace);\n};\n\n/**\n * Replace the amount in a string with discount.\n */\nexport const replaceAmountFromString = (amountStr, protocol) =>\n protocol?.preservation_coupon ? replaceAmount(amountStr, getHumanDiscount(protocol?.preservation_coupon as Coupon)) : amountStr;\n\n/**\n *\n */\nexport const getCurrentBehaviourContent = (protocol: SubscriptionProtocol, hasDiscount) => {\n const { preserve_title, preserve_description, preserve_button, cancel_link } = protocol?.preservation_locales || {};\n\n if (hasDiscount) {\n const discountLocales = {\n title: replaceAmountFromString(__('Your {{ amount }} discount is still active.', 'surecart'), protocol),\n description: replaceAmountFromString(\n __('You have a {{ amount }} discount active. Cancelling now will forfeit this discount forever. Are you sure you wish to cancel?', 'surecart'),\n protocol,\n ),\n button: __('Keep My Discount', 'surecart'),\n cancel_link: __('Cancel Anyway', 'surecart'),\n };\n return discountLocales;\n }\n\n const defaultLocales = {\n title: replaceAmountFromString(preserve_title, protocol),\n description: replaceAmountFromString(preserve_description, protocol),\n button: preserve_button,\n cancel_link: cancel_link,\n };\n return defaultLocales;\n};\n",".cancel-discount {\n &__abort-link {\n color: var(--sc-input-help-text-color);\n }\n}\n","import { Component, Event, EventEmitter, h, Prop, State } from '@stencil/core';\nimport { __ } from '@wordpress/i18n';\nimport { addQueryArgs } from '@wordpress/url';\n\nimport apiFetch from '../../../../functions/fetch';\nimport { CancellationReason, ResponseError, Subscription, SubscriptionProtocol } from '../../../../types';\nimport { getCurrentBehaviourContent } from './functions';\n\n@Component({\n tag: 'sc-cancel-discount',\n styleUrl: 'sc-cancel-discount.scss',\n shadow: true,\n})\nexport class ScCancelDiscount {\n @Prop() subscription: Subscription;\n @Prop() reason: CancellationReason;\n @Prop() comment: string;\n @Prop() protocol: SubscriptionProtocol;\n @Event() scCancel: EventEmitter<void>;\n @Event() scPreserved: EventEmitter<void>;\n @State() loading: boolean;\n @State() error: ResponseError;\n\n async addDiscount() {\n try {\n this.loading = true;\n this.subscription = (await apiFetch({\n method: 'PATCH',\n path: addQueryArgs(`surecart/v1/subscriptions/${this.subscription?.id}/preserve`, {\n cancellation_act: {\n ...(!!this.comment ? { comment: this.comment } : {}),\n cancellation_reason_id: this.reason?.id,\n },\n }),\n })) as Subscription;\n this.scPreserved.emit();\n } catch (e) {\n console.error(e);\n this.error = e;\n } finally {\n this.loading = false;\n }\n }\n\n hasDiscount() {\n return !!this.subscription?.discount?.id;\n }\n\n render() {\n const { title, description, button, cancel_link } = getCurrentBehaviourContent(this.protocol, this.subscription?.discount?.id);\n return (\n <div class=\"cancel-discount\">\n <sc-dashboard-module heading={title} style={{ '--sc-dashboard-module-spacing': '2em' }}>\n <span slot=\"description\">{description}</span>\n <sc-flex justifyContent=\"flex-start\">\n <sc-button type=\"primary\" onClick={() => this.addDiscount()}>\n {button}\n </sc-button>\n <sc-button class=\"cancel-discount__abort-link\" type=\"text\" onClick={() => this.scCancel.emit()}>\n {cancel_link}\n </sc-button>\n </sc-flex>\n {!!this.loading && <sc-block-ui spinner />}\n </sc-dashboard-module>\n </div>\n );\n }\n}\n",".cancel-survey {\n &__abort-link {\n color: var(--sc-input-help-text-color);\n }\n}\n","import { Component, Event, EventEmitter, h, Prop, State, Watch } from '@stencil/core';\nimport { __ } from '@wordpress/i18n';\nimport apiFetch from '../../../../functions/fetch';\nimport { CancellationReason, ResponseError, SubscriptionProtocol } from '../../../../types';\n\n@Component({\n tag: 'sc-cancel-survey',\n styleUrl: 'sc-cancel-survey.scss',\n shadow: true,\n})\nexport class ScCancelSurvey {\n private textArea: HTMLScTextareaElement;\n @Prop() protocol: SubscriptionProtocol;\n @Prop({ mutable: true }) reasons: CancellationReason[];\n @State() loading: boolean;\n @State() selectedReason: CancellationReason;\n @State() comment: string;\n @State() error: ResponseError;\n @Event() scAbandon: EventEmitter<void>;\n @Event() scSubmitReason: EventEmitter<{ reason: CancellationReason; comment: string }>;\n\n componentWillLoad() {\n if (!this.reasons) {\n this.fetchReasons();\n }\n }\n\n @Watch('selectedReason')\n handleSelectedReasonChange() {\n if (this.selectedReason?.comment_enabled) {\n setTimeout(() => {\n this.textArea.triggerFocus();\n }, 50);\n }\n }\n\n async fetchReasons() {\n try {\n this.loading = true;\n this.reasons = await apiFetch({\n path: 'surecart/v1/cancellation_reasons',\n });\n } catch (e) {\n console.error(e);\n this.error = e;\n } finally {\n this.loading = false;\n }\n }\n\n async handleSubmit(e) {\n e.preventDefault();\n e.stopImmediatePropagation();\n try {\n this.scSubmitReason.emit({ reason: this.selectedReason, comment: this.comment });\n } catch (e) {\n console.error(e);\n this.error = e;\n }\n }\n\n renderReasons() {\n if (this.loading) {\n return (\n <sc-choice>\n <sc-skeleton></sc-skeleton>\n </sc-choice>\n );\n }\n\n return (this.reasons || []).map(reason => (\n <sc-choice\n value={reason?.id}\n name=\"reason\"\n onScChange={e => {\n if (e.target.checked) {\n this.selectedReason = reason;\n }\n }}\n >\n {reason?.label}\n </sc-choice>\n ));\n }\n\n render() {\n const { reasons_title, reasons_description, skip_link } = this.protocol?.preservation_locales || {};\n\n if (this.loading) {\n return <sc-skeleton></sc-skeleton>;\n }\n\n return (\n <div class=\"cancel-survey\">\n <sc-dashboard-module heading={reasons_title} style={{ '--sc-dashboard-module-spacing': '2em' }}>\n <span slot=\"description\">{reasons_description}</span>\n <sc-form onScSubmit={e => this.handleSubmit(e)} style={{ '--sc-form-row-spacing': '2em' }}>\n <sc-choices showLabel={false} label={__('Choose a reason', 'surecart')} style={{ '--columns': '2' }} required>\n {this.renderReasons()}\n </sc-choices>\n {this.selectedReason?.comment_enabled && (\n <sc-textarea\n label={this.selectedReason?.comment_prompt || __('Additional Comments', 'surecart')}\n required\n ref={el => (this.textArea = el as HTMLScTextareaElement)}\n onScInput={e => (this.comment = e.target.value)}\n ></sc-textarea>\n )}\n <sc-flex justifyContent=\"flex-start\">\n <sc-button type=\"primary\" submit>\n {__('Continue', 'surecart')}\n <sc-icon name=\"arrow-right\" slot=\"suffix\" />\n </sc-button>\n {!!skip_link && (\n <sc-button class=\"cancel-survey__abort-link\" type=\"text\" onClick={() => this.scAbandon.emit()}>\n {skip_link}\n </sc-button>\n )}\n </sc-flex>\n </sc-form>\n </sc-dashboard-module>\n </div>\n );\n }\n}\n",":host {\n display: block;\n position: relative;\n}\n\n.subscription-cancel {\n display: grid;\n gap: 0.5em;\n}\n\n.subscription-cancel__terms {\n color: var(--sc-input-help-text-color);\n font-size: var(--sc-font-size-small);\n}\n","import { Component, Event, EventEmitter, Fragment, h, Prop, State } from '@stencil/core';\nimport { __ } from '@wordpress/i18n';\nimport { addQueryArgs } from '@wordpress/url';\nimport apiFetch from '../../../../functions/fetch';\nimport { CancellationReason, SubscriptionProtocol } from '../../../../types';\nimport { Subscription } from '../../../../types';\n@Component({\n tag: 'sc-subscription-cancel',\n styleUrl: 'sc-subscription-cancel.scss',\n shadow: true,\n})\nexport class ScSubscriptionCancel {\n @Prop() heading: string;\n @Prop() backUrl: string;\n @Prop() successUrl: string;\n @Prop() subscription: Subscription;\n @Prop() protocol: SubscriptionProtocol;\n @Prop() reason: CancellationReason;\n @Prop() comment: string;\n @State() loading: boolean;\n @State() busy: boolean;\n @State() error: string;\n @Event() scAbandon: EventEmitter<void>;\n @Event() scCancelled: EventEmitter<void>;\n\n async cancelSubscription() {\n try {\n this.error = '';\n this.busy = true;\n await apiFetch({\n path: addQueryArgs(`/surecart/v1/subscriptions/${this.subscription?.id}/cancel`, {\n cancellation_act: {\n ...(!!this.comment ? { comment: this.comment } : {}),\n cancellation_reason_id: this.reason?.id,\n },\n }),\n method: 'PATCH',\n });\n this.scCancelled.emit();\n } catch (e) {\n this.error = e?.message || __('Something went wrong', 'surecart');\n this.busy = false;\n }\n }\n\n renderContent() {\n if (this.loading) {\n return this.renderLoading();\n }\n\n return (\n <Fragment>\n {this?.protocol?.cancel_behavior === 'pending' ? (\n <Fragment>\n {__('Your plan will be canceled, but is still available until the end of your billing period on', 'surecart')}{' '}\n <strong>{this?.subscription?.current_period_end_at_date}</strong>. {__('If you change your mind, you can renew your subscription.', 'surecart')}\n </Fragment>\n ) : (\n <Fragment>{__('Your plan will be canceled immediately and cannot be modified later.', 'surecart')}</Fragment>\n )}\n </Fragment>\n );\n }\n\n renderLoading() {\n return (\n <div style={{ padding: '0.5em' }}>\n <sc-skeleton style={{ width: '30%', marginBottom: '0.75em' }}></sc-skeleton>\n <sc-skeleton style={{ width: '20%', marginBottom: '0.75em' }}></sc-skeleton>\n <sc-skeleton style={{ width: '40%' }}></sc-skeleton>\n </div>\n );\n }\n\n render() {\n let heading = this.heading || __('Cancel your plan', 'surecart');\n let cancelButtonText = __('Cancel Plan', 'surecart');\n let keepButtonText = __('Keep My Plan', 'surecart');\n let content = this.renderContent();\n\n if (window?.wp?.hooks?.applyFilters) {\n heading = window.wp.hooks.applyFilters('surecart_dashboard_subscription_cancel_popup_heading', heading, this?.subscription) as string;\n cancelButtonText = window.wp.hooks.applyFilters('surecart_dashboard_subscription_cancel_popup_cancel_button_text', cancelButtonText, this?.subscription) as string;\n keepButtonText = window.wp.hooks.applyFilters('surecart_dashboard_subscription_cancel_popup_keep_button_text', keepButtonText, this?.subscription) as string;\n content = window.wp.hooks.applyFilters('surecart_dashboard_subscription_cancel_popup_content', content, this?.subscription);\n }\n\n return (\n <sc-dashboard-module heading={heading} class=\"subscription-cancel\" error={this.error} style={{ '--sc-dashboard-module-spacing': '1em' }}>\n <div slot=\"description\">\n {content}\n <slot name=\"cancel-popup-content\"></slot>\n </div>\n <sc-flex justifyContent=\"flex-start\">\n <sc-button type=\"primary\" loading={this.loading || this.busy} disabled={this.loading || this.busy} onClick={() => this.cancelSubscription()}>\n {cancelButtonText}\n </sc-button>\n\n <sc-button\n type=\"text\"\n onClick={() => this.scAbandon.emit()}\n loading={this.loading || this.busy}\n disabled={this.loading || this.busy}\n >\n {keepButtonText}\n </sc-button>\n </sc-flex>\n\n {this.busy && <sc-block-ui></sc-block-ui>}\n </sc-dashboard-module>\n );\n }\n}\n",":host {\n display: block;\n}\n.textarea {\n display: flex;\n align-items: center;\n position: relative;\n width: 100%;\n font-family: var(--sc-input-font-family);\n font-weight: var(--sc-input-font-weight);\n line-height: var(--sc-line-height-normal);\n letter-spacing: var(--sc-input-letter-spacing);\n vertical-align: middle;\n transition: var(--sc-input-transition, var(--sc-transition-medium)) color, var(--sc-input-transition, var(--sc-transition-medium)) border, var(--sc-input-transition, var(--sc-transition-medium)) box-shadow,\n var(--sc-input-transition, var(--sc-transition-medium)) background-color;\n cursor: text;\n}\n/* Standard textareas */\n.textarea--standard {\n background-color: var(--sc-input-background-color);\n border: solid var(--sc-input-border-width) var(--sc-input-border-color);\n}\n.textarea--standard:hover:not(.textarea--disabled) {\n background-color: var(--sc-input-background-color-hover);\n border-color: var(--sc-input-border-color-hover);\n}\n.textarea--standard:hover:not(.textarea--disabled) .textarea__control {\n color: var(--sc-input-color-hover);\n}\n.textarea--standard.textarea--focused:not(.textarea--disabled) {\n background-color: var(--sc-input-background-color-focus);\n border-color: var(--sc-input-border-color-focus);\n color: var(--sc-input-color-focus);\n box-shadow: 0 0 0 var(--sc-focus-ring-width) var(--sc-input-focus-ring-color);\n}\n.textarea--standard.textarea--focused:not(.textarea--disabled) .textarea__control {\n color: var(--sc-input-color-focus);\n}\n.textarea--standard.textarea--disabled {\n background-color: var(--sc-input-background-color-disabled);\n border-color: var(--sc-input-border-color-disabled);\n opacity: 0.5;\n cursor: not-allowed;\n}\n.textarea--standard.textarea--disabled .textarea__control {\n color: var(--sc-input-color-disabled);\n}\n.textarea--standard.textarea--disabled .textarea__control::placeholder {\n color: var(--sc-input-placeholder-color-disabled);\n}\n/* Filled textareas */\n.textarea--filled {\n border: none;\n background-color: var(--sc-input-filled-background-color);\n color: var(--sc-input-color);\n}\n.textarea--filled:hover:not(.textarea--disabled) {\n background-color: var(--sc-input-filled-background-color-hover);\n}\n.textarea--filled.textarea--focused:not(.textarea--disabled) {\n background-color: var(--sc-input-filled-background-color-focus);\n outline: var(--sc-focus-ring);\n outline-offset: var(--sc-focus-ring-offset);\n}\n.textarea--filled.textarea--disabled {\n background-color: var(--sc-input-filled-background-color-disabled);\n opacity: 0.5;\n cursor: not-allowed;\n}\n.textarea__control {\n flex: 1 1 auto;\n font-family: inherit;\n font-size: inherit;\n font-weight: inherit;\n line-height: 1.4;\n color: var(--sc-input-color);\n border: none;\n background: none;\n box-shadow: none;\n cursor: inherit;\n -webkit-appearance: none;\n}\n.textarea__control::-webkit-search-decoration,\n.textarea__control::-webkit-search-cancel-button,\n.textarea__control::-webkit-search-results-button,\n.textarea__control::-webkit-search-results-decoration {\n -webkit-appearance: none;\n}\n.textarea__control::placeholder {\n color: var(--sc-input-placeholder-color);\n user-select: none;\n}\n.textarea__control:focus {\n outline: none;\n}\n/*\n * Size modifiers\n */\n.textarea--small {\n border-radius: var(--sc-input-border-radius-small);\n font-size: var(--sc-input-font-size-small);\n}\n.textarea--small .textarea__control {\n padding: 0.5em var(--sc-input-spacing-small);\n}\n.textarea--medium {\n border-radius: var(--sc-input-border-radius-medium);\n font-size: var(--sc-input-font-size-medium);\n}\n.textarea--medium .textarea__control {\n padding: 0.5em var(--sc-input-spacing-medium);\n}\n.textarea--large {\n border-radius: var(--sc-input-border-radius-large);\n font-size: var(--sc-input-font-size-large);\n}\n.textarea--large .textarea__control {\n padding: 0.5em var(--sc-input-spacing-large);\n}\n/*\n * Resize types\n */\n.textarea--resize-none .textarea__control {\n resize: none;\n}\n.textarea--resize-vertical .textarea__control {\n resize: vertical;\n}\n.textarea--resize-auto .textarea__control {\n height: auto;\n resize: none;\n}\n\n.textarea__char-limit-warning{\n margin-top: var(--sc-input-spacing-small);\n color: var(--sc-input-help-text-color);\n font-size: var(--sc-input-help-text-font-size-medium);\n}\n","import { Component, h, State, Prop, Element, Watch, Event, EventEmitter, Method } from '@stencil/core';\nimport { sprintf, __ } from '@wordpress/i18n';\nimport { FormSubmitController } from '../../../functions/form-data';\n\nconst CHAR_LIMIT_THRESHOLD = 20;\nlet id = 0;\n\n/**\n * @part base - The elements base wrapper.\n * @part input - The html input element.\n * @part form-control - The form control wrapper.\n * @part label - The input label.\n * @part help-text - Help text that describes how to use the input.\n */\n@Component({\n tag: 'sc-textarea',\n styleUrl: 'sc-textarea.css',\n shadow: true,\n})\nexport class ScTextarea {\n private inputId: string = `textarea-${++id}`;\n private helpId = `textarea-help-text-${id}`;\n private labelId = `textarea-label-${id}`;\n\n @Element() el: HTMLScTextareaElement;\n\n private formController: any;\n\n private input: HTMLTextAreaElement;\n private resizeObserver: ResizeObserver;\n\n @State() private hasFocus = false;\n @State() private showCharLimit = false;\n\n /** The textarea's size. */\n @Prop({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';\n\n /** The textarea's name attribute. */\n @Prop() name: string;\n\n /** The textarea's value attribute. */\n @Prop() value = '';\n\n /** Draws a filled textarea. */\n @Prop({ reflect: true }) filled: boolean = false;\n\n /** The textarea's label. Alternatively, you can use the label slot. */\n @Prop() label = '';\n\n /** Should we show the label */\n @Prop() showLabel: boolean = true;\n\n /** The textarea's help text. Alternatively, you can use the help-text slot. */\n @Prop() help = '';\n\n /** The textarea's placeholder text. */\n @Prop() placeholder: string;\n\n /** The number of rows to display by default. */\n @Prop() rows: number = 4;\n\n /** Controls how the textarea can be resized. */\n @Prop() resize: 'none' | 'vertical' | 'auto' = 'vertical';\n\n /** Disables the textarea. */\n @Prop({ reflect: true }) disabled: boolean = false;\n\n /** Makes the textarea readonly. */\n @Prop({ reflect: true }) readonly: boolean = false;\n\n /** The minimum length of input that will be considered valid. */\n @Prop() minlength: number;\n\n /** The maximum length of input that will be considered valid. */\n @Prop() maxlength: number;\n\n /** Makes the textarea a required field. */\n @Prop({ reflect: true }) required: boolean = false;\n\n /**\n * This will be true when the control is in an invalid state. Validity is determined by props such as `type`,\n * `required`, `minlength`, and `maxlength` using the browser's constraint validation API.\n */\n @Prop({ reflect: true }) invalid: boolean = false;\n\n /** The textarea's autocapitalize attribute. */\n @Prop() autocapitalize: 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';\n\n /** The textarea's autocorrect attribute. */\n @Prop() autocorrect: string;\n\n /** The textarea's autocomplete attribute. */\n @Prop() autocomplete: string;\n\n /** The textarea's autofocus attribute. */\n @Prop() autofocus: boolean;\n\n /**\n * The input's enterkeyhint attribute. This can be used to customize the label or icon of the Enter key on virtual\n * keyboards.\n */\n @Prop() enterkeyhint: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';\n\n /** Enables spell checking on the textarea. */\n @Prop() spellcheck: boolean;\n\n /** The textarea's inputmode attribute. */\n @Prop() inputmode: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';\n\n @Event() scChange: EventEmitter<void>;\n @Event() scInput: EventEmitter<void>;\n @Event() scBlur: EventEmitter<void>;\n @Event() scFocus: EventEmitter<void>;\n\n @Watch('rows')\n handleRowsChange() {\n this.setTextareaHeight();\n }\n\n @Watch('value')\n handleValueChange() {\n this.invalid = !this.input.checkValidity();\n const valueLength = this.value?.length || 0;\n const maxLength = this.maxlength || Number.MAX_SAFE_INTEGER;\n this.showCharLimit = maxLength - valueLength <= CHAR_LIMIT_THRESHOLD;\n }\n\n @Watch('disabled')\n handleDisabledChange() {\n // Disabled form controls are always valid, so we need to recheck validity when the state changes\n this.input.disabled = this.disabled;\n this.invalid = !this.input.checkValidity();\n }\n\n /** Sets focus on the input. */\n @Method()\n async triggerFocus(options?: FocusOptions) {\n return this.input.focus(options);\n }\n\n /** Sets focus on the textarea. */\n focus(options?: FocusOptions) {\n this.input.focus(options);\n }\n\n /** Removes focus from the textarea. */\n blur() {\n this.input.blur();\n }\n\n /** Selects all the text in the textarea. */\n select() {\n this.input.select();\n }\n\n /** Gets or sets the textarea's scroll position. */\n scrollPosition(position?: { top?: number; left?: number }): { top: number; left: number } | undefined {\n if (position) {\n if (typeof position.top === 'number') this.input.scrollTop = position.top;\n if (typeof position.left === 'number') this.input.scrollLeft = position.left;\n return;\n }\n\n // eslint-disable-next-line consistent-return\n return {\n top: this.input.scrollTop,\n left: this.input.scrollTop,\n };\n }\n\n /** Sets the start and end positions of the text selection (0-based). */\n setSelectionRange(selectionStart: number, selectionEnd: number, selectionDirection: 'forward' | 'backward' | 'none' = 'none') {\n this.input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);\n }\n\n /** Replaces a range of text with a new string. */\n setRangeText(replacement: string, start: number, end: number, selectMode: 'select' | 'start' | 'end' | 'preserve' = 'preserve') {\n this.input.setRangeText(replacement, start, end, selectMode);\n\n if (this.value !== this.input.value) {\n this.value = this.input.value;\n this.scInput.emit();\n }\n\n if (this.value !== this.input.value) {\n this.value = this.input.value;\n this.setTextareaHeight();\n this.scInput.emit();\n this.scChange.emit();\n }\n }\n\n /** Checks for validity and shows the browser's validation message if the control is invalid. */\n @Method()\n async reportValidity() {\n return this.input.reportValidity();\n }\n\n /** Sets a custom validation message. If `message` is not empty, the field will be considered invalid. */\n setCustomValidity(message: string) {\n this.input.setCustomValidity(message);\n this.invalid = !this.input.checkValidity();\n }\n\n handleBlur() {\n this.hasFocus = false;\n this.scBlur.emit();\n }\n\n handleChange() {\n this.value = this.input.value;\n this.setTextareaHeight();\n this.scChange.emit();\n }\n\n handleFocus() {\n this.hasFocus = true;\n this.scFocus.emit();\n }\n\n handleInput() {\n this.value = this.input.value;\n this.setTextareaHeight();\n this.scInput.emit();\n }\n\n componentWillLoad() {\n if (!window?.ResizeObserver) {\n return;\n }\n this.resizeObserver = new window.ResizeObserver(() => this.setTextareaHeight());\n }\n\n componentDidLoad() {\n this.formController = new FormSubmitController(this.el).addFormData();\n if (!window?.ResizeObserver) {\n return;\n }\n this.resizeObserver.observe(this.input);\n }\n\n disconnectedCallback() {\n this.formController?.removeFormData();\n this.resizeObserver.unobserve(this.input);\n }\n\n setTextareaHeight() {\n if (this.resize === 'auto') {\n this.input.style.height = 'auto';\n this.input.style.height = `${this.input.scrollHeight}px`;\n } else {\n (this.input.style.height as string | undefined) = undefined;\n }\n }\n\n render() {\n return (\n <div\n part=\"form-control\"\n class={{\n 'form-control': true,\n 'form-control--small': this.size === 'small',\n 'form-control--medium': this.size === 'medium',\n 'form-control--large': this.size === 'large',\n }}\n >\n <sc-form-control\n exportparts=\"label, help-text, form-control\"\n size={this.size}\n required={this.required}\n label={this.label}\n showLabel={this.showLabel}\n help={this.help}\n inputId={this.inputId}\n helpId={this.helpId}\n labelId={this.labelId}\n name={this.name}\n >\n <div part=\"form-control-input\" class=\"form-control-input\">\n <div\n part=\"base\"\n class={{\n 'textarea': true,\n 'textarea--small': this.size === 'small',\n 'textarea--medium': this.size === 'medium',\n 'textarea--large': this.size === 'large',\n 'textarea--standard': !this.filled,\n 'textarea--filled': this.filled,\n 'textarea--disabled': this.disabled,\n 'textarea--focused': this.hasFocus,\n 'textarea--empty': !this.value,\n 'textarea--invalid': this.invalid,\n 'textarea--resize-none': this.resize === 'none',\n 'textarea--resize-vertical': this.resize === 'vertical',\n 'textarea--resize-auto': this.resize === 'auto',\n }}\n >\n <textarea\n part=\"textarea\"\n ref={el => (this.input = el as HTMLTextAreaElement)}\n id=\"input\"\n class=\"textarea__control\"\n name={this.name}\n value={this.value}\n disabled={this.disabled}\n readonly={this.readonly}\n required={this.required}\n placeholder={this.placeholder}\n rows={this.rows}\n minlength={this.minlength}\n maxlength={this.maxlength}\n autocapitalize={this.autocapitalize}\n autocorrect={this.autocorrect}\n autofocus={this.autofocus}\n spellcheck={this.spellcheck}\n enterkeyhint={this.enterkeyhint}\n inputmode={this.inputmode}\n aria-describedby=\"help-text\"\n onChange={() => this.handleChange()}\n onInput={() => this.handleInput()}\n onFocus={() => this.handleFocus()}\n onBlur={() => this.handleBlur()}\n onKeyDown={(e) => e.stopPropagation()}\n ></textarea>\n </div>\n {this.showCharLimit && (\n <div slot=\"help\" class={'textarea__char-limit-warning'}>\n {sprintf(__('%d characters remaining', 'surecart'), this.maxlength && this.input?.value ? this.maxlength - this.input.value.length : this.maxlength)}\n </div>\n )}\n </div>\n </sc-form-control>\n </div>\n );\n }\n}\n"],"version":3}