From d5ef2a846be7677f33b219e2d3ae767e51e0c6e7 Mon Sep 17 00:00:00 2001 From: "e.kireev" Date: Tue, 16 Apr 2024 16:44:45 +0300 Subject: [PATCH] feat(PAYMENTS-18330): form loaded event --- src/core/form/form-loader.ts | 61 +++++++++++++++++++ .../web-components/web-component.abstract.ts | 22 +++---- .../headless-checkout/headless-checkout.ts | 4 ++ .../apple-pay/apple-pay.component.ts | 13 ---- .../checkbox/checkbox.component.spec.ts | 10 ++- .../checkbox/checkbox.component.ts | 2 +- .../payment-form.component.spec.ts | 10 ++- .../payment-form/payment-form.component.ts | 11 +++- .../payment-methods.component.ts | 3 - .../qr-code/qr-code.component.ts | 23 ++++--- .../saved-methods/saved-methods.component.ts | 2 - .../select/select.component.spec.ts | 10 +++ .../web-components/select/select.component.ts | 2 +- .../text-component/text.component.spec.ts | 47 ++------------ .../text-component/text.component.ts | 21 +++---- .../dispatch-finish-loading-event.function.ts | 13 ---- .../is-loading-css-class-name.const.ts | 1 - 17 files changed, 141 insertions(+), 114 deletions(-) create mode 100644 src/core/form/form-loader.ts delete mode 100644 src/shared/loading-state/dispatch-finish-loading-event.function.ts delete mode 100644 src/shared/loading-state/is-loading-css-class-name.const.ts diff --git a/src/core/form/form-loader.ts b/src/core/form/form-loader.ts new file mode 100644 index 0000000..24ecdf9 --- /dev/null +++ b/src/core/form/form-loader.ts @@ -0,0 +1,61 @@ +import { singleton } from 'tsyringe'; +import { Field } from './field.interface'; + +@singleton() +export class FormLoader { + private _fields!: { [key: string]: boolean }; + private _isAllFieldsLoaded!: Promise; + + private _resolve!: () => void; + private _isPromiseResolved = false; + + public async setupFieldsToTrackLoading(fields: Field[]): Promise { + this._isAllFieldsLoaded = new Promise((resolve) => { + this._resolve = () => { + resolve(); + }; + }); + + this._isPromiseResolved = false; + this._fields = {}; + + const filteredFields = this.filterFields(fields); + + if (!filteredFields.length) { + this._resolve(); + this._isPromiseResolved = true; + } + + filteredFields.forEach((field) => { + this._fields[field.name] = false; + }); + + return this._isAllFieldsLoaded; + } + + public setFieldLoaded(name: string): void { + if (name in this._fields) { + this._fields[name] = true; + } + + if (this.isAllFieldsLoaded && !this._isPromiseResolved) { + this._resolve(); + this._isPromiseResolved = true; + } + } + + private get isAllFieldsLoaded(): boolean { + return Object.values(this._fields).every((value) => value); + } + + private filterFields(fields: Field[]): Field[] { + return fields.filter((field) => { + const isTextControl = field.type === 'text'; + const isCheckboxControl = field.type === 'check'; + const isSelectControl = field.type === 'select'; + const isQrCode = field.name === 'qr'; + + return isTextControl || isCheckboxControl || isSelectControl || isQrCode; + }); + } +} diff --git a/src/core/web-components/web-component.abstract.ts b/src/core/web-components/web-component.abstract.ts index 22c5acd..98966d3 100644 --- a/src/core/web-components/web-component.abstract.ts +++ b/src/core/web-components/web-component.abstract.ts @@ -1,14 +1,21 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { isLoadingCssClassName } from '../../shared/loading-state/is-loading-css-class-name.const'; -import { createFinishLoadingEvent } from '../../shared/loading-state/dispatch-finish-loading-event.function'; +import { FormLoader } from '../form/form-loader'; +import { container } from 'tsyringe'; export abstract class WebComponentAbstract extends HTMLElement { + protected formLoader!: FormLoader; + protected eventListeners: Array<{ element: Element; eventType: string; listener(event: Event): void; }> = []; + public constructor() { + super(); + this.formLoader = container.resolve(FormLoader); + } + protected abstract getHtml(): string; protected connectedCallback(): void { @@ -75,16 +82,7 @@ export abstract class WebComponentAbstract extends HTMLElement { } } - protected startLoadingComponentHandler(): void { - this.classList.add(isLoadingCssClassName); - } - protected finishLoadingComponentHandler(componentName: string): void { - this.classList.remove(isLoadingCssClassName); - this.dispatchFinishLoadingEvent(componentName); - } - - protected dispatchFinishLoadingEvent(componentName: string): void { - this.dispatchEvent(createFinishLoadingEvent(componentName)); + this.formLoader.setFieldLoaded(componentName); } } diff --git a/src/features/headless-checkout/headless-checkout.ts b/src/features/headless-checkout/headless-checkout.ts index f1369c4..e4fa774 100644 --- a/src/features/headless-checkout/headless-checkout.ts +++ b/src/features/headless-checkout/headless-checkout.ts @@ -38,6 +38,7 @@ import { Themes } from '../../core/customization/themes.type'; import { Lang } from '../../core/i18n/lang.enum'; import { getCountryListHandler } from './post-messages-handlers/get-country-list.handler'; import { CountryResponse } from '../../core/country-response.interface'; +import { FormLoader } from '../../core/form/form-loader'; @singleton() export class HeadlessCheckout { @@ -137,6 +138,8 @@ export class HeadlessCheckout { activate: (): void => { this.formStatus = FormStatus.active; }, + setupFieldsToTrackLoading: async (fields: Field[]): Promise => + this.formLoader.setupFieldsToTrackLoading(fields), }; public get formConfiguration(): FormConfiguration | undefined { @@ -159,6 +162,7 @@ export class HeadlessCheckout { private readonly headlessCheckoutSpy: HeadlessCheckoutSpy, private readonly formSpy: FormSpy, private readonly themesLoader: ThemesLoader, + private readonly formLoader: FormLoader, ) {} public async init(environment: { diff --git a/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.ts b/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.ts index b5084f6..8c6195b 100644 --- a/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.ts +++ b/src/features/headless-checkout/web-components/apple-pay/apple-pay.component.ts @@ -8,7 +8,6 @@ import { errorsHtmlWrapperClassName } from './errors-html-wrapper-classname.cons import { applePayErrors } from './apple-pay.errors.const'; import i18next from 'i18next'; import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; -import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler'; import { headlessCheckoutAppUrl } from '../../environment'; import { openApplePayPageHandler } from '../../post-messages-handlers/apple-pay/open-apple-pay-page.handler'; import { Message } from '../../../../core/message.interface'; @@ -59,21 +58,9 @@ export class ApplePayComponent extends SecureComponentAbstract { } }, ); - - this.headlessCheckout.events.onCoreEvent( - EventName.finishLoadComponent, - finishLoadComponentHandler, - (res) => { - if (res?.fieldName && res?.fieldName === this.componentName) { - this.finishLoadingComponentHandler(this.componentName); - } - }, - ); } protected connectedCallback(): void { - this.startLoadingComponentHandler(); - if (!this.formSpy.formWasInit) { this.formSpy.listenFormInit(() => this.connectedCallback()); return; diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox.component.spec.ts b/src/features/headless-checkout/web-components/checkbox/checkbox.component.spec.ts index 24d1cc5..085ca83 100644 --- a/src/features/headless-checkout/web-components/checkbox/checkbox.component.spec.ts +++ b/src/features/headless-checkout/web-components/checkbox/checkbox.component.spec.ts @@ -7,6 +7,7 @@ import { CheckboxComponent } from './checkbox.component'; import { CheckboxComponentConfig } from './checkbox-component-config.interface'; import { XpsBoolean } from '../../../../core/xps-boolean.enum'; import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { FormLoader } from '../../../../core/form/form-loader'; const config: CheckboxComponentConfig = { name: 'test', @@ -29,6 +30,7 @@ describe('CheckboxComponent', () => { let headlessCheckout: HeadlessCheckout; let postMessagesClient: PostMessagesClient; let windowService: Window; + let formLoader: FormLoader; window.customElements.define( WebComponentTagName.CheckboxComponent, @@ -48,6 +50,11 @@ describe('CheckboxComponent', () => { windowService = window; + formLoader = { + setupFieldsToTrackLoading: noopStub, + setFieldLoaded: noopStub, + } as unknown as FormLoader; + container.clearInstances(); container @@ -64,7 +71,8 @@ describe('CheckboxComponent', () => { }, } as FormSpy, }) - .register(Window, { useValue: windowService }); + .register(Window, { useValue: windowService }) + .register(FormLoader, { useValue: formLoader }); }); afterEach(() => { diff --git a/src/features/headless-checkout/web-components/checkbox/checkbox.component.ts b/src/features/headless-checkout/web-components/checkbox/checkbox.component.ts index 9c9a2ca..7c87d65 100644 --- a/src/features/headless-checkout/web-components/checkbox/checkbox.component.ts +++ b/src/features/headless-checkout/web-components/checkbox/checkbox.component.ts @@ -40,11 +40,11 @@ export class CheckboxComponent extends BaseControl { void this.getComponentConfig(this.controlName).then((config) => { this.config = config as CheckboxComponentConfig; - this.render(); this.addEventListenerToElement(this.inputRef, 'change', (event: Event) => this.notifyOnValueChanges(event), ); + this.finishLoadingComponentHandler(this.controlName); }); } diff --git a/src/features/headless-checkout/web-components/payment-form/payment-form.component.spec.ts b/src/features/headless-checkout/web-components/payment-form/payment-form.component.spec.ts index 441d4de..db1a246 100644 --- a/src/features/headless-checkout/web-components/payment-form/payment-form.component.spec.ts +++ b/src/features/headless-checkout/web-components/payment-form/payment-form.component.spec.ts @@ -8,6 +8,7 @@ import { PaymentFormComponent } from './payment-form.component'; import { Field } from '../../../../core/form/field.interface'; import { PostMessagesClient } from '../../../../core/post-messages-client/post-messages-client'; import { FieldSettings } from './field-settings.interface'; +import { FormLoader } from '../../../../core/form/form-loader'; function createComponent(): void { const element = document.createElement( @@ -47,6 +48,7 @@ describe('PaymentFormComponent', () => { let paymentFormFieldsManager: PaymentFormFieldsService; let postMessagesClient: PostMessagesClient; let windowService: Window; + let formLoader: FormLoader; window.customElements.define( WebComponentTagName.PaymentFormComponent, @@ -77,6 +79,11 @@ describe('PaymentFormComponent', () => { }, } as unknown as FormSpy; + formLoader = { + setupFieldsToTrackLoading: noopStub, + setFieldLoaded: noopStub, + } as unknown as FormLoader; + paymentFormFieldsManager = { createMissedFields: noopStub, removeExtraFields: noopStub, @@ -104,7 +111,8 @@ describe('PaymentFormComponent', () => { .register(PostMessagesClient, { useValue: postMessagesClient, }) - .register(Window, { useValue: windowService }); + .register(Window, { useValue: windowService }) + .register(FormLoader, { useValue: formLoader }); }); afterEach(() => { diff --git a/src/features/headless-checkout/web-components/payment-form/payment-form.component.ts b/src/features/headless-checkout/web-components/payment-form/payment-form.component.ts index 2bfc3c5..0a671fe 100644 --- a/src/features/headless-checkout/web-components/payment-form/payment-form.component.ts +++ b/src/features/headless-checkout/web-components/payment-form/payment-form.component.ts @@ -36,12 +36,15 @@ export class PaymentFormComponent extends WebComponentAbstract { return; } const formExpectedFields = this.formSpy.formFields; - const formRequriedFields = this.getRequriedFields(this.formSpy.formFields); + + const formRequiredFields = this.getRequriedFields(this.formSpy.formFields); super.render(); if (formExpectedFields) { + this.setupFormLoader(formRequiredFields); + const expectedFields = this.getFieldsSettings(formExpectedFields); - const requiredFields = this.getFieldsSettings(formRequriedFields); + const requiredFields = this.getFieldsSettings(formRequiredFields); const existsControls = this.getExistsControls(); this.setupFormFields(expectedFields, requiredFields, existsControls); @@ -150,4 +153,8 @@ export class PaymentFormComponent extends WebComponentAbstract { () => null, ); } + + private setupFormLoader(fields: Field[]): void { + void this.formLoader.setupFieldsToTrackLoading(fields); + } } diff --git a/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.ts b/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.ts index c7cacea..dc7796d 100644 --- a/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.ts +++ b/src/features/headless-checkout/web-components/payment-methods/payment-methods.component.ts @@ -48,8 +48,6 @@ export class PaymentMethodsComponent extends WebComponentAbstract { } protected connectedCallback(): void { - this.startLoadingComponentHandler(); - if (!this.headlessCheckoutSpy.appWasInit) { this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); return; @@ -103,7 +101,6 @@ export class PaymentMethodsComponent extends WebComponentAbstract { this.filteredMethods = this.paymentMethods.slice(); super.render(); - this.finishLoadingComponentHandler('payment-methods'); this.listenClicks(); this.setupSearch(); }; diff --git a/src/features/headless-checkout/web-components/qr-code/qr-code.component.ts b/src/features/headless-checkout/web-components/qr-code/qr-code.component.ts index f9147ef..5b2120f 100644 --- a/src/features/headless-checkout/web-components/qr-code/qr-code.component.ts +++ b/src/features/headless-checkout/web-components/qr-code/qr-code.component.ts @@ -1,12 +1,11 @@ import { SecureComponentAbstract } from '../../../../core/web-components/secure-component/secure-component.abstract'; -import { EventName } from '../../../../core/event-name.enum'; -import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler'; import { container } from 'tsyringe'; import { HeadlessCheckout } from '../../headless-checkout'; import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; export class QrCodeComponent extends SecureComponentAbstract { protected componentName = 'qr-code'; + protected inputName = 'qr'; private readonly headlessCheckout: HeadlessCheckout; private readonly formSpy: FormSpy; @@ -17,22 +16,26 @@ export class QrCodeComponent extends SecureComponentAbstract { } protected connectedCallback(): void { - this.startLoadingComponentHandler(); - if (!this.formSpy.formWasInit) { this.formSpy.listenFormInit(() => this.connectedCallback()); + return; } - this.headlessCheckout.events.onCoreEvent( - EventName.finishLoadComponent, - finishLoadComponentHandler, - () => this.finishLoadingComponentHandler('qr-code'), - ); - super.connectedCallback(); + this.addEventListeners(); } protected getHtml(): string { return this.getSecureHtml(); } + + private addEventListeners(): void { + this.addEventListenerToElement( + this.querySelector('iframe') as unknown as HTMLIFrameElement, + 'load', + () => { + this.finishLoadingComponentHandler(this.inputName); + }, + ); + } } diff --git a/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.ts b/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.ts index 1985b27..e749034 100644 --- a/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.ts +++ b/src/features/headless-checkout/web-components/saved-methods/saved-methods.component.ts @@ -70,7 +70,6 @@ export class SavedMethodsComponent extends WebComponentAbstract { } protected connectedCallback(): void { - this.startLoadingComponentHandler(); if (!this.headlessCheckoutSpy.appWasInit) { this.headlessCheckoutSpy.listenAppInit(() => this.connectedCallback()); return; @@ -98,7 +97,6 @@ export class SavedMethodsComponent extends WebComponentAbstract { ): void => { this.savedMethods = savedMethods; super.render(); - this.finishLoadingComponentHandler('saved-methods'); this.listenClicks(); }; diff --git a/src/features/headless-checkout/web-components/select/select.component.spec.ts b/src/features/headless-checkout/web-components/select/select.component.spec.ts index 421824f..379028f 100644 --- a/src/features/headless-checkout/web-components/select/select.component.spec.ts +++ b/src/features/headless-checkout/web-components/select/select.component.spec.ts @@ -7,6 +7,7 @@ import { SelectAttributes } from './select-attributes.enum'; import { SelectComponent } from './select.component'; import { HeadlessCheckoutSpy } from '../../../../core/spy/headless-checkout-spy/headless-checkout-spy'; import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; +import { FormLoader } from '../../../../core/form/form-loader'; function createComponent(name: string): HTMLElement { const element = document.createElement(WebComponentTagName.SelectComponent); @@ -37,6 +38,7 @@ describe('SelectComponent', () => { let postMessagesClient: PostMessagesClient; let headlessCheckout: HeadlessCheckout; let headlessCheckoutSpy: HeadlessCheckoutSpy; + let formLoader: FormLoader; window.customElements.define( WebComponentTagName.SelectComponent, @@ -50,6 +52,11 @@ describe('SelectComponent', () => { send: noopStub, } as unknown as PostMessagesClient; + formLoader = { + setupFieldsToTrackLoading: noopStub, + setFieldLoaded: noopStub, + } as unknown as FormLoader; + headlessCheckout = { form: { onFieldsStatusChange: noopStub, @@ -84,6 +91,9 @@ describe('SelectComponent', () => { }) .register(HeadlessCheckoutSpy, { useValue: headlessCheckoutSpy, + }) + .register(FormLoader, { + useValue: formLoader, }); }); diff --git a/src/features/headless-checkout/web-components/select/select.component.ts b/src/features/headless-checkout/web-components/select/select.component.ts index adee00a..c01ab8f 100644 --- a/src/features/headless-checkout/web-components/select/select.component.ts +++ b/src/features/headless-checkout/web-components/select/select.component.ts @@ -421,8 +421,8 @@ export class SelectComponent extends BaseControl { void this.getComponentConfig(this.controlName).then((config) => { this.config = config; - this.render(); + this.finishLoadingComponentHandler(this.controlName); }); } } diff --git a/src/features/headless-checkout/web-components/text-component/text.component.spec.ts b/src/features/headless-checkout/web-components/text-component/text.component.spec.ts index 7667ace..a9c6e50 100644 --- a/src/features/headless-checkout/web-components/text-component/text.component.spec.ts +++ b/src/features/headless-checkout/web-components/text-component/text.component.spec.ts @@ -6,7 +6,6 @@ import { FormSpy } from '../../../../core/spy/form-spy/form-spy'; import { HeadlessCheckout } from '../../headless-checkout'; import { TextComponent } from './text.component'; import { FormFieldsStatus } from '../../../../core/form/form-fields-status.interface'; -import { isLoadingCssClassName } from '../../../../shared/loading-state/is-loading-css-class-name.const'; const fieldName = 'zip'; @@ -61,7 +60,7 @@ describe('TextComponent', () => { window.customElements.define( WebComponentTagName.TextComponent, - TextComponent + TextComponent, ); beforeEach(() => { @@ -129,7 +128,7 @@ describe('TextComponent', () => { spyOn(headlessCheckout.form, 'onFieldsStatusChange').and.callFake( (callbackFn) => { setTimeout(() => (callback = callbackFn)); - } + }, ); element = createComponent(); @@ -151,7 +150,7 @@ describe('TextComponent', () => { spyOn(headlessCheckout.form, 'onFieldsStatusChange').and.callFake( (callbackFn) => { setTimeout(() => (callback = callbackFn)); - } + }, ); element = createComponent(); @@ -173,7 +172,7 @@ describe('TextComponent', () => { spyOn(headlessCheckout.form, 'onFieldsStatusChange').and.callFake( (callbackFn) => { setTimeout(() => (callback = callbackFn)); - } + }, ); element = createComponent(); @@ -195,7 +194,7 @@ describe('TextComponent', () => { spyOn(headlessCheckout.form, 'onFieldsStatusChange').and.callFake( (callbackFn) => { setTimeout(() => (callback = callbackFn)); - } + }, ); element = createComponent(); @@ -210,40 +209,4 @@ describe('TextComponent', () => { done(); }); }); - - it('Should add loading css class', () => { - spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); - spyOn(postMessagesClient, 'send').and.resolveTo({ - name: fieldName, - }); - - const element = createComponent(); - expect(element.classList).toContain(isLoadingCssClassName); - }); - - it('Should remove loading css class', (done) => { - // eslint-disable-next-line prefer-const - let element: HTMLElement; - let callback: (value?: unknown) => void = noopStub; - spyOn(headlessCheckout.events, 'onCoreEvent').and.callFake((...args) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - callback = args[2]; - - setTimeout(() => { - callback({ fieldName }); - expect(element?.classList).not.toContain(isLoadingCssClassName); - - done(); - }); - return noopStub; - }); - spyOnProperty(formSpy, 'formWasInit').and.returnValue(true); - spyOn(postMessagesClient, 'send').and.resolveTo({ - name: fieldName, - }); - - element = createComponent(); - expect(element.classList).toContain(isLoadingCssClassName); - }); }); diff --git a/src/features/headless-checkout/web-components/text-component/text.component.ts b/src/features/headless-checkout/web-components/text-component/text.component.ts index 9a64f07..d7f5db9 100644 --- a/src/features/headless-checkout/web-components/text-component/text.component.ts +++ b/src/features/headless-checkout/web-components/text-component/text.component.ts @@ -12,7 +12,6 @@ import { HeadlessCheckout } from '../../headless-checkout'; import { ValidationErrors } from '../../../../core/form/validation-errors.interface'; import { TextComponentConfig } from './text-component.config.interface'; import { FieldStatus } from '../../../../core/form/field-status.interface'; -import { finishLoadComponentHandler } from '../../post-messages-handlers/finish-load-component.handler'; export class TextComponent extends SecureComponentAbstract { protected config?: TextComponentConfig; @@ -30,16 +29,6 @@ export class TextComponent extends SecureComponentAbstract { this.postMessagesClient = container.resolve(PostMessagesClient); this.window = container.resolve(Window); this.headlessCheckout = container.resolve(HeadlessCheckout); - - this.headlessCheckout.events.onCoreEvent( - EventName.finishLoadComponent, - finishLoadComponentHandler, - (res) => { - if (res?.fieldName && res?.fieldName === this.inputName) { - this.finishLoadingComponentHandler(this.inputName); - } - }, - ); } public static get observedAttributes(): string[] { @@ -47,7 +36,6 @@ export class TextComponent extends SecureComponentAbstract { } protected connectedCallback(): void { - this.startLoadingComponentHandler(); if (!this.formSpy.formWasInit) { this.formSpy.listenFormInit(() => this.getConfigFromInputName()); return; @@ -83,6 +71,15 @@ export class TextComponent extends SecureComponentAbstract { this.config = config; this.componentName = componentName; super.render(); + this.addEventListenerToElement( + this.querySelector('iframe') as unknown as HTMLIFrameElement, + 'load', + () => { + if (this.inputName) { + this.finishLoadingComponentHandler(this.inputName); + } + }, + ); }; protected attributeChangedCallback(): void { diff --git a/src/shared/loading-state/dispatch-finish-loading-event.function.ts b/src/shared/loading-state/dispatch-finish-loading-event.function.ts deleted file mode 100644 index f7d20b4..0000000 --- a/src/shared/loading-state/dispatch-finish-loading-event.function.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { EventName } from '../../core/event-name.enum'; - -export function createFinishLoadingEvent(componentName: string): CustomEvent { - const eventOptions = { - bubbles: true, - composed: true, - detail: { - componentName, - }, - }; - - return new CustomEvent(EventName.finishLoadComponent, eventOptions); -} diff --git a/src/shared/loading-state/is-loading-css-class-name.const.ts b/src/shared/loading-state/is-loading-css-class-name.const.ts deleted file mode 100644 index 28dab1d..0000000 --- a/src/shared/loading-state/is-loading-css-class-name.const.ts +++ /dev/null @@ -1 +0,0 @@ -export const isLoadingCssClassName = 'psdk-is-loading';