Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/ui/wizard/src/ |
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/ui/wizard/src/index.js |
import { Tag, Loc, Dom, Type } from 'main.core'; import './style.css'; export type Metadata = { [key: string]: { get content(): HTMLElement | HTMLElement[]; title: string; beforeCompletion?: () => Promise<boolean>; }; }; export type WizardOptions = { back?: { className?: string; titles?: { [$Keys<Metadata>]: string; }; }; next?: { className?: string; titles?: { [$Keys<Metadata>]: string; }; }; complete?: { className?: string; title?: string; onComplete?: Function; }; swapButtons: boolean; }; export class Wizard { #metadata: Metadata; #order: Array<$Keys<Metadata>>; #options: WizardOptions; #stepIndex: number; #stepNode: HTMLElement; #stages: Map<string, HTMLElement>; #navigationButtons: { [key: string]: HTMLElement }; constructor(metadata: Metadata = {}, options: ?WizardOptions = {}) { this.#metadata = metadata; this.#options = options; this.#order = Object.keys(metadata); this.#stepIndex = 0; this.#stepNode = Tag.render`<div class="sign-wizard__step"></div>`; this.#stages = new Map(); this.#navigationButtons = this.#createNavigationButtons(); } #createNavigationButtons(): { [key: string]: HTMLElement } { const classList = [ 'ui-btn', 'ui-btn-lg', 'ui-btn-round', 'sign-wizard__footer_button', ]; const { back = {}, next = {}, complete = {}, swapButtons = false } = this.#options ?? {}; const { title: completeTitle, onComplete } = complete; const backClassName = (back.className ?? '').split(' '); const nextClassName = (next.className ?? '').split(' '); const completeClassName = (complete.className ?? '').split(' '); const backButton = { id: 'back', title: Loc.getMessage('SIGN_WIZARD_FOOTER_BUTTON_BACK'), method: () => this.#onPrevStep(), buttonClassList: [...classList, ...backClassName] }; const buttons = [ { id: 'next', title: Loc.getMessage('SIGN_WIZARD_FOOTER_BUTTON_NEXT'), method: () => this.#onNextStep(), buttonClassList: [...classList, ...nextClassName] }, { id: 'complete', title: completeTitle ?? Loc.getMessage('SIGN_WIZARD_FOOTER_BUTTON_COMPLETE'), method: async () => { const completed = await this.#tryCompleteStep('complete'); completed && onComplete?.(); }, buttonClassList: [...classList, ...completeClassName] }, ]; if (swapButtons) { buttons.push(backButton); } else { buttons.unshift(backButton); } return buttons.reduce((acc, button) => { const { title, method, buttonClassList = classList, id } = button; const node = Tag.render` <button class="${buttonClassList.join(' ')}" title="${title}" onclick="${method}" > ${title} </button> `; acc[id] = node; return acc; }, {}); } #createStages(): Map<String, HTMLElement> { const entries = Object.entries(this.#metadata); const stages = new Map(); entries.forEach(([stepName, step]) => { const stage = Tag.render` <span class="sign-wizard__stages_item"> ${step.title} </span> `; stages.set(stepName, stage); }); return stages; } #onPrevStep() { this.#stepIndex -= 1; this.moveOnStep(this.#stepIndex); } async #tryCompleteStep(buttonId: string = 'next'): Promise<boolean> { const stepName = this.#order[this.#stepIndex]; const { beforeCompletion } = this.#metadata[stepName] ?? {}; this.toggleBtnLoadingState(buttonId, true); const shouldComplete = await beforeCompletion?.() ?? true; this.toggleBtnLoadingState(buttonId, false); return shouldComplete; } async #onNextStep() { const completed = await this.#tryCompleteStep(); if (completed) { this.#stepIndex += 1; this.moveOnStep(this.#stepIndex); } } #renderButtonTitle(backButton: HTMLElement, nextButton: HTMLElement) { const { back = {}, next = {} } = this.#options ?? {}; const stepName = this.#order[this.#stepIndex]; const backTitle = back.titles?.[stepName] ?? Loc.getMessage('SIGN_WIZARD_FOOTER_BUTTON_BACK'); const nextTitle = next.titles?.[stepName] ?? Loc.getMessage('SIGN_WIZARD_FOOTER_BUTTON_NEXT'); backButton.textContent = backTitle; nextButton.textContent = nextTitle; } #renderNavigationButtons() { const { back: backButton, next: nextButton, complete: completeButton } = this.#navigationButtons; const isFirstStep = this.#stepIndex === 0; const isLastStep = this.#stepIndex + 1 === this.#order.length; Dom.removeClass(backButton, '--hide'); Dom.removeClass(nextButton, '--hide'); Dom.addClass(completeButton, '--hide'); this.#renderButtonTitle(backButton, nextButton); if (isFirstStep) { Dom.addClass(backButton, '--hide'); } if (isLastStep) { Dom.addClass(nextButton, '--hide'); Dom.removeClass(completeButton, '--hide'); } } #renderActiveStage() { this.#stages.forEach((stageNode) => { Dom.removeClass(stageNode, '--active'); }); const stepName = this.#order[this.#stepIndex]; const stageNode = this.#stages.get(stepName); Dom.addClass(stageNode, '--active'); } #renderStep() { const stepName = this.#order[this.#stepIndex]; const { content } = this.#metadata[stepName] ?? {}; if (!content) { return; } Dom.clean(this.#stepNode); if (Type.isArrayFilled(content)) { content.forEach((node) => Dom.append(node, this.#stepNode)); } else { Dom.append(content, this.#stepNode); } } getLayout(): HTMLElement { this.#stages = this.#createStages(); const content = Tag.render` <div class="sign-wizard__content"> <div class="sign-wizard__stages"> ${[...this.#stages.values()]} </div> ${this.#stepNode} </div> `; const footer = Tag.render` <div class="sign-wizard__footer"> ${Object.values(this.#navigationButtons)} </div> `; return Tag.render` <div class="sign-wizard__scope sign-wizard"> ${content} ${footer} </div> `; } moveOnStep(step: number) { this.#stepIndex = step; this.#renderActiveStage(); this.#renderNavigationButtons(); this.#renderStep(); } toggleBtnLoadingState(buttonId: string, loading: boolean) { const button = this.#navigationButtons[buttonId]; if (loading) { Dom.addClass(button, 'ui-btn-wait'); } else { Dom.removeClass(button, 'ui-btn-wait'); } } toggleBtnActiveState(buttonId: string, shouldDisable: boolean) { const button = this.#navigationButtons[buttonId]; if (shouldDisable) { Dom.addClass(button, 'ui-btn-disabled'); } else { Dom.removeClass(button, 'ui-btn-disabled'); } } }