UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

751 lines (709 loc) 29.9 kB
/** * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact Volker Schukai. * * SPDX-License-Identifier: AGPL-3.0 */ import { instanceSymbol } from "../../constants.mjs"; import { assembleMethodSymbol, registerCustomElement, } from "../../dom/customelement.mjs"; import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs"; import { fireCustomEvent } from "../../dom/events.mjs"; import { getLocaleOfDocument } from "../../dom/locale.mjs"; import { isFunction, isObject, isString } from "../../types/is.mjs"; import { validateString } from "../../types/validate.mjs"; import { getGlobal } from "../../types/global.mjs"; import { PopperButton } from "./popper-button.mjs"; import { AccessibilityStyleSheet } from "../stylesheet/accessibility.mjs"; import "./form.mjs"; import "./field-set.mjs"; import "./password.mjs"; import "./message-state-button.mjs"; export { CredentialButton }; /** * @private * @type {symbol} */ const passwordElementSymbol = Symbol("passwordElement"); const passwordInputElementSymbol = Symbol("passwordInputElement"); const submitButtonElementSymbol = Symbol("submitButtonElement"); const formElementSymbol = Symbol("formElement"); const fieldSetElementSymbol = Symbol("fieldSetElement"); const inFlightSymbol = Symbol("inFlight"); const wiredSymbol = Symbol("wired"); const mutationObserverSymbol = Symbol("mutationObserver"); const formWiredSymbol = Symbol("formWired"); const passwordWiredSymbol = Symbol("passwordWired"); /** * A password credential button control. * * @fragments /fragments/components/form/credential-button/ * * @example /examples/components/form/credential-button-simple * * @since 3.91.0 * @copyright Volker Schukai * @summary A popper button that opens a password change form. * @fires monster-credential-submit * @fires monster-credential-successful * @fires monster-credential-failed */ class CredentialButton extends PopperButton { /** * This method is called by the `instanceof` operator. * @return {symbol} */ static get [instanceSymbol]() { return Symbol.for( "@schukai/monster/components/form/credential-button@@instance", ); } /** * To set the options via the HTML tag, the attribute `data-monster-options` must be used. * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} * * The individual configuration values can be found in the table. * * @property {Object} templates Template definitions * @property {string} templates.main Main template * @property {Object} labels Label definitions * @property {string} labels.button Button label * @property {string} labels.title Field-set title * @property {string} labels.password Password label * @property {string} labels.description Description text * @property {string} labels.submit Submit button label * @property {string} labels.messageEmptyPassword Empty password message * @property {string} labels.messageNotConfigured Not configured message * @property {string} labels.messageSuccess Success message * @property {string} labels.messageFailed Failure message * @property {string} url API URL * @property {Object} fetch Fetch options * @property {string} fetch.redirect Fetch redirect option * @property {string} fetch.method Fetch method * @property {string} fetch.mode Fetch mode * @property {string} fetch.credentials Fetch credentials * @property {Object} fetch.headers Fetch headers * @property {Object} mapping Mapping configuration * @property {string} mapping.password Key for the password field in the payload * @property {Object|Function} mapping.data Additional payload data or builder * @property {Object} payload Payload configuration * @property {string} payload.password Key for the password field in the payload * @property {Object|Function} payload.data Additional payload data or builder * @property {Object} placeholder Placeholder configuration * @property {string} placeholder.password Password placeholder * @property {Object} autocomplete Autocomplete configuration * @property {string} autocomplete.password Password autocomplete * @property {Object} classes Class definitions * @property {Object} layout Layout configuration * @property {string} layout.containerStyle Inline style for the container * @property {string} layout.submitStyle Inline style for the submit button * @property {number} timeoutForMessage Duration in milliseconds to show messages * @property {number} timeoutForSuccess Duration in milliseconds for success state * @property {Object} features Feature flags * @property {boolean} features.closeOnSuccess Close popper on success * @property {boolean} features.clearOnSuccess Clear password on success */ get defaults() { const base = super.defaults; const labels = getTranslations(); return Object.assign({}, base, { templates: { main: getTemplate(), }, labels, aria: Object.assign({}, base.aria, { label: labels.button, }), layout: { containerStyle: "width: 20rem;", submitStyle: "margin-top: var(--monster-space-4);", }, url: "", fetch: { redirect: "error", method: "POST", mode: "same-origin", credentials: "same-origin", headers: { accept: "application/json", "content-type": "application/json", }, }, mapping: {}, payload: { password: "password", data: null, }, placeholder: { password: "", }, autocomplete: { password: "new-password", }, timeoutForMessage: 3500, timeoutForSuccess: 1200, features: { closeOnSuccess: true, clearOnSuccess: true, }, content: getContentTemplate(labels), }); } /** * @return {CredentialButton} */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); return this; } /** * @return {string} */ static getTag() { return "monster-credential-button"; } /** * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { const styles = super.getCSSStyleSheet(); styles.push(AccessibilityStyleSheet); return styles; } } /** * @private * @return {CredentialButton} */ function initControlReferences() { this[passwordElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=password]`, ); this[submitButtonElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=submit-button]`, ); this[formElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=form]`, ); this[fieldSetElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=field-set]`, ); return this; } /** * @private * @return {CredentialButton} */ function initEventHandler() { const wireUp = () => { initControlReferences.call(this); if (this[formElementSymbol]) { if (!this[formWiredSymbol]) { this[formWiredSymbol] = true; this[formElementSymbol].addEventListener("keydown", (event) => { if (event.key === "Enter") { this[submitButtonElementSymbol]?.click?.(); } }); } } if (this[fieldSetElementSymbol]) { const title = this.getOption("labels.title"); if (title !== undefined) { this[fieldSetElementSymbol].setOption("labels.title", title); } } if (this[passwordElementSymbol]) { const input = this[passwordElementSymbol]?.shadowRoot?.querySelector?.("input"); if (input instanceof HTMLElement) { this[passwordInputElementSymbol] = input; } if (this[passwordInputElementSymbol] && !this[passwordWiredSymbol]) { this[passwordWiredSymbol] = true; const sync = () => { const value = this[passwordElementSymbol]?.value ?? ""; this.value = value; fireCustomEvent(this, "monster-change", { value }); this.dispatchEvent( new Event("input", { bubbles: true, composed: true }), ); }; this[passwordInputElementSymbol].addEventListener("input", sync); this[passwordInputElementSymbol].addEventListener("change", () => { this.dispatchEvent( new Event("change", { bubbles: true, composed: true }), ); }); } } if (!this[submitButtonElementSymbol] || this[wiredSymbol]) { return; } this[wiredSymbol] = true; this[submitButtonElementSymbol].setOption("actions.click", () => { handleSubmit.call(this); }); }; queueMicrotask(wireUp); if (!this[mutationObserverSymbol]) { this[mutationObserverSymbol] = new MutationObserver(() => { wireUp(); }); this[mutationObserverSymbol].observe(this.shadowRoot, { childList: true, subtree: true, }); } return this; } /** * @private */ function handleSubmit() { if (this[inFlightSymbol]) { return; } const password = this[passwordElementSymbol]?.value; const timeout = this.getOption("timeoutForMessage"); if (!password) { this[submitButtonElementSymbol].setMessage( this.getOption("labels.messageEmptyPassword"), ); this[submitButtonElementSymbol].showMessage(timeout); this[submitButtonElementSymbol].setState("failed", timeout); queueMicrotask(() => this[passwordElementSymbol]?.focus?.()); return; } this.value = password; this.dispatchEvent(new Event("change", { bubbles: true, composed: true })); let url = this.getOption("url") || this.getOption("fetch.url"); if (isString(url)) { url = validateString(url); } const mapping = this.getOption("mapping"); const passwordField = mapping?.password || this.getOption("payload.password") || "password"; let payload = { [passwordField]: password }; let extraPayload = mapping?.data ?? this.getOption("payload.data"); if (isFunction(extraPayload)) { extraPayload = extraPayload({ password, element: this }); } if (isObject(extraPayload)) { payload = Object.assign({}, extraPayload, payload); } if (!url) { const timeoutSuccess = this.getOption("timeoutForSuccess"); this[submitButtonElementSymbol].setState("successful", timeoutSuccess); const successMessage = this.getOption("labels.messageSuccess"); if (successMessage) { this[submitButtonElementSymbol].setMessage(successMessage); this[submitButtonElementSymbol].showMessage(timeout); } if (this.getOption("features.clearOnSuccess")) { this[passwordElementSymbol].value = ""; } if (this.getOption("features.closeOnSuccess")) { setTimeout(() => { this.hideDialog(); }, timeoutSuccess); } fireCustomEvent(this, "monster-credential-successful", { password, payload, url: null, local: true, }); return; } const fetchOptions = Object.assign({}, this.getOption("fetch")); if (fetchOptions.url) { delete fetchOptions.url; } fetchOptions.headers = isObject(fetchOptions.headers) ? Object.assign({}, fetchOptions.headers) : {}; if (!fetchOptions.headers.accept) { fetchOptions.headers.accept = "application/json"; } if (!fetchOptions.headers["content-type"]) { fetchOptions.headers["content-type"] = "application/json"; } fetchOptions.body = JSON.stringify(payload); this[inFlightSymbol] = true; this[submitButtonElementSymbol].setState("activity"); fireCustomEvent(this, "monster-credential-submit", { password, payload, url, }); const global = getGlobal(); global .fetch(url, fetchOptions) .then((response) => { if (!response.ok) { return Promise.reject(response); } const timeoutSuccess = this.getOption("timeoutForSuccess"); this[submitButtonElementSymbol].setState("successful", timeoutSuccess); const successMessage = this.getOption("labels.messageSuccess"); if (successMessage) { this[submitButtonElementSymbol].setMessage(successMessage); this[submitButtonElementSymbol].showMessage(timeout); } if (this.getOption("features.clearOnSuccess")) { this[passwordElementSymbol].value = ""; } if (this.getOption("features.closeOnSuccess")) { setTimeout(() => { this.hideDialog(); }, timeoutSuccess); } fireCustomEvent(this, "monster-credential-successful", { password, payload, url, response, }); return response; }) .catch((error) => { const message = this.getOption("labels.messageFailed"); this[submitButtonElementSymbol].setMessage(message); this[submitButtonElementSymbol].showMessage(timeout); this[submitButtonElementSymbol].setState("failed", timeout); fireCustomEvent(this, "monster-credential-failed", { password, payload, url, error, }); }) .finally(() => { this[inFlightSymbol] = false; }); } function getTranslations() { const locale = getLocaleOfDocument(); switch (locale.language) { case "de": return { button: "Passwort ändern", title: "Passwort ändern", password: "Neues Passwort", description: "Wählen Sie ein sicheres Passwort, damit Ihr Konto gut geschützt ist. Es könnte aus Groß- und Kleinbuchstaben, Zahlen und Sonderzeichen bestehen. Auch wäre es besser, kein leicht zu erratendes Wort wie einen Namen oder ein Geburtsdatum zu verwenden.", submit: "Passwort ändern", messageEmptyPassword: "Bitte geben Sie ein neues Passwort ein", messageNotConfigured: "Dieses Formular ist nicht konfiguriert.", messageSuccess: "Passwort wurde erfolgreich geändert.", messageFailed: "Etwas ist schief gelaufen, bitte versuchen Sie es später erneut", }; case "es": return { button: "Cambiar contraseña", title: "Cambiar contraseña", password: "Nueva contraseña", description: "Elige una contraseña segura para proteger tu cuenta. Usa una combinación de mayúsculas, minúsculas, números y símbolos. Evita palabras fáciles de adivinar como nombres o fechas de nacimiento.", submit: "Cambiar contraseña", messageEmptyPassword: "Por favor ingrese una nueva contraseña", messageNotConfigured: "Este formulario no está configurado.", messageSuccess: "La contraseña se cambió correctamente.", messageFailed: "Algo salió mal, por favor intenta de nuevo más tarde.", }; case "zh": return { button: "更改密码", title: "更改密码", password: "新密码", description: "请选择一个安全密码以保护您的账户。建议包含大小写字母、数字和符号。避免使用容易猜到的词语,例如姓名或生日。", submit: "更改密码", messageEmptyPassword: "请输入新密码", messageNotConfigured: "此表单尚未配置。", messageSuccess: "密码修改成功。", messageFailed: "出了点问题,请稍后再试。", }; case "hi": return { button: "पासवर्ड बदलें", title: "पासवर्ड बदलें", password: "नया पासवर्ड", description: "अपने खाते की सुरक्षा के लिए एक मजबूत पासवर्ड चुनें। इसमें बड़े/छोटे अक्षर, संख्या और प्रतीक शामिल करें। नाम या जन्मतिथि जैसे आसानी से अनुमानित शब्दों से बचें।", submit: "पासवर्ड बदलें", messageEmptyPassword: "कृपया नया पासवर्ड दर्ज करें", messageNotConfigured: "यह फॉर्म कॉन्फ़िगर नहीं है।", messageSuccess: "पासवर्ड सफलतापूर्वक बदल दिया गया है।", messageFailed: "कुछ गलत हुआ, कृपया बाद में फिर से प्रयास करें।", }; case "bn": return { button: "পাসওয়ার্ড পরিবর্তন করুন", title: "পাসওয়ার্ড পরিবর্তন করুন", password: "নতুন পাসওয়ার্ড", description: "আপনার অ্যাকাউন্ট সুরক্ষিত রাখতে একটি শক্তিশালী পাসওয়ার্ড বেছে নিন। বড় ও ছোট হাতের অক্ষর, সংখ্যা এবং প্রতীক ব্যবহার করুন। নাম বা জন্মতারিখের মতো সহজে অনুমানযোগ্য শব্দ এড়িয়ে চলুন।", submit: "পাসওয়ার্ড পরিবর্তন করুন", messageEmptyPassword: "অনুগ্রহ করে একটি নতুন পাসওয়ার্ড লিখুন", messageNotConfigured: "এই ফর্মটি কনফিগার করা হয়নি।", messageSuccess: "পাসওয়ার্ড সফলভাবে পরিবর্তন হয়েছে।", messageFailed: "কিছু ভুল হয়েছে, পরে আবার চেষ্টা করুন।", }; case "pt": return { button: "Alterar senha", title: "Alterar senha", password: "Nova senha", description: "Escolha uma senha forte para manter sua conta segura. Use letras maiúsculas e minúsculas, números e símbolos. Evite palavras fáceis de adivinhar, como nomes ou datas de nascimento.", submit: "Alterar senha", messageEmptyPassword: "Por favor, insira uma nova senha", messageNotConfigured: "Este formulário não está configurado.", messageSuccess: "Senha alterada com sucesso.", messageFailed: "Algo deu errado, tente novamente mais tarde.", }; case "ru": return { button: "Изменить пароль", title: "Изменить пароль", password: "Новый пароль", description: "Выберите надежный пароль, чтобы защитить учетную запись. Используйте заглавные и строчные буквы, цифры и символы. Избегайте легко угадываемых слов, таких как имена или даты рождения.", submit: "Изменить пароль", messageEmptyPassword: "Пожалуйста, введите новый пароль", messageNotConfigured: "Эта форма не настроена.", messageSuccess: "Пароль успешно изменен.", messageFailed: "Что-то пошло не так, попробуйте позже.", }; case "ja": return { button: "パスワードを変更", title: "パスワードを変更", password: "新しいパスワード", description: "アカウントを安全に保つため、強力なパスワードを設定してください。大文字・小文字、数字、記号を組み合わせ、名前や誕生日など推測しやすい語は避けてください。", submit: "パスワードを変更", messageEmptyPassword: "新しいパスワードを入力してください", messageNotConfigured: "このフォームは設定されていません。", messageSuccess: "パスワードを変更しました。", messageFailed: "問題が発生しました。後でもう一度お試しください。", }; case "pa": return { button: "ਪਾਸਵਰਡ ਬਦਲੋ", title: "ਪਾਸਵਰਡ ਬਦਲੋ", password: "ਨਵਾਂ ਪਾਸਵਰਡ", description: "ਆਪਣਾ ਖਾਤਾ ਸੁਰੱਖਿਅਤ ਰੱਖਣ ਲਈ ਇੱਕ ਮਜ਼ਬੂਤ ਪਾਸਵਰਡ ਚੁਣੋ। ਵੱਡੇ/ਛੋਟੇ ਅੱਖਰ, ਅੰਕ ਅਤੇ ਚਿੰਨ੍ਹ ਵਰਤੋ। ਨਾਮ ਜਾਂ ਜਨਮ ਤਾਰੀਖ ਵਰਗੇ ਆਸਾਨੀ ਨਾਲ ਅਨੁਮਾਨਯੋਗ ਸ਼ਬਦਾਂ ਤੋਂ ਬਚੋ।", submit: "ਪਾਸਵਰਡ ਬਦਲੋ", messageEmptyPassword: "ਕਿਰਪਾ ਕਰਕੇ ਨਵਾਂ ਪਾਸਵਰਡ ਦਾਖਲ ਕਰੋ", messageNotConfigured: "ਇਹ ਫਾਰਮ ਸੰਰਚਿਤ ਨਹੀਂ ਹੈ।", messageSuccess: "ਪਾਸਵਰਡ ਸਫਲਤਾਪੂਰਵਕ ਬਦਲਿਆ ਗਿਆ।", messageFailed: "ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ, ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਕੋਸ਼ਿਸ਼ ਕਰੋ।", }; case "mr": return { button: "पासवर्ड बदला", title: "पासवर्ड बदला", password: "नवीन पासवर्ड", description: "आपले खाते सुरक्षित ठेवण्यासाठी मजबूत पासवर्ड निवडा. मोठी/लहान अक्षरे, संख्या आणि चिन्हे वापरा. नाव किंवा जन्मतारीखसारखे सहज अंदाज लावता येणारे शब्द टाळा.", submit: "पासवर्ड बदला", messageEmptyPassword: "कृपया नवीन पासवर्ड प्रविष्ट करा", messageNotConfigured: "हा फॉर्म कॉन्फिगर केलेला नाही.", messageSuccess: "पासवर्ड यशस्वीरित्या बदलला.", messageFailed: "काहीतरी चूक झाली, कृपया नंतर पुन्हा प्रयत्न करा.", }; case "fr": return { button: "Changer le mot de passe", title: "Changer le mot de passe", password: "Nouveau mot de passe", description: "Choisissez un mot de passe fort pour protéger votre compte. Utilisez des majuscules, minuscules, chiffres et symboles. Évitez les mots faciles à deviner comme des noms ou des dates de naissance.", submit: "Changer le mot de passe", messageEmptyPassword: "Veuillez saisir un nouveau mot de passe", messageNotConfigured: "Ce formulaire n'est pas configuré.", messageSuccess: "Mot de passe modifié avec succès.", messageFailed: "Une erreur s'est produite, veuillez réessayer plus tard.", }; case "it": return { button: "Cambia password", title: "Cambia password", password: "Nuova password", description: "Scegli una password sicura per proteggere il tuo account. Usa lettere maiuscole e minuscole, numeri e simboli. Evita parole facili da indovinare come nomi o date di nascita.", submit: "Cambia password", messageEmptyPassword: "Per favore, inserisci una nuova password", messageNotConfigured: "Questo modulo non è configurato.", messageSuccess: "Password aggiornata con successo.", messageFailed: "Qualcosa è andato storto, riprova più tardi.", }; case "nl": return { button: "Wachtwoord wijzigen", title: "Wachtwoord wijzigen", password: "Nieuw wachtwoord", description: "Kies een sterk wachtwoord om je account te beveiligen. Gebruik hoofdletters, kleine letters, cijfers en symbolen. Vermijd gemakkelijk te raden woorden zoals namen of geboortedata.", submit: "Wachtwoord wijzigen", messageEmptyPassword: "Voer een nieuw wachtwoord in", messageNotConfigured: "Dit formulier is niet geconfigureerd.", messageSuccess: "Wachtwoord succesvol gewijzigd.", messageFailed: "Er is iets misgegaan, probeer het later opnieuw.", }; case "sv": return { button: "Ändra lösenord", title: "Ändra lösenord", password: "Nytt lösenord", description: "Välj ett starkt lösenord för att skydda ditt konto. Använd stora och små bokstäver, siffror och symboler. Undvik lättgissade ord som namn eller födelsedatum.", submit: "Ändra lösenord", messageEmptyPassword: "Ange ett nytt lösenord", messageNotConfigured: "Det här formuläret är inte konfigurerat.", messageSuccess: "Lösenordet har ändrats.", messageFailed: "Något gick fel, försök igen senare.", }; case "pl": return { button: "Zmień hasło", title: "Zmień hasło", password: "Nowe hasło", description: "Wybierz silne hasło, aby chronić swoje konto. Używaj wielkich i małych liter, cyfr oraz symboli. Unikaj łatwych do odgadnięcia słów, takich jak imiona czy daty urodzenia.", submit: "Zmień hasło", messageEmptyPassword: "Wpisz nowe hasło", messageNotConfigured: "Ten formularz nie jest skonfigurowany.", messageSuccess: "Hasło zostało zmienione.", messageFailed: "Coś poszło nie tak, spróbuj ponownie później.", }; case "da": return { button: "Skift adgangskode", title: "Skift adgangskode", password: "Ny adgangskode", description: "Vælg en stærk adgangskode for at beskytte din konto. Brug store og små bogstaver, tal og symboler. Undgå let gættelige ord som navne eller fødselsdatoer.", submit: "Skift adgangskode", messageEmptyPassword: "Indtast en ny adgangskode", messageNotConfigured: "Denne formular er ikke konfigureret.", messageSuccess: "Adgangskoden er ændret.", messageFailed: "Noget gik galt, prøv igen senere.", }; case "no": return { button: "Endre passord", title: "Endre passord", password: "Nytt passord", description: "Velg et sterkt passord for å beskytte kontoen din. Bruk store og små bokstaver, tall og symboler. Unngå lett gjettelige ord som navn eller fødselsdato.", submit: "Endre passord", messageEmptyPassword: "Skriv inn et nytt passord", messageNotConfigured: "Dette skjemaet er ikke konfigurert.", messageSuccess: "Passordet er endret.", messageFailed: "Noe gikk galt, prøv igjen senere.", }; case "cs": return { button: "Změnit heslo", title: "Změnit heslo", password: "Nové heslo", description: "Zvolte silné heslo, aby byl váš účet v bezpečí. Použijte velká a malá písmena, čísla a symboly. Vyhněte se snadno uhodnutelným slovům, jako jsou jména nebo data narození.", submit: "Změnit heslo", messageEmptyPassword: "Zadejte nové heslo", messageNotConfigured: "Tento formulář není nakonfigurován.", messageSuccess: "Heslo bylo úspěšně změněno.", messageFailed: "Něco se pokazilo, zkuste to prosím později.", }; default: case "en": return { button: "Change password", title: "Change password", password: "New password", description: "Choose a strong password to keep your account secure. Use a mix of upper- and lowercase letters, numbers, and symbols. Avoid easy-to-guess words like names or birthdays.", submit: "Change password", messageEmptyPassword: "Please enter a new password", messageNotConfigured: "This form is not configured.", messageSuccess: "Password updated successfully.", messageFailed: "Something went wrong. Please try again later.", }; } } /** * @private * @return {string} */ function getContentTemplate(labels) { const title = labels?.title ?? ""; const password = labels?.password ?? ""; const description = labels?.description ?? ""; const submit = labels?.submit ?? ""; // language=HTML return ` <div data-monster-role="container" part="container" data-monster-attributes="style path:layout.containerStyle"> <monster-form data-monster-role="form"> <monster-field-set part="field-set" data-monster-role="field-set" data-monster-option-labels-title="${title}"> <label part="password-label">${password}</label> <monster-password part="password" data-monster-role="password" data-monster-attributes="data-monster-option-placeholder path:placeholder.password, data-monster-option-autocomplete path:autocomplete.password"> </monster-password> <p part="description">${description}</p> <monster-message-state-button data-monster-role="submit-button" part="submit-button" data-monster-attributes="style path:layout.submitStyle"> ${submit} </monster-message-state-button> </monster-field-set> </monster-form> </div> `; } /** * @private * @return {string} */ function getTemplate() { return ` <div data-monster-role="control" part="control"> <button data-monster-attributes="disabled path:disabled | if:true, class path:classes.button" data-monster-role="button" part="button" aria-labelledby="monster-credential-button-aria-label" data-monster-replace="path:labels.button"></button> <div id="monster-credential-button-aria-label" class="visually-hidden" data-monster-replace="path:aria.label">change password</div> <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1"> <div data-monster-role="arrow"></div> <div part="content" class="flex" data-monster-replace="path:content"></div> </div> </div> `; } registerCustomElement(CredentialButton);