JezK
Edit File: sc-address2.js.map
{"file":"sc-address2.js","mappings":";;;;;;;;;;;;;;;;;;AAAA,MAAM,YAAY,GAAG,oiCAAoiC,CAAC;AAC1jC,wBAAe,YAAY;;ACM3B;;;;eAIW,UAAE;;;;;eAKF,UAAE;;;;;eAKF,UAAE;;;;;eAKF,UAAE;;;;;eAKF,UAAE;;;;;eAKF,UAAE;;;;;eAKF,UAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;g7BA0XwD,UAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","names":[],"sources":["src/components/ui/address/sc-address.scss?tag=sc-address&encapsulation=shadow","src/components/ui/address/sc-address.tsx"],"sourcesContent":[":host {\n display: block;\n}\n\n.sc-address {\n display: block;\n position: relative;\n\n [hidden] {\n display: none;\n }\n\n &--loading {\n min-height: 230px;\n }\n\n sc-skeleton {\n display: block;\n margin-bottom: 1em;\n }\n\n &__control {\n display: block;\n\n > * {\n margin-bottom: var(--sc-address-column-spacing, -1px);\n }\n }\n\n &__collapsible {\n // Stripe-like technique: fields stay in the DOM so browser autocomplete\n // can detect and fill them, but they are visually hidden and non-interactive.\n opacity: 0;\n pointer-events: none;\n position: absolute;\n transform: translateY(-8px);\n width: 100%;\n\n > * {\n margin-bottom: var(--sc-address-column-spacing, -1px);\n }\n\n &--expanded {\n opacity: 1;\n pointer-events: auto;\n position: relative;\n transform: translateY(0);\n transition: opacity 0.2s ease-in, transform 0.2s ease-in;\n z-index: var(--sc-address-collapsible-z-index, 9);\n }\n\n &:focus-within {\n z-index: var(--sc-address-collapsible-focus-z-index, 10);\n }\n }\n\n &__columns {\n display: flex;\n flex-direction: row;\n align-items: center;\n flex-wrap: wrap;\n justify-content: space-between;\n\n > * {\n flex: 1;\n width: 50%;\n margin-right: var(--sc-address-column-spacing, -1px);\n\n &:last-child {\n margin-right: 0;\n }\n }\n }\n}\n","import { Component, Element, Event, EventEmitter, h, Method, Prop, State, Watch } from '@stencil/core';\nimport { __ } from '@wordpress/i18n';\nimport { countryChoices, getCountryDetails } from '../../../functions/address';\nimport { reportChildrenValidity } from '../../../functions/form-data';\nimport { getCurrentUserCountryCode } from '../../../functions/google-maps';\nimport { Address, CountryLocaleFieldValue } from '../../../types';\n\nconst DEFAULT_COUNTRY_FIELDS: Array<CountryLocaleFieldValue> = [\n {\n name: 'country',\n priority: 30,\n label: __('Country', 'surecart'),\n },\n {\n name: 'name',\n priority: 40,\n label: __('Name or Company Name', 'surecart'),\n },\n {\n name: 'line_1',\n priority: 50,\n label: __('Address', 'surecart'),\n },\n {\n name: 'line_2',\n priority: 60,\n label: __('Line 2', 'surecart'),\n },\n {\n name: 'city',\n priority: 70,\n label: __('City', 'surecart'),\n },\n {\n name: 'state',\n priority: 80,\n label: __('State / County', 'surecart'),\n },\n {\n name: 'postal_code',\n priority: 90,\n label: __('Postal Code', 'surecart'),\n },\n];\n\n/**\n * @part base - The elements base wrapper.\n * @part input__base - The inputs base element.\n * @part select__base - The select boxes base element.\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 * @part trigger - The select box trigger.\n * @part panel - The select box panel.\n * @part caret - The select box caret.\n * @part search__base - The select search base.\n * @part search__input - The select search input.\n * @part search__form-control - The select search form control.\n * @part menu__base - The select menu base.\n * @part spinner__base - The select spinner base.\n * @part empty - The select empty message.\n * @part block-ui - The block ui base component.\n * @part block-ui__content - The block ui content (spinner).\n */\n@Component({\n tag: 'sc-address',\n styleUrl: 'sc-address.scss',\n shadow: true,\n})\nexport class ScAddress {\n @Element() el: HTMLScAddressElement;\n\n /** The address. */\n @Prop({ mutable: true }) address: Partial<Address> = {\n country: null,\n city: null,\n line_1: null,\n line_2: null,\n postal_code: null,\n state: null,\n };\n\n @Prop() names: Partial<Address> = {\n name: 'shipping_name',\n country: 'shipping_country',\n city: 'shipping_city',\n line_1: 'shipping_line_1',\n line_2: 'shipping_line_2',\n postal_code: 'shipping_postal_code',\n state: 'shipping_state',\n };\n\n /** Is this loading? */\n @Prop() loading: boolean = false;\n\n /** Is this disabled? */\n @Prop() disabled: boolean;\n\n /** The label for the field. */\n @Prop() label: string;\n\n /** Should we show name field? */\n @Prop({ reflect: true, mutable: true }) showName: boolean;\n\n /** Should we show name field? */\n @Prop() showLine2: boolean;\n\n /** Is this required? */\n @Prop({ reflect: true }) required: boolean = false;\n\n /** Is the name required */\n @Prop({ reflect: true }) requireName: boolean = false;\n\n /** Should we show the city field? */\n @State() showCity: boolean = true;\n\n /** Should we show the postal field? */\n @State() showPostal: boolean = true;\n\n /** Should we show the state field? */\n @State() showState: boolean = true;\n\n /** Country details. */\n @State() countryDetails: any = null;\n\n /** Holds our country choices. */\n @State() countryChoices: Array<{ value: string; label: string }>;\n\n /** Address change event. */\n @Event() scChangeAddress: EventEmitter<Partial<Address>>;\n\n /** Address change event. */\n @Event() scInputAddress: EventEmitter<Partial<Address>>;\n\n /** When the state changes, we want to update city and postal fields. */\n @Watch('address')\n async handleAddressChange() {\n if (!this.address?.country) return;\n if (!this.countryDetails || this.countryDetails?.code !== this.address.country) {\n this.countryDetails = await getCountryDetails(this.address.country);\n }\n this.scChangeAddress.emit(this.address);\n this.scInputAddress.emit(this.address);\n }\n\n @Watch('requireName')\n handleNameChange() {\n if (this.requireName) {\n this.showName = true;\n }\n }\n\n decodeHtmlEntities(html: string) {\n return new DOMParser().parseFromString(html, 'text/html')?.body.textContent || html;\n }\n\n updateAddress(address: Partial<Address>) {\n this.address = { ...this.address, ...address };\n }\n\n handleAddressInput(address: Partial<Address>) {\n this.scInputAddress.emit({ ...this.address, ...address });\n }\n\n clearAddress() {\n // Only reset fields whose format depends on country (region list, postal code pattern).\n // Keep line_1/line_2 — they're free text, and clearing them silently discards what the buyer already typed.\n this.address = {\n name: this.address?.name,\n line_1: this.address?.line_1,\n line_2: this.address?.line_2,\n country: null,\n city: null,\n postal_code: null,\n state: null,\n };\n }\n\n componentWillLoad() {\n // Start collapsed when Google Maps is active — the child component\n // will emit show/hide events to control this going forward.\n if (window?.scData?.google_map_api_key) {\n this.toggleAddressFieldsVisibility(false);\n }\n\n this.initCountryChoices();\n this.handleAddressChange();\n this.handleNameChange();\n this.fetchUserCountry();\n }\n\n async fetchUserCountry() {\n if (this.address?.country) {\n return;\n }\n\n const countryCode = await getCurrentUserCountryCode();\n if (countryCode) {\n this.updateAddress({ country: countryCode });\n }\n }\n\n @Method()\n async initCountryChoices() {\n this.countryChoices = await countryChoices();\n const country = this.countryChoices?.find(country => country.value === this.address?.country)?.value || null;\n this.updateAddress({ country });\n }\n\n @Method()\n async reportValidity() {\n // Collapsed city/state/postal fields render as not-required (see isFieldIncluded/render) so they don't\n // block submission while hidden. Expand them first so a required address can't be bypassed by leaving\n // line_1 empty (fields never having shown) or clearing it after the fact.\n if (this.required && !this.isFieldsExpanded()) {\n this.toggleAddressFieldsVisibility(true);\n await new Promise(resolve => requestAnimationFrame(resolve));\n }\n\n return reportChildrenValidity(this.el);\n }\n\n sortedFields() {\n if (!this.countryDetails || !this?.address?.country) {\n return DEFAULT_COUNTRY_FIELDS;\n }\n\n return (this?.countryDetails?.address_formats?.edit\n ?.match(/{{([^}]+)}}/g)\n .map(match => match.slice(2, -2))\n .map(field => ({\n name: field,\n label: this?.countryDetails?.address_labels?.[field] || DEFAULT_COUNTRY_FIELDS?.find(defaultField => defaultField?.name === field)?.label,\n })) || []) as CountryLocaleFieldValue[];\n }\n\n regions() {\n let regions =\n this?.countryDetails?.states?.map(state => ({\n value: state?.code,\n label: state?.name,\n })) || [];\n\n if (window?.wp?.hooks?.applyFilters) {\n regions = window.wp.hooks.applyFilters('surecart_address_regions', regions, this?.address?.country) as Array<{ value: string; label: string }>;\n }\n\n return regions as Array<{ value: string; label: string }>;\n }\n\n toggleAddressFieldsVisibility(show: boolean) {\n this.showCity = show;\n this.showPostal = show;\n this.showState = show;\n }\n\n getRoundedProps(index: number, length: number) {\n const isFirst = index === 0;\n const isLast = index === length - 1;\n\n return {\n squaredTop: isLast,\n squaredBottom: isFirst,\n squared: !isLast && !isFirst,\n };\n }\n\n /** Whether the collapsible fields are expanded.\n * When Google Maps is off, showCity stays at its default (true) and fields are always expanded.\n * When Google Maps is on, the child toggles this via show/hide events. */\n isFieldsExpanded(): boolean {\n return this.showCity;\n }\n\n /** Names of fields that collapse when Google Maps autocomplete is active. */\n private collapsibleFieldNames = ['line_2', 'city', 'state', 'postal_code'];\n\n /** Whether a field should be included in the render. */\n isFieldIncluded(field: CountryLocaleFieldValue): boolean {\n switch (field.name) {\n case 'name':\n return !!this.showName;\n case 'state':\n return !!this?.regions()?.length && !!this?.address?.country;\n case 'line_2':\n // When collapsed, always include so browser autocomplete can fill it (CSS hides it).\n // When expanded, respect the showLine2 prop from settings.\n if (!this.isFieldsExpanded()) return true;\n return this.showLine2 || !!this?.address?.line_2?.length;\n default:\n return true;\n }\n }\n\n renderField(field: CountryLocaleFieldValue, roundedProps: any, isRequired: boolean) {\n switch (field.name) {\n case 'country':\n return (\n <sc-select\n exportparts=\"base:select__base, input, form-control, label, help-text, trigger, panel, caret, search__base, search__input, search__form-control, menu__base, spinner__base, empty\"\n part=\"name__input\"\n value={this.address?.country ?? ''}\n onScChange={(e: any) => {\n if (e.target.value === this.address?.country) return;\n this.clearAddress();\n this.updateAddress({ country: e.target.value });\n }}\n choices={this.countryChoices}\n autocomplete={'country-name'}\n placeholder={field.label}\n name={this.names?.country}\n search\n unselect={false}\n disabled={this.disabled}\n required={isRequired}\n aria-label={field.label}\n {...roundedProps}\n />\n );\n\n case 'name':\n return (\n <sc-input\n exportparts=\"base:input__base, input, form-control, label, help-text\"\n value={this?.address?.name ?? ''}\n onScChange={(e: any) => this.updateAddress({ name: e.target.value || null })}\n onScInput={(e: any) => this.handleAddressInput({ name: e.target.value || null })}\n autocomplete=\"name\"\n placeholder={field.label}\n name={this.names?.name}\n disabled={this.disabled}\n required={this.requireName}\n aria-label={field.label}\n {...roundedProps}\n />\n );\n\n case 'line_1':\n return (\n <sc-address-suggestions\n address={this.address}\n names={this.names}\n regions={this.regions()}\n label={field.label}\n disabled={this.disabled}\n required={isRequired}\n inputProps={roundedProps}\n onScChangeAddress={(e: any) => this.updateAddress(e.detail)}\n onScShowAddressFields={() => this.toggleAddressFieldsVisibility(true)}\n onScHideAddressFields={() => this.toggleAddressFieldsVisibility(false)}\n />\n );\n\n case 'line_2':\n return (\n <sc-input\n exportparts=\"base:input__base, input, form-control, label, help-text\"\n value={this?.address?.line_2 ?? ''}\n onScChange={(e: any) => this.updateAddress({ line_2: e.target.value || null })}\n onScInput={(e: any) => this.handleAddressInput({ line_2: e.target.value || null })}\n autocomplete=\"address-line2\"\n placeholder={field.label}\n name={this.names?.line_2}\n disabled={this.disabled}\n aria-label={field.label}\n {...roundedProps}\n />\n );\n\n case 'city':\n return (\n <sc-input\n exportparts=\"base:input__base, input, form-control, label, help-text\"\n placeholder={field.label}\n name={this.names?.city}\n value={this?.address?.city ?? ''}\n onScChange={(e: any) => this.updateAddress({ city: e.target.value || null })}\n onScInput={(e: any) => this.handleAddressInput({ city: e.target.value || null })}\n autocomplete={'address-level2'}\n required={isRequired}\n disabled={this.disabled}\n aria-label={field.label}\n {...roundedProps}\n />\n );\n\n case 'state':\n return (\n <sc-select\n exportparts=\"base:select__base, input, form-control, label, help-text, trigger, panel, caret, search__base, search__input, search__form-control, menu__base, spinner__base, empty\"\n placeholder={field.label}\n name={this.names?.state}\n autocomplete={'address-level1'}\n value={this?.address?.state ?? ''}\n onScChange={(e: any) => this.updateAddress({ state: e.target.value || e.detail?.value || null })}\n choices={this.regions()}\n required={isRequired}\n disabled={this.disabled}\n search\n aria-label={field.label}\n {...roundedProps}\n />\n );\n\n case 'postal_code':\n return (\n <sc-input\n exportparts=\"base:input__base, input, form-control, label, help-text\"\n placeholder={field.label}\n name={this.names?.postal_code}\n onScChange={(e: any) => this.updateAddress({ postal_code: e.target.value || null })}\n onScInput={(e: any) => this.handleAddressInput({ postal_code: e.target.value || null })}\n autocomplete={'postal-code'}\n required={isRequired}\n value={this?.address?.postal_code ?? ''}\n disabled={this.disabled}\n maxlength={this.address?.country === 'US' ? 5 : undefined}\n pattern={this.countryDetails?.postal_code_regex || undefined}\n customValidity={this.countryDetails?.postal_code_regex ? __('Please enter a valid postal code', 'surecart') : undefined}\n aria-label={field.label}\n {...roundedProps}\n />\n );\n\n default:\n return null;\n }\n }\n\n render() {\n const allFields = (this.sortedFields() ?? []).filter(field => this.isFieldIncluded(field));\n const isExpanded = this.isFieldsExpanded();\n\n // Split fields into always-visible (country, name, line_1) and collapsible (line_2, city, state, postal_code).\n const topFields = allFields.filter(f => !this.collapsibleFieldNames.includes(f.name));\n const bottomFields = allFields.filter(f => this.collapsibleFieldNames.includes(f.name));\n\n // Compute rounded props based on visible field count.\n const totalForRounding = isExpanded ? allFields.length : topFields.length;\n\n return (\n <div class=\"sc-address\" part=\"base\">\n <sc-form-control label={this.label} exportparts=\"label, help-text, form-control\" class=\"sc-address__control\" required={this.required}>\n {topFields.map((field: any, index: number) => {\n const roundedProps = this.getRoundedProps(index, totalForRounding);\n return this.renderField(field, roundedProps, this.required);\n })}\n\n <div\n class={{\n 'sc-address__collapsible': true,\n 'sc-address__collapsible--expanded': isExpanded,\n }}\n aria-hidden={!isExpanded ? 'true' : 'false'}\n >\n {bottomFields.map((field: any, index: number) => {\n const globalIndex = topFields.length + index;\n const roundedProps = this.getRoundedProps(globalIndex, allFields.length);\n // Don't require fields when collapsed — prevents hidden required validation errors.\n const isRequired = this.required && isExpanded;\n return this.renderField(field, roundedProps, isRequired);\n })}\n </div>\n </sc-form-control>\n\n {this.loading && <sc-block-ui exportparts=\"base:block-ui, content:block-ui__content\"></sc-block-ui>}\n </div>\n );\n }\n}\n"],"version":3}