diff --git a/custom_components/alexa_media/.translations/ar.json b/custom_components/alexa_media/.translations/ar.json new file mode 100644 index 00000000..216f8f32 --- /dev/null +++ b/custom_components/alexa_media/.translations/ar.json @@ -0,0 +1,57 @@ +{ + "config": { + "abort": { + "forgot_password": "The Forgot Password page was detected. This normally is the result of too may failed logins. Amazon may require action before a relogin can be attempted.", + "login_failed": "Alexa Media Player failed to login.", + "reauth_successful": "Alexa Media Player successfully reauthenticated." + }, + "error": { + "2fa_key_invalid": "Invalid Built-In 2FA key", + "connection_error": "Error connecting; check network and retry", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the External Url under Configuration -> General", + "identifier_exists": "Email for Alexa URL already registered", + "invalid_credentials": "Invalid credentials", + "unknown_error": "Unknown error, please enable advanced debugging and report log info" + }, + "step": { + "captcha": { + "data": { + "securitycode": "2FA Code (recommended to avoid login issues)" + } + }, + "totp_register": { + "data": { + "registered": "OTP from the Built-in 2FA App Key confirmed successfully." + }, + "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", + "title": "Alexa Media Player - OTP Confirmation" + }, + "user": { + "data": { + "debug": "Advanced debugging", + "email": "Email Address", + "exclude_devices": "Excluded device (comma separated)", + "hass_url": "Url to access Home Assistant", + "include_devices": "Included device (comma separated)", + "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", + "password": "Password", + "scan_interval": "Seconds between scans", + "securitycode": "2FA Code (recommended to avoid login issues)", + "url": "Amazon region domain (e.g., amazon.co.uk)" + }, + "description": "Please confirm the information below.", + "title": "Alexa Media Player - Configuration" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "extended_entity_discovery": "Include devices connected via Echo", + "queue_delay": "Seconds to wait to queue commands together" + } + } + } + } +} diff --git a/custom_components/alexa_media/.translations/de.json b/custom_components/alexa_media/.translations/de.json index 15a60736..39e2fba1 100644 --- a/custom_components/alexa_media/.translations/de.json +++ b/custom_components/alexa_media/.translations/de.json @@ -14,38 +14,10 @@ "unknown_error": "Unbekannter Fehler, bitte Log-Info melden" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon sendet eine Push-Benachrichtigung je folgender Nachricht. Bitte antworte sie vollständig, bevor Du fortfährst. \n{message}", - "title": "Alexa Media Player - Aktion erforderlich" - }, - "authselect": { - "data": { - "authselectoption": "Einmal Pin Methode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Einmal Pin Passwort" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Passwort", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Verifizierungs Methode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nBitte Verifizierungs Methode auswählen. (z.B., `0` oder `1`) \n{message}", - "title": "Alexa Media Player - Verifizierungs Methode" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code" - }, - "description": "**{email} - alexa.{url}** \nDen Zwei Faktor Pin eingeben. \n{message}", - "title": "Alexa Media Player - Zwei Faktor Authentifizierung" - }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Erweitertes debugging", "email": "Email Adresse", "exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)", "hass_url": "Url to access Home Assistant", "include_devices": "Eingebundene Geräte (komma getrennnt)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Passwort", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Sekunden zwischen den Scans", "securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)", "url": "Amazon Region (z.B., amazon.de)" }, "description": "Bitte geben Sie ihre Informationen ein.", "title": "Alexa Media Player - Konfiguration" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "Erweitertes debugging", - "email": "Email Adresse", - "exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)", - "include_devices": "Eingebundene Geräte (komma getrennnt)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Passwort", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Sekunden zwischen den Scans", - "securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)", - "url": "Amazon Region (z.B., amazon.de)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/.translations/en.json b/custom_components/alexa_media/.translations/en.json index f52044a0..216f8f32 100644 --- a/custom_components/alexa_media/.translations/en.json +++ b/custom_components/alexa_media/.translations/en.json @@ -14,38 +14,10 @@ "unknown_error": "Unknown error, please enable advanced debugging and report log info" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" - }, - "authselect": { - "data": { - "authselectoption": "OTP method", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - One Time Password" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA Code (recommended to avoid login issues)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Verification method", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - Verification Method" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code" - }, - "description": "**{email} - alexa.{url}** \nEnter the One Time Password (OTP). \n{message}", - "title": "Alexa Media Player - Two Factor Authentication" - }, "user": { "data": { - "cookies_txt": "Cookies.txt data", "debug": "Advanced debugging", "email": "Email Address", "exclude_devices": "Excluded device (comma separated)", "hass_url": "Url to access Home Assistant", "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Seconds between scans", "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, - "description": "Please confirm the information below. For legacy configuration, disable `Use Login Proxy method` option.", + "description": "Please confirm the information below.", "title": "Alexa Media Player - Configuration" - }, - "user_legacy": { - "data": { - "cookies_txt": "Cookies.txt data", - "debug": "Advanced debugging", - "email": "Email Address", - "exclude_devices": "Excluded device (comma separated)", - "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Seconds between scans", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Amazon region domain (e.g., amazon.co.uk)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/.translations/es.json b/custom_components/alexa_media/.translations/es.json index d1ddc7a7..644bc547 100644 --- a/custom_components/alexa_media/.translations/es.json +++ b/custom_components/alexa_media/.translations/es.json @@ -11,41 +11,13 @@ "hass_url_invalid": "No se puede conectar a la url de Home Assistant. Compruebe la dirección URL externa en Configuración -> General", "identifier_exists": "Correo electrónico para la URL de Alexa ya registrado", "invalid_credentials": "Credenciales no válidas", - "unknown_error": "Error desconocido, por favor revisa los registros en Home Assistant y reporta el error si es necesario." + "unknown_error": "Error desconocido, habilite la depuración avanzada e informe la información de registro" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon enviará una notificación a tu dispositivo vinculado. Completa los pasos descritos antes de continuar.\n{message}", - "title": "Alexa Media Player - Acción requerida" - }, - "authselect": { - "data": { - "authselectoption": "Clave OTP", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Contraseña de un solo uso" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Contraseña", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Método de verificación", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nSeleccione el método de verificación por número. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - Método de verificación" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code" - }, - "description": "**{email} - alexa.{url}** \nIngrese la contraseña de un solo uso (OTP). \n{message}", - "title": "Alexa Media Player - Autenticación de dos factores" - }, "user": { "data": { - "cookies_txt": "Datos de Cookies.txt", "debug": "Depuración avanzada", "email": "Dirección de correo electrónico", "exclude_devices": "Dispositivo excluido (separado por comas)", - "hass_url": "Url to access Home Assistant", + "hass_url": "Url para acceder a Home Assistant", "include_devices": "Dispositivo incluido (separado por comas)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Contraseña", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Segundos entre escaneos", "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)", "url": "Región del dominio de Amazon (por ejemplo, amazon.es)" }, - "description": "Confirme la siguiente información. Para la configuración heredada, desactive la opción `Usar método de proxy de inicio de sesión`.", + "description": "Confirme la siguiente información.", "title": "Alexa Media Player - Configuración" - }, - "user_legacy": { - "data": { - "cookies_txt": "Datos de Cookies.txt", - "debug": "Depuración avanzada", - "email": "Dirección de correo electrónico", - "exclude_devices": "Dispositivo excluido (separado por comas)", - "include_devices": "Dispositivo incluido (separado por comas)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Contraseña", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Segundos entre escaneos", - "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)", - "url": "Región del dominio de Amazon (por ejemplo, amazon.es)" - }, - "description": "Por favor introduce tu [información](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **El método más rápido es [Importar cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import).** \n**ADVERTENCIA: Amazon informará 'Introduce un correo electrónico o número de teléfono válido' si tu cuenta utiliza [códigos 2FA - Segundo Factor de Autenticación](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/.translations/fr.json b/custom_components/alexa_media/.translations/fr.json index 6d73c4e9..f466b335 100644 --- a/custom_components/alexa_media/.translations/fr.json +++ b/custom_components/alexa_media/.translations/fr.json @@ -14,38 +14,10 @@ "unknown_error": "Erreur inconnue, veuillez signaler les informations du journal" }, "step": { - "action_required": { - "data": { - "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)" - }, - "description": "** {email} - alexa. {url} ** \n Amazon enverra une notification push conformément au message ci-dessous. Veuillez répondre complètement avant de continuer. \n {message}", - "title": "Alexa Media Player - Action requise" - }, - "authselect": { - "data": { - "authselectoption": "OTP méthode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Mot de passe unique" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Mot de passe", - "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)", "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Méthode de vérification", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nVeuillez sélectionner la méthode de vérification par numéro. (exemple., `0` ou `1`) \n{message}", - "title": "Alexa Media Player - Méthode de vérification" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "** {email} - alexa. {url} **\n Avez-vous confirmé avec succès un OTP à partir de la clé d'application 2FA intégrée avec Amazon?\n > Code OTP {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)", - "securitycode": "2FA Code" - }, - "description": "**{email} - alexa.{url}** \nEntrez le mot de passe unique (OTP). \n{message}", - "title": "Alexa Media Player - Authentification à deux facteurs" - }, "user": { "data": { - "cookies_txt": "Données cookies.txt", "debug": "Débogage avancé", "email": "Adresse Email", "exclude_devices": "Appareil exclu (séparé par des virgules)", "hass_url": "Url to access Home Assistant", "include_devices": "Appareil inclus (séparé par des virgules)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)", "password": "Mot de passe", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Secondes entre les analyses", "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)", "url": "Domaine de la région Amazon (exemple, amazon.fr)" }, - "description": "Veuillez confirmer les informations ci-dessous. Pour la configuration héritée, désactivez l'option `Utiliser la méthode proxy de connexion`.", + "description": "Veuillez confirmer les informations ci-dessous.", "title": "Alexa Media Player - Configuration" - }, - "user_legacy": { - "data": { - "cookies_txt": "Données cookies.txt", - "debug": "Débogage avancé", - "email": "Adresse Email", - "exclude_devices": "Appareil exclu (séparé par des virgules)", - "include_devices": "Appareil inclus (séparé par des virgules)", - "oauth_login": "Activer la méthode d'application oauth-token", - "otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)", - "password": "Mot de passe", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Secondes entre les analyses", - "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)", - "url": "Domaine de la région Amazon (exemple, amazon.fr)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/.translations/it.json b/custom_components/alexa_media/.translations/it.json index f48012a9..318c7b5a 100644 --- a/custom_components/alexa_media/.translations/it.json +++ b/custom_components/alexa_media/.translations/it.json @@ -14,38 +14,10 @@ "unknown_error": "Errore sconosciuto, si prega di abilitare il debug avanzato e riportare i log informativi" }, "step": { - "action_required": { - "data": { - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" - }, - "description": "**{email} - alexa. {url}**\n Amazon invierà una notifica push per il seguente messaggio. Si prega di rispondere completamente prima di continuare.\n {message}", - "title": "Alexa Media Player - Azione Richiesta" - }, - "authselect": { - "data": { - "authselectoption": "Metodo password usa e getta (OTP)", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" - }, - "description": "**{email} - alexa.{url}**\n{message}", - "title": "Alexa Media Player - Password Usa e Getta (One Time Password)" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Password", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Metodo di verifica", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" - }, - "description": "**{email} - alexa.{url}**\nSelezionare un metodo di verifica con un numero. (ad es., `0` o `1`)\n{message}", - "title": "Alexa Media Player - Metodo di verifica" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}**\nHai confermato con successo una chiave usa e getta (OTP) dall'applicazione 2FA integrata con Amazon?\n>Codice OTP {message}", "title": "Alexa Media Player - Conferma OTP" }, - "twofactor": { - "data": { - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", - "securitycode": "Codice autenticazione a due fattori (2FA)" - }, - "description": "**{email} - alexa.{url}** \nInserisci la password usa e getta (OTP). \n{message}", - "title": "Alexa Media Player - Autenticazione a Due Fattori" - }, "user": { "data": { - "cookies_txt": "Dati cookies.txt", "debug": "Debug avanzato", "email": "Indirizzo email", "exclude_devices": "Dispositivi da escludere (separati da virgola)", "hass_url": "URL per accedere a Home Assistant", "include_devices": "Dispositivi da includere (separati da virgola)", - "oauth_login": "Abilita metodo app oauth-token", "otp_secret": "Chiave dell'app 2FA integrata (generazione automatica di codici 2FA)", "password": "Password", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", "scan_interval": "Tempo in secondi fra le scansioni", "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)", "url": "Regione del dominio Amazon (ad es., amazon.it)" }, - "description": "Confermare le informazioni sottostanti. Per le vecchie configurazioni, disabilitare l'opzione `Usa Proxy come metodo di login`.", + "description": "Confermare le informazioni sottostanti.", "title": "Alexa Media Player - Configurazione" - }, - "user_legacy": { - "data": { - "cookies_txt": "Dati cookies.txt", - "debug": "Debug avanzato", - "email": "Indirizzo email", - "exclude_devices": "Dispositivi da escludere (separati da virgola)", - "include_devices": "Dispositivi da includere (separati da virgola)", - "oauth_login": "Abilitare il metodo oauth-token app", - "otp_secret": "Chiave 2FA integrata (genera automaticamente i codici 2FA)", - "password": "Password", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", - "scan_interval": "Tempo in secondi fra le scansioni", - "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)", - "url": "Regione del dominio Amazon (ad es., amazon.it)" - }, - "description": "Prego inserisci le tue [informazioni](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) potrebbe essere più semplice!** \n**ATTENZIONE: Amazon risponde incorrettamente 'Inserire una mail valida o un numero di telefono' quando è richiesto il codice 2FA](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Vecchia configurazione" } } }, diff --git a/custom_components/alexa_media/.translations/nb.json b/custom_components/alexa_media/.translations/nb.json index 333797c9..87f5b062 100644 --- a/custom_components/alexa_media/.translations/nb.json +++ b/custom_components/alexa_media/.translations/nb.json @@ -14,38 +14,10 @@ "unknown_error": "Ukjent feil, vennligst rapporter logginfo" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon vil sende et push-varsel i henhold til meldingen nedenfor. Fullfør svaret før du fortsetter. \n{message}", - "title": "Alexa Media Player - Handling påkrevd" - }, - "authselect": { - "data": { - "authselectoption": "OTP-metode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Engangspassord" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Passord", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)" - }, - "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Bekreftelsesmetode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nVelg bekreftelsesmetode etter nummer. (f.eks. \"0\" eller \"1\") \n{message}", - "title": "Alexa Media Player - Bekreftelsesmetode" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Kode" - }, - "description": "**{email} - alexa.{url}** \nSkriv inn engangspassordet (OTP). \n>{message}", - "title": "Alexa Media Player - Tofaktorautentisering" - }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Avansert feilsøking", "email": "Epostadresse", "exclude_devices": "Ekskludert enhet (kommaseparert)", "hass_url": "Url to access Home Assistant", "include_devices": "Inkluder enhet (kommaseparert)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Passord", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Sekunder mellom skanninger", "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)", "url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)" }, - "description": "Bekreft informasjonen nedenfor. For eldre konfigurasjon, deaktiver alternativet \"Bruk innlogging proxy-metode\".", + "description": "Bekreft informasjonen nedenfor.", "title": "Alexa Media Player - Konfigurasjon" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "Avansert feilsøking", - "email": "Epostadresse", - "exclude_devices": "Ekskludert enhet (kommaseparert)", - "include_devices": "Inkluder enhet (kommaseparert)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Passord", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Sekunder mellom skanninger", - "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)", - "url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/.translations/nl.json b/custom_components/alexa_media/.translations/nl.json index 9a4afdbe..77558398 100644 --- a/custom_components/alexa_media/.translations/nl.json +++ b/custom_components/alexa_media/.translations/nl.json @@ -14,38 +14,10 @@ "unknown_error": "Onbekende fout, meld de loggegevens" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" - }, - "authselect": { - "data": { - "authselectoption": "Eenmalige Pincode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Eenmalige Pincode" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Paswoord", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "config::step::captcha::data::securitycode" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Verificatiemethode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nSelecteer de verificatiemethode (bv.`0` of `1`) \n{message}", - "title": "Alexa Media Player - Verificatiemethode" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "Verificatiecode" - }, - "description": "**{email} - alexa.{url}** \nGeef de verificatiecode in. \n{message}", - "title": "Alexa Media Player - Tweestapsverificatie" - }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Geavanceerd debuggen", "email": "E-mailadres", "exclude_devices": "Apparaten uitsluiten (Scheiding: komma)", "hass_url": "Url to access Home Assistant", "include_devices": "Apparaten toevoegen (Scheiding: komma)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Paswoord", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Aantal seconden tussen scans", "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Domeinnaam van Amazon regio (bv.amazon.co.uk)" }, "description": "Vul je gegevens in a.u.b.", "title": "Alexa Media Player - Configuratie" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "Geavanceerd debuggen", - "email": "E-mailadres", - "exclude_devices": "Apparaten uitsluiten (Scheiding: komma)", - "include_devices": "Apparaten toevoegen (Scheiding: komma)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Paswoord", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Aantal seconden tussen scans", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Domeinnaam van Amazon regio (bv.amazon.co.uk)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/.translations/pl.json b/custom_components/alexa_media/.translations/pl.json index e0121412..fcb2c462 100644 --- a/custom_components/alexa_media/.translations/pl.json +++ b/custom_components/alexa_media/.translations/pl.json @@ -14,38 +14,10 @@ "unknown_error": "Nieznany błąd, włącz zaawansowane debugowanie i zgłoś log z tego zdarzenia" }, "step": { - "action_required": { - "data": { - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" - }, - "description": "**{email} - alexa.{url}** \nAmazon wyśle powiadomienie push zgodnie z poniższą wiadomością. Przed kontynuowaniem odpowiedz na wiadomość. \n{message}", - "title": "Alexa Media Player — wymagane działanie" - }, - "authselect": { - "data": { - "authselectoption": "Metoda OTP", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player — hasło jednorazowe" - }, "captcha": { "data": { - "captcha": "Kod Captcha", - "password": "Hasło", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "securitycode": "Kod uwierzytelniania dwuskładnikowego" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player — kod Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Metoda weryfikacji", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" - }, - "description": "**{email} - alexa.{url}** \nWybierz metodę weryfikacji. (np., `0` lub `1`) \n{message}", - "title": "Alexa Media Player — metoda weryfikacji" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nCzy pomyślnie potwierdziłeś hasło jednorazowe z wbudowanej aplikacji uwierzytelniania dwuskładnikowego z Amazonu? \n >Kod hasła jednorazowego {message}", "title": "Alexa Media Player - Potwierdzanie hasła jednorazowego" }, - "twofactor": { - "data": { - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", - "securitycode": "Kod uwierzytelniania dwuskładnikowego" - }, - "description": "**{email} - alexa.{url}** \nWprowadź hasło jednorazowe (OTP). \n{message}", - "title": "Alexa Media Player — uwierzytelnianie dwuskładnikowe" - }, "user": { "data": { - "cookies_txt": "zawartość pliku cookies.txt", "debug": "Zaawansowane debugowanie", "email": "Adres email", "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", "hass_url": "URL dostępu do Home Assistanta", "include_devices": "Dodawane urządzenia (oddzielone przecinkami)", - "oauth_login": "Włącz metodę tokena OAuth aplikacji", "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)", "password": "Hasło", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "scan_interval": "Interwał skanowania (sekundy)", "securitycode": "Kod uwierzytelniania dwuskładnikowego (zalecany w celu uniknięcia problemów z logowaniem)", "url": "Region/domena Amazon (np. amazon.co.uk)" }, - "description": "Potwierdź poniższe informacje. W przypadku starszych konfiguracji wyłącz opcję 'Użyj metody logowania proxy'.", + "description": "Potwierdź poniższe informacje.", "title": "Alexa Media Player — konfiguracja" - }, - "user_legacy": { - "data": { - "cookies_txt": "zawartość pliku cookies.txt", - "debug": "Zaawansowane debugowanie", - "email": "Adres e-mail", - "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", - "include_devices": "Dodawane urządzenia (oddzielone przecinkami)", - "oauth_login": "Włącz metodę tokena OAuth aplikacji", - "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)", - "password": "Hasło", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", - "scan_interval": "Interwał skanowania (sekundy)", - "securitycode": "Kod uwierzytelniania dwuskładnikowego", - "url": "Region/domena Amazon (np. amazon.co.uk)" - }, - "description": "Wprowadź [dane](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Import pliku Cookie](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) może być najłatwiejszą metodą!** \n**OSTRZEŻENIE: Amazon nieprawidłowo zgłasza 'Wprowadź prawidłowy adres e-mail lub numer telefonu komórkowego', gdy wymagany jest [kod uwierzytelniania dwuskładnikowego](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player — starsza konfiguracja" } } }, diff --git a/custom_components/alexa_media/.translations/pt-BR.json b/custom_components/alexa_media/.translations/pt-BR.json index 1f3897e2..6d4b4804 100644 --- a/custom_components/alexa_media/.translations/pt-BR.json +++ b/custom_components/alexa_media/.translations/pt-BR.json @@ -14,38 +14,10 @@ "unknown_error": "Erro desconhecido, favor habilitar depuração avançada e informações de registro de relatório" }, "step": { - "action_required": { - "data": { - "proxy": "Usar método Login Proxy (Não requer 2FA)" - }, - "description": "** {email} - alexa. {url} **\n A Amazon enviará uma notificação por push de acordo com a mensagem abaixo. Por favor, responda completamente antes de continuar.\n {message}", - "title": "Alexa Media Player - Ação necessária" - }, - "authselect": { - "data": { - "authselectoption": "Método OTP", - "proxy": "Usar método Login Proxy (Não requer 2FA)" - }, - "description": "**{email} - alexa.{url}**\n{message}", - "title": "Alexa Media Player - Senha de uso único" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Senha", - "proxy": "Usar método Login Proxy (Não requer 2FA)", "securitycode": "Código 2FA (recomendado para evitar problemas de login)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Método de verificação", - "proxy": "Usar método Login Proxy (Não requer 2FA)" - }, - "description": "** {email} - alexa. {url} **\n Selecione o método de verificação por número. (por exemplo, `0` ou `1`)\n {message}", - "title": "Alexa Media Player - Método de verificação" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "** {email} - alexa. {url} **\n Você confirmou com sucesso um OTP da chave de aplicativo 2FA integrada com a Amazon?\n > Código OTP {message}", "title": "Alexa Media Player - Confirmação OTP" }, - "twofactor": { - "data": { - "proxy": "Usar método Login Proxy (Não requer 2FA)", - "securitycode": "Código 2FA" - }, - "description": "** {email} - alexa. {url} **\n Digite a senha de uso único (OTP).\n {message}", - "title": "Alexa Media Player - Autenticação de dois fatores" - }, "user": { "data": { - "cookies_txt": "Cookies.txt data", "debug": "Depuração avançada", "email": "Endereço de e-mail", "exclude_devices": "Dispositivos excluídos (separado por vírgula)", "hass_url": "Url para acesso ao Home Assistant", "include_devices": "Dispositivos incluídos (separado por vírgula)", - "oauth_login": "Habilitar o aplicativo para método auth-token", "otp_secret": "Chave de aplicativo 2FA integrada (gerar automaticamente códigos 2FA)", "password": "Senha", - "proxy": "Usar método Login Proxy (Não requer 2FA)", "scan_interval": "Segundos entre varreduras", "securitycode": "Código 2FA (recomendado para evitar problemas de login)", "url": "Domínio regional da Amazon (ex: amazon.co.uk)" }, - "description": "Por favor, confirme as informações abaixo. Para configuração legada, desative a opção `Usar método de proxy de login`.", + "description": "Por favor, confirme as informações abaixo.", "title": "Alexa Media Player - Configurações" - }, - "user_legacy": { - "data": { - "cookies_txt": "Cookies.txt data", - "debug": "Depuração avançada", - "email": "Endereço de e-mail", - "exclude_devices": "Dispositivos excluídos (separado por vírgula)", - "include_devices": "Dispositivos incluídos (separado por vírgula)", - "oauth_login": "Habilitar o aplicativo para método auth-token", - "otp_secret": "Chave de aplicativo 2FA integrada (gerar automaticamente códigos 2FA)", - "password": "Senha", - "proxy": "Usar método Login Proxy (Não requer 2FA)", - "scan_interval": "Segundos entre escaneamentos", - "securitycode": "Código 2FA (recomendado para evitar problemas de login)", - "url": "Domínio regional da Amazon (ex: amazon.co.uk)" - }, - "description": "Insira suas [informações](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Importação de cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) pode ser mais fácil!**\n **AVISO: A Amazon informa incorretamente 'Digite um e-mail ou número de celular válido' quando [o código 2FA é necessário](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication- para-sua-conta-amazon).**\n > {message}", - "title": "Alexa Media Player - Configurações legado" } } }, diff --git a/custom_components/alexa_media/.translations/pt.json b/custom_components/alexa_media/.translations/pt.json index 5cf76c7d..0ea685b6 100644 --- a/custom_components/alexa_media/.translations/pt.json +++ b/custom_components/alexa_media/.translations/pt.json @@ -14,38 +14,10 @@ "unknown_error": "Erro desconhecido, por favor habilite depuração avançada e informações de log de relatório" }, "step": { - "action_required": { - "data": { - "proxy": "Usar método de proxy de login (2FA não é necessário)" - }, - "description": "**{email} - alexa.{url}** \nA Amazon enviará uma notificação push de acordo com a mensagem abaixo. Por favor, responda completamente antes de continuar. \n{message}", - "title": "Alexa Media Player - Ação Necessária" - }, - "authselect": { - "data": { - "authselectoption": "Método OTP (senha de uso único)", - "proxy": "Usar método de proxy de login (2FA não é necessário)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Senha de uso único" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Senha", - "proxy": "Usar método de proxy de login (2FA não é necessário)", "securitycode": "Código 2FA (recomendado para evitar problemas de login)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Método de verificação", - "proxy": "Usar método de proxy de login (2FA não é necessário)" - }, - "description": "**{email} - alexa.{url}** \nPor favor, selecione o método de verificação por número. (ex. '0' ou '1') \n{message}", - "title": "Alexa Media Player - Método de verificação" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "** {email} - alexa. {url} **\n Você confirmou com sucesso uma senha de uso único na aplicação 2FA integrada com a Amazon?\n > Código OTP {message}", "title": "Alexa Media Player - Confirmação OTP" }, - "twofactor": { - "data": { - "proxy": "Usar método de proxy de login (2FA não é necessário)", - "securitycode": "Código 2FA" - }, - "description": "**{email} - alexa.{url}** \nDigite a senha de uso único (OTP). \n{message}", - "title": "Alexa Media Player - autenticação de dois fatores" - }, "user": { "data": { - "cookies_txt": "Dados de cookies.txt", "debug": "Depuração avançada", "email": "Endereço de e-mail", "exclude_devices": "Dispositivo excluído (separado por vírgula)", "hass_url": "URL para aceder o Home Assistant", "include_devices": "Dispositivo incluído (separado por vírgula)", - "oauth_login": "Habilitar método de aplicativo oauth-token", "otp_secret": "Chave de aplicativo 2FA integrada (gerar códigos 2FA automaticamente)", "password": "Senha", - "proxy": "Usar método de proxy de login (2FA não é necessário)", "scan_interval": "Segundos entre análises", "securitycode": "Código 2FA (recomendado para evitar problemas de login)", "url": "Região do domínio Amazon (ex. amazon.com.br)" }, - "description": "Por favor, confirme as informações abaixo. Para configuração de compatibilidade, desative a opção 'Usar método de proxy de login'.", + "description": "Por favor, confirme as informações abaixo.", "title": "Alexa Media Player - Configuração" - }, - "user_legacy": { - "data": { - "cookies_txt": "Dados de cookies.txt", - "debug": "Depuração avançada", - "email": "Endereço de e-mail", - "exclude_devices": "Dispositivo excluído (separado por vírgula)", - "include_devices": "Dispositivo incluído (separado por vírgula)", - "oauth_login": "Habilitar método de aplicativo oauth-token", - "otp_secret": "Chave de aplicativo 2FA integrada (gerar códigos 2FA automaticamente)", - "password": "Senha", - "proxy": "Usar método de proxy de login (2FA não é necessário)", - "scan_interval": "Segundos entre análises", - "securitycode": "Código 2FA (recomendado para evitar problemas de login)", - "url": "Região do domínio Amazon (ex. amazon.com.br)" - }, - "description": "Por favor, introduza a sua [informação](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) pode ser mais fácil!** \n**Aviso: a Amazon informa incorretamente 'Insira um e-mail ou número de celular válido' quando [2FA Code é necessário](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Configuração de Compatibilidade" } } }, diff --git a/custom_components/alexa_media/.translations/pt_BR.json b/custom_components/alexa_media/.translations/pt_BR.json index 2696cc29..2e8d00b0 100644 --- a/custom_components/alexa_media/.translations/pt_BR.json +++ b/custom_components/alexa_media/.translations/pt_BR.json @@ -8,7 +8,7 @@ "error": { "2fa_key_invalid": "Chave integrada 2FA inválida", "connection_error": "Erro de conexão; Verifique a sua conexão e tente novamente", - "hass_url_invalid": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL externa em Configuração -> Geral", + "unable_to_connect_hass_url": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL externa em Configuração -> Geral", "identifier_exists": "Email para URL Alexa já registrado", "invalid_credentials": "Credenciais inválidas", "unknown_error": "Erro desconhecido, favor habilitar a depuração avançada e reporte as informações de registro" diff --git a/custom_components/alexa_media/.translations/pt_PT.json b/custom_components/alexa_media/.translations/pt_PT.json index 5cf76c7d..2d7b4382 100644 --- a/custom_components/alexa_media/.translations/pt_PT.json +++ b/custom_components/alexa_media/.translations/pt_PT.json @@ -8,7 +8,7 @@ "error": { "2fa_key_invalid": "Chave 2FA integrada inválida", "connection_error": "Erro ao conectar; verifique a rede e tente novamente", - "hass_url_invalid": "Não foi possível conectar ao URL do Home Assistant. Verifique o URL externo em Configuração - > Geral", + "unable_to_connect_hass_url": "Não foi possível conectar ao URL do Home Assistant. Verifique o URL externo em Configuração - > Geral", "identifier_exists": "E-mail para URL Alexa já registado", "invalid_credentials": "Credenciais inválidas", "unknown_error": "Erro desconhecido, por favor habilite depuração avançada e informações de log de relatório" diff --git a/custom_components/alexa_media/.translations/ru.json b/custom_components/alexa_media/.translations/ru.json index ee10f828..e6aa3c69 100644 --- a/custom_components/alexa_media/.translations/ru.json +++ b/custom_components/alexa_media/.translations/ru.json @@ -14,38 +14,10 @@ "unknown_error": "Неизвестная ошибка, пожалуйста, сообщите информацию журнала" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa. {url}**\nAmazon отправит push-уведомление в соответствии с нижеприведенным сообщением. Пожалуйста, полностью ответьте, прежде чем продолжить.\n{message}", - "title": "Alexa Media Player - Требуемое действие" - }, - "authselect": { - "data": { - "authselectoption": "Одноразовый пароль", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Одноразовый Пароль" - }, "captcha": { "data": { - "captcha": "Капча", - "password": "Пароль", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA Code (recommended to avoid login issues)" - }, - "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}", - "title": "Alexa Media Player - Капча" - }, - "claimspicker": { - "data": { - "authselectoption": "Метод проверки", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}**\nПожалуйста, выберите способ проверки по номеру. (например, \" 0 \" или \" 1`)\n{message}", - "title": "Alexa Media Player - Метод проверки" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2-факторная авторизация" - }, - "description": "**{email} - alexa.{url}**\nВведите одноразовый пароль.\n{message}", - "title": "Alexa Media Player - Двух факторная идентификация" - }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Расширенные возможности отладки", "email": "Адрес электронной почты", "exclude_devices": "Исключенные устройства (через запятую)", "hass_url": "Url to access Home Assistant", "include_devices": "Включенные устройства (разделенное запятыми)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Пароль", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Секунды между сканированиями", "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Домен региона Amazon (например, amazon.co.uk)" }, - "description": "Пожалуйста, подтвердите информацию ниже. Для старой конфигурации отключите опцию `Use Login Proxy method`.", + "description": "Пожалуйста, подтвердите информацию ниже.", "title": "Alexa Media Player - Конфигурация" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "Расширенные возможности отладки", - "email": "Адрес электронной почты", - "exclude_devices": "Исключенные устройства (через запятую)", - "include_devices": "Включенные устройства (разделенное запятыми)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Пароль", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Секунды между сканированиями", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Домен региона Amazon (например, amazon.co.uk)" - }, - "description": "Пожалуйста, введите свои данные.{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/.translations/zh-Hans.json b/custom_components/alexa_media/.translations/zh-Hans.json index 4af369e3..2658d0e6 100644 --- a/custom_components/alexa_media/.translations/zh-Hans.json +++ b/custom_components/alexa_media/.translations/zh-Hans.json @@ -14,38 +14,10 @@ "unknown_error": "Unknown error, please report log info" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" - }, - "authselect": { - "data": { - "authselectoption": "OTP方式", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}**\n{message}", - "title": "Alexa Media Player - 一次性密码" - }, "captcha": { "data": { - "captcha": "验证码", - "password": "密码", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA Code (recommended to avoid login issues)" - }, - "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}", - "title": "Alexa Media Player-验证码" - }, - "claimspicker": { - "data": { - "authselectoption": "验证方式", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - 验证方法" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA代码" - }, - "description": "**{email} - Alexa.{url} ** \n输入一次性密码(OTP)。 \n {message}", - "title": "Alexa Media Player - Two Factor Authentication" - }, "user": { "data": { - "cookies_txt": "Cookie.txt数据", "debug": "高级调试", "email": "电子邮件地址", "exclude_devices": "Excluded device (comma separated)", "hass_url": "Url to access Home Assistant", "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "密码", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Seconds between scans", "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, - "description": "请确认以下信息。对于旧版配置,请禁用“使用登录代理方法”选项。", + "description": "请确认以下信息。", "title": "Alexa Media Player-配置" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "高级调试", - "email": "电子邮件地址", - "exclude_devices": "Excluded device (comma separated)", - "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "密码", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Seconds between scans", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Amazon region domain (e.g., amazon.co.uk)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/__init__.py b/custom_components/alexa_media/__init__.py index 3f604e3f..a02027ea 100644 --- a/custom_components/alexa_media/__init__.py +++ b/custom_components/alexa_media/__init__.py @@ -148,8 +148,12 @@ async def async_setup(hass, config, discovery_info=None): CONF_SCAN_INTERVAL: account[ CONF_SCAN_INTERVAL ].total_seconds(), - CONF_OAUTH: account.get(CONF_OAUTH, {}), - CONF_OTPSECRET: account.get(CONF_OTPSECRET, ""), + CONF_OAUTH: account.get( + CONF_OAUTH, entry.data.get(CONF_OAUTH, {}) + ), + CONF_OTPSECRET: account.get( + CONF_OTPSECRET, entry.data.get(CONF_OTPSECRET, "") + ), }, ) entry_found = True @@ -642,6 +646,7 @@ async def async_update_data() -> Optional[AlexaEntityData]: "access_token": login_obj.access_token, "refresh_token": login_obj.refresh_token, "expires_in": login_obj.expires_in, + "mac_dms": login_obj.mac_dms, }, }, ) @@ -652,7 +657,7 @@ async def process_notifications(login_obj, raw_notifications=None): """Process raw notifications json.""" if not raw_notifications: raw_notifications = await AlexaAPI.get_notifications(login_obj) - email: Text = login_obj.email + email: str = login_obj.email previous = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get( "notifications", {} ) @@ -1054,7 +1059,7 @@ async def ws_handler(message_obj): async def ws_open_handler(): """Handle websocket open.""" - email: Text = login_obj.email + email: str = login_obj.email _LOGGER.debug("%s: Websocket successfully connected", hide_email(email)) hass.data[DATA_ALEXAMEDIA]["accounts"][email][ "websocketerror" @@ -1069,7 +1074,7 @@ async def ws_close_handler(): This should attempt to reconnect up to 5 times """ - email: Text = login_obj.email + email: str = login_obj.email if login_obj.close_requested: _LOGGER.debug( "%s: Close requested; will not reconnect websocket", hide_email(email) @@ -1081,7 +1086,7 @@ async def ws_close_handler(): ) return errors: int = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"] - delay: int = 5 * 2 ** errors + delay: int = 5 * 2**errors last_attempt = hass.data[DATA_ALEXAMEDIA]["accounts"][email][ "websocket_lastattempt" ] @@ -1107,7 +1112,7 @@ async def ws_close_handler(): errors = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"] = ( hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"] + 1 ) - delay = 5 * 2 ** errors + delay = 5 * 2**errors errors = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"] await asyncio.sleep(delay) if not websocket_enabled: @@ -1128,7 +1133,7 @@ async def ws_error_handler(message): the websocket and determine if a reconnect should be done. By specification, websockets will issue a close after every error. """ - email: Text = login_obj.email + email: str = login_obj.email errors = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"] _LOGGER.debug( "%s: Received websocket error #%i %s: type %s", @@ -1245,7 +1250,7 @@ async def async_unload_entry(hass, entry) -> bool: return True -async def close_connections(hass, email: Text) -> None: +async def close_connections(hass, email: str) -> None: """Clear open aiohttp connections for email.""" if ( email not in hass.data[DATA_ALEXAMEDIA]["accounts"] @@ -1293,7 +1298,7 @@ async def test_login_status(hass, config_entry, login) -> bool: account = config_entry.data _LOGGER.debug("Logging in: %s %s", obfuscate(account), in_progess_instances(hass)) _LOGGER.debug("Login stats: %s", login.stats) - message: Text = f"Reauthenticate {login.email} on the [Integrations](/config/integrations) page. " + message: str = f"Reauthenticate {login.email} on the [Integrations](/config/integrations) page. " if login.stats.get("login_timestamp") != datetime(1, 1, 1): elaspsed_time: str = str(datetime.now() - login.stats.get("login_timestamp")) api_calls: int = login.stats.get("api_calls") diff --git a/custom_components/alexa_media/config_flow.py b/custom_components/alexa_media/config_flow.py index d94281de..8bba88f3 100644 --- a/custom_components/alexa_media/config_flow.py +++ b/custom_components/alexa_media/config_flow.py @@ -14,7 +14,7 @@ import logging from typing import Any, Dict, List, Optional, Text -from aiohttp import ClientConnectionError, ClientSession, web, web_response +from aiohttp import ClientConnectionError, ClientSession, InvalidURL, web, web_response from aiohttp.web_exceptions import HTTPBadRequest from alexapy import ( AlexaLogin, @@ -50,6 +50,7 @@ CONF_INCLUDE_DEVICES, CONF_OAUTH, CONF_OTPSECRET, + CONF_PROXY_WARNING, CONF_QUEUE_DELAY, CONF_SECURITYCODE, CONF_TOTP_REGISTER, @@ -103,7 +104,7 @@ def __init__(self): _LOGGER.info(STARTUP) _LOGGER.info("Loaded alexapy==%s", alexapy_version) self.login = None - self.securitycode: Optional[Text] = None + self.securitycode: Optional[str] = None self.automatic_steps: int = 0 self.config = OrderedDict() self.proxy_schema = None @@ -123,6 +124,9 @@ def __init__(self): self.totp_register = OrderedDict( [(vol.Optional(CONF_TOTP_REGISTER, default=False), bool)] ) + self.proxy_warning = OrderedDict( + [(vol.Optional(CONF_PROXY_WARNING, default=False), bool)] + ) async def async_step_import(self, import_config): """Import a config entry from configuration.yaml.""" @@ -132,7 +136,7 @@ async def async_step_user(self, user_input=None): """Provide a proxy for login.""" self._save_user_input_to_config(user_input=user_input) try: - hass_url: Text = get_url(self.hass, prefer_external=True) + hass_url: str = get_url(self.hass, prefer_external=True) except NoURLAvailableError: hass_url = "" self.proxy_schema = OrderedDict( @@ -222,6 +226,7 @@ async def async_step_user(self, user_input=None): outputpath=self.hass.config.path, debug=self.config[CONF_DEBUG], otp_secret=self.config.get(CONF_OTPSECRET, ""), + oauth=self.config.get(CONF_OAUTH, {}), uuid=uuid, oauth_login=True, ) @@ -239,35 +244,39 @@ async def async_step_user(self, user_input=None): errors={"base": "2fa_key_invalid"}, description_placeholders={"message": ""}, ) - hass_url: Text = user_input.get(CONF_HASS_URL) + hass_url: str = user_input.get(CONF_HASS_URL) hass_url_valid: bool = False + hass_url_error: str = "" async with ClientSession() as session: try: async with session.get(hass_url) as resp: hass_url_valid = resp.status == 200 - except ClientConnectionError: + except (ClientConnectionError) as err: + hass_url_valid = False + hass_url_error = str(err) + except (InvalidURL) as err: hass_url_valid = False + hass_url_error = str(err.__cause__) if not hass_url_valid: _LOGGER.debug( "Unable to connect to provided Home Assistant url: %s", hass_url ) return self.async_show_form( - step_id="user", - errors={"base": "hass_url_invalid"}, - description_placeholders={"message": ""}, - ) - if not self.proxy: - self.proxy = AlexaProxy( - self.login, str(URL(hass_url).with_path(AUTH_PROXY_PATH)) - ) - # Swap the login object - self.proxy.change_login(self.login) + step_id="proxy_warning", + data_schema=vol.Schema(self.proxy_warning), + errors={}, + description_placeholders={ + "email": self.login.email, + "hass_url": hass_url, + "error": hass_url_error + }, + ) if ( user_input and user_input.get(CONF_OTPSECRET) and user_input.get(CONF_OTPSECRET).replace(" ", "") ): - otp: Text = self.login.get_totp_token() + otp: str = self.login.get_totp_token() if otp: _LOGGER.debug("Generating OTP from %s", otp) return self.async_show_form( @@ -289,6 +298,19 @@ async def async_step_start_proxy(self, user_input=None): hide_email(self.login.email), self.login.url, ) + if not self.proxy: + try: + self.proxy = AlexaProxy( + self.login, str(URL(self.config.get(CONF_HASS_URL)).with_path(AUTH_PROXY_PATH)) + ) + except ValueError as ex: + return self.async_show_form( + step_id="user", + errors={"base": "invalid_url"}, + description_placeholders={"message": str(ex)}, + ) + # Swap the login object + self.proxy.change_login(self.login) if not self.proxy_view: self.proxy_view = AlexaMediaAuthorizationProxyView(self.proxy.all_handler) else: @@ -297,7 +319,7 @@ async def async_step_start_proxy(self, user_input=None): self.hass.http.register_view(AlexaMediaAuthorizationCallbackView()) self.hass.http.register_view(self.proxy_view) callback_url = ( - URL(self.config["hass_url"]) + URL(self.config[CONF_HASS_URL]) .with_path(AUTH_CALLBACK_PATH) .with_query({"flow_id": self.flow_id}) ) @@ -387,7 +409,7 @@ async def async_step_user_legacy(self, user_input=None): and user_input.get(CONF_OTPSECRET) and user_input.get(CONF_OTPSECRET).replace(" ", "") ): - otp: Text = self.login.get_totp_token() + otp: str = self.login.get_totp_token() if otp: _LOGGER.debug("Generating OTP from %s", otp) return self.async_show_form( @@ -435,15 +457,28 @@ async def async_step_user_legacy(self, user_input=None): return self.async_show_form( step_id="user_legacy", errors={"base": "unknown_error"}, + description_placeholders={"message": str(ex)}, + ) + + async def async_step_proxy_warning(self, user_input=None): + """Handle the proxy_warning for the config flow.""" + self._save_user_input_to_config(user_input=user_input) + if user_input and user_input.get(CONF_PROXY_WARNING) is False: + _LOGGER.debug("User is not accepting warning, go back") + return self.async_show_form( + step_id="user", + data_schema=vol.Schema(self.proxy_schema), description_placeholders={"message": ""}, ) + _LOGGER.debug("User is ignoring proxy warning; starting proxy anyway") + return await self.async_step_start_proxy(user_input) async def async_step_totp_register(self, user_input=None): """Handle the input processing of the config flow.""" self._save_user_input_to_config(user_input=user_input) - if user_input and user_input.get("registered") is False: + if user_input and user_input.get(CONF_TOTP_REGISTER) is False: _LOGGER.debug("Not registered, regenerating") - otp: Text = self.login.get_totp_token() + otp: str = self.login.get_totp_token() if otp: _LOGGER.debug("Generating OTP from %s", otp) return self.async_show_form( @@ -523,6 +558,7 @@ async def _test_login(self): "access_token": login.access_token, "refresh_token": login.refresh_token, "expires_in": login.expires_in, + "mac_dms": login.mac_dms } self.hass.data.setdefault( DATA_ALEXAMEDIA, @@ -567,7 +603,7 @@ async def _test_login(self): "Creating config_flow to request 2FA. Saved security code %s", self.securitycode, ) - generated_securitycode: Text = login.get_totp_token() + generated_securitycode: str = login.get_totp_token() if ( self.securitycode or generated_securitycode ) and self.automatic_steps < 2: @@ -783,12 +819,12 @@ async def get(self, request: web.Request): class AlexaMediaAuthorizationProxyView(HomeAssistantView): """Handle proxy connections.""" - url: Text = AUTH_PROXY_PATH - extra_urls: List[Text] = [f"{AUTH_PROXY_PATH}/{{tail:.*}}"] - name: Text = AUTH_PROXY_NAME + url: str = AUTH_PROXY_PATH + extra_urls: List[str] = [f"{AUTH_PROXY_PATH}/{{tail:.*}}"] + name: str = AUTH_PROXY_NAME requires_auth: bool = False handler: web.RequestHandler = None - known_ips: Dict[Text, datetime.datetime] = {} + known_ips: Dict[str, datetime.datetime] = {} auth_seconds: int = 300 def __init__(self, handler: web.RequestHandler): diff --git a/custom_components/alexa_media/const.py b/custom_components/alexa_media/const.py index 72ec35c9..ba627e2e 100644 --- a/custom_components/alexa_media/const.py +++ b/custom_components/alexa_media/const.py @@ -8,7 +8,7 @@ """ from datetime import timedelta -__version__ = "4.0.1" +__version__ = "4.1.0" PROJECT_URL = "https://github.com/custom-components/alexa_media_player/" ISSUE_URL = f"{PROJECT_URL}issues" @@ -42,6 +42,7 @@ CONF_SECURITYCODE = "securitycode" CONF_OTPSECRET = "otp_secret" CONF_PROXY = "proxy" +CONF_PROXY_WARNING = "proxy_warning" CONF_TOTP_REGISTER = "registered" CONF_OAUTH = "oauth" DATA_LISTENER = "listener" @@ -69,6 +70,15 @@ "XXXX-WXX-7": "Every Sunday", } +RECURRING_DAY = { + "MO": 1, + "TU": 2, + "WE": 3, + "TH": 4, + "FR": 5, + "SA": 6, + "SU": 7, +} RECURRING_PATTERN_ISO_SET = { None: {}, "P1D": {1, 2, 3, 4, 5, 6, 7}, diff --git a/custom_components/alexa_media/manifest.json b/custom_components/alexa_media/manifest.json index aa953dfc..4dbc698f 100644 --- a/custom_components/alexa_media/manifest.json +++ b/custom_components/alexa_media/manifest.json @@ -1,12 +1,12 @@ { "domain": "alexa_media", "name": "Alexa Media Player", - "version": "4.0.1", + "version": "4.1.0", "config_flow": true, "documentation": "https://github.com/custom-components/alexa_media_player/wiki", "issue_tracker": "https://github.com/custom-components/alexa_media_player/issues", "dependencies": ["persistent_notification", "http"], "codeowners": ["@alandtse", "@keatontaylor"], - "requirements": ["alexapy==1.25.6", "packaging>=20.3", "wrapt>=1.12.1"], + "requirements": ["alexapy==1.26.1", "packaging>=20.3", "wrapt>=1.12.1"], "iot_class": "cloud_polling" } diff --git a/custom_components/alexa_media/notify.py b/custom_components/alexa_media/notify.py index 51b02a34..c8e76ef5 100644 --- a/custom_components/alexa_media/notify.py +++ b/custom_components/alexa_media/notify.py @@ -146,22 +146,44 @@ def targets(self): for email, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items(): if "entities" not in account_dict: return devices + last_called_entity = None for _, entity in account_dict["entities"]["media_player"].items(): entity_name = (entity.entity_id).split(".")[1] devices[entity_name] = entity.unique_id if self.last_called and entity.extra_state_attributes.get( "last_called" ): - entity_name_last_called = ( - f"last_called{'_'+ email if entity_name[-1:].isdigit() else ''}" - ) - _LOGGER.debug( - "%s: Creating last_called target %s using %s", - hide_email(email), - entity_name_last_called, - entity, - ) - devices[entity_name_last_called] = entity.unique_id + if last_called_entity is None: + _LOGGER.debug( + "%s: Found last_called %s called at %s", + hide_email(email), + entity, + entity.extra_state_attributes.get("last_called_timestamp"), + ) + last_called_entity = entity + elif (last_called_entity.extra_state_attributes.get("last_called_timestamp") + < entity.extra_state_attributes.get("last_called_timestamp") + ): + _LOGGER.debug( + "%s: Found newer last_called %s called at %s", + hide_email(email), + entity, + entity.extra_state_attributes.get("last_called_timestamp"), + ) + last_called_entity = entity + if last_called_entity is not None: + entity_name = (last_called_entity.entity_id).split(".")[1] + entity_name_last_called = ( + f"last_called{'_'+ email if entity_name[-1:].isdigit() else ''}" + ) + _LOGGER.debug( + "%s: Creating last_called target %s using %s called at %s", + hide_email(email), + entity_name_last_called, + last_called_entity, + last_called_entity.extra_state_attributes.get("last_called_timestamp"), + ) + devices[entity_name_last_called] = last_called_entity.unique_id return devices @property diff --git a/custom_components/alexa_media/sensor.py b/custom_components/alexa_media/sensor.py index a9990695..5d05ccc3 100644 --- a/custom_components/alexa_media/sensor.py +++ b/custom_components/alexa_media/sensor.py @@ -10,6 +10,7 @@ import logging from typing import Callable, List, Optional, Text # noqa pylint: disable=unused-import +from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( DEVICE_CLASS_TIMESTAMP, STATE_UNAVAILABLE, @@ -18,7 +19,6 @@ ) from homeassistant.exceptions import ConfigEntryNotReady, NoEntitySpecifiedError from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import dt @@ -37,6 +37,7 @@ from .alexa_entity import parse_temperature_from_coordinator from .const import ( CONF_EXTENDED_ENTITY_DISCOVERY, + RECURRING_DAY, RECURRING_PATTERN, RECURRING_PATTERN_ISO_SET, ) @@ -181,7 +182,7 @@ def lookup_device_info(account_dict, device_serial): return None -class TemperatureSensor(CoordinatorEntity): +class TemperatureSensor(SensorEntity, CoordinatorEntity): """A temperature sensor reported by an Echo.""" def __init__(self, coordinator, entity_id, name, media_player_device_id): @@ -221,14 +222,14 @@ def unique_id(self): return self.alexa_entity_id + "_temperature" -class AlexaMediaNotificationSensor(Entity): +class AlexaMediaNotificationSensor(SensorEntity): """Representation of Alexa Media sensors.""" def __init__( self, client, n_dict, - sensor_property: Text, + sensor_property: str, account, name="Next Notification", icon=None, @@ -252,9 +253,9 @@ def __init__( self._tracker: Optional[Callable] = None self._state: Optional[datetime.datetime] = None self._dismissed: Optional[datetime.datetime] = None - self._status: Optional[Text] = None - self._amz_id: Optional[Text] = None - self._version: Optional[Text] = None + self._status: Optional[str] = None + self._amz_id: Optional[str] = None + self._version: Optional[str] = None def _process_raw_notifications(self): self._all = ( @@ -356,35 +357,47 @@ def _fix_alarm_date_time(self, value): def _update_recurring_alarm(self, value): _LOGGER.debug("Sensor value %s", value) - alarm = value[1][self._sensor_property] + next_item = value[1] + alarm = next_item[self._sensor_property] reminder = None - if isinstance(value[1][self._sensor_property], (int, float)): + recurrence = [] + if isinstance(next_item[self._sensor_property], (int, float)): reminder = True alarm = dt.as_local( self._round_time( datetime.datetime.fromtimestamp(alarm / 1000, tz=LOCAL_TIMEZONE) ) ) - alarm_on = value[1]["status"] == "ON" - recurring_pattern = value[1].get("recurringPattern") + alarm_on = next_item["status"] == "ON" + r_rule_data = next_item.get("rRuleData") + if r_rule_data: # the new recurrence pattern; https://github.com/custom-components/alexa_media_player/issues/1608 + next_trigger_times = r_rule_data.get("nextTriggerTimes") + weekdays = r_rule_data.get("byWeekDays") + if next_trigger_times: + alarm = next_trigger_times[0] + elif weekdays: + for day in weekdays: + recurrence.append(RECURRING_DAY[day]) + else: + recurring_pattern = next_item.get("recurringPattern") + recurrence = RECURRING_PATTERN_ISO_SET.get(recurring_pattern) while ( alarm_on - and recurring_pattern - and RECURRING_PATTERN_ISO_SET.get(recurring_pattern) - and alarm.isoweekday not in RECURRING_PATTERN_ISO_SET[recurring_pattern] + and recurrence + and alarm.isoweekday not in recurrence and alarm < dt.now() ): alarm += datetime.timedelta(days=1) if reminder: alarm = dt.as_timestamp(alarm) * 1000 - if alarm != value[1][self._sensor_property]: + if alarm != next_item[self._sensor_property]: _LOGGER.debug( "%s with recurrence %s set to %s", - value[1]["type"], - RECURRING_PATTERN.get(recurring_pattern), + next_item["type"], + recurrence, alarm, ) - value[1][self._sensor_property] = alarm + next_item[self._sensor_property] = alarm return value @staticmethod diff --git a/custom_components/alexa_media/strings.json b/custom_components/alexa_media/strings.json index 0844ad31..55e880b0 100644 --- a/custom_components/alexa_media/strings.json +++ b/custom_components/alexa_media/strings.json @@ -4,9 +4,10 @@ "connection_error": "Error connecting; check network and retry", "identifier_exists": "Email for Alexa URL already registered", "invalid_credentials": "Invalid credentials", + "invalid_url": "URL is invalid: {message}", "2fa_key_invalid": "Invalid Built-In 2FA key", - "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", - "unknown_error": "Unknown error, please enable advanced debugging and report log info" + "unable_to_connect_hass_url": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", + "unknown_error": "Unknown error: {message}" }, "step": { "user": { @@ -15,7 +16,7 @@ "email": "Email Address", "url": "Amazon region domain (e.g., amazon.co.uk)", "hass_url": "Url to access Home Assistant", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", + "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes). This not six digits long.", "include_devices": "Included device (comma separated)", "exclude_devices": "Excluded device (comma separated)", "debug": "Advanced debugging", @@ -26,33 +27,12 @@ "description": "Please confirm the information below. For legacy configuration, disable `Use Login Proxy method` option.", "title": "Alexa Media Player - Configuration" }, - "user_legacy": { + "proxy_warning": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "password": "Password", - "email": "Email Address", - "securitycode": "2FA Code (recommended to avoid login issues)", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "url": "Amazon region domain (e.g., amazon.co.uk)", - "include_devices": "Included device (comma separated)", - "exclude_devices": "Excluded device (comma separated)", - "debug": "Advanced debugging", - "scan_interval": "Seconds between scans", - "cookies_txt": "Cookies.txt data", - "oauth_login": "Enable oauth-token app method" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" - }, - "captcha": { - "data": { - "password": "Password", - "securitycode": "2FA Code (recommended to avoid login issues)", - "captcha": "Captcha", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy_warning": "Ignore and Continue - I understand that no support for login issues are provided for bypassing this warning." }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" + "description": "The HA server cannot connect to the URL provided: {hass_url}.\n> {error}\n\nTo fix this, please confirm your **HA server** can reach {hass_url}. This field is from the External Url under Configuration -> General but you can try your internal url.\n\nIf you are **certain** your client can reach this url, you can bypass this warning.", + "title": "Alexa Media Player - Unable to Connect to HA URL" }, "totp_register": { "data": { @@ -60,43 +40,12 @@ }, "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" - }, - "twofactor": { - "data": { - "securitycode": "2FA Code", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nEnter the One Time Password (OTP). \n{message}", - "title": "Alexa Media Player - Two Factor Authentication" - }, - "authselect": { - "data": { - "authselectoption": "OTP method", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - One Time Password" - }, - "claimspicker": { - "data": { - "authselectoption": "Verification method", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - Verification Method" - }, - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" } }, "abort": { "forgot_password": "The Forgot Password page was detected. This normally is the result of too may failed logins. Amazon may require action before a relogin can be attempted.", "login_failed": "Alexa Media Player failed to login.", - "reauth_successful": "Alexa Media Player successfully reauthenticated." + "reauth_successful": "Alexa Media Player successfully reauthenticated. Please ignore the \"Aborted\" message from HA." } }, "options": { diff --git a/custom_components/alexa_media/translations/ar.json b/custom_components/alexa_media/translations/ar.json new file mode 100644 index 00000000..216f8f32 --- /dev/null +++ b/custom_components/alexa_media/translations/ar.json @@ -0,0 +1,57 @@ +{ + "config": { + "abort": { + "forgot_password": "The Forgot Password page was detected. This normally is the result of too may failed logins. Amazon may require action before a relogin can be attempted.", + "login_failed": "Alexa Media Player failed to login.", + "reauth_successful": "Alexa Media Player successfully reauthenticated." + }, + "error": { + "2fa_key_invalid": "Invalid Built-In 2FA key", + "connection_error": "Error connecting; check network and retry", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the External Url under Configuration -> General", + "identifier_exists": "Email for Alexa URL already registered", + "invalid_credentials": "Invalid credentials", + "unknown_error": "Unknown error, please enable advanced debugging and report log info" + }, + "step": { + "captcha": { + "data": { + "securitycode": "2FA Code (recommended to avoid login issues)" + } + }, + "totp_register": { + "data": { + "registered": "OTP from the Built-in 2FA App Key confirmed successfully." + }, + "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", + "title": "Alexa Media Player - OTP Confirmation" + }, + "user": { + "data": { + "debug": "Advanced debugging", + "email": "Email Address", + "exclude_devices": "Excluded device (comma separated)", + "hass_url": "Url to access Home Assistant", + "include_devices": "Included device (comma separated)", + "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", + "password": "Password", + "scan_interval": "Seconds between scans", + "securitycode": "2FA Code (recommended to avoid login issues)", + "url": "Amazon region domain (e.g., amazon.co.uk)" + }, + "description": "Please confirm the information below.", + "title": "Alexa Media Player - Configuration" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "extended_entity_discovery": "Include devices connected via Echo", + "queue_delay": "Seconds to wait to queue commands together" + } + } + } + } +} diff --git a/custom_components/alexa_media/translations/de.json b/custom_components/alexa_media/translations/de.json index 15a60736..39e2fba1 100644 --- a/custom_components/alexa_media/translations/de.json +++ b/custom_components/alexa_media/translations/de.json @@ -14,38 +14,10 @@ "unknown_error": "Unbekannter Fehler, bitte Log-Info melden" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon sendet eine Push-Benachrichtigung je folgender Nachricht. Bitte antworte sie vollständig, bevor Du fortfährst. \n{message}", - "title": "Alexa Media Player - Aktion erforderlich" - }, - "authselect": { - "data": { - "authselectoption": "Einmal Pin Methode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Einmal Pin Passwort" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Passwort", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Verifizierungs Methode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nBitte Verifizierungs Methode auswählen. (z.B., `0` oder `1`) \n{message}", - "title": "Alexa Media Player - Verifizierungs Methode" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code" - }, - "description": "**{email} - alexa.{url}** \nDen Zwei Faktor Pin eingeben. \n{message}", - "title": "Alexa Media Player - Zwei Faktor Authentifizierung" - }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Erweitertes debugging", "email": "Email Adresse", "exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)", "hass_url": "Url to access Home Assistant", "include_devices": "Eingebundene Geräte (komma getrennnt)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Passwort", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Sekunden zwischen den Scans", "securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)", "url": "Amazon Region (z.B., amazon.de)" }, "description": "Bitte geben Sie ihre Informationen ein.", "title": "Alexa Media Player - Konfiguration" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "Erweitertes debugging", - "email": "Email Adresse", - "exclude_devices": "Ausgeschlossene Geräte (komma getrennnt)", - "include_devices": "Eingebundene Geräte (komma getrennnt)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Passwort", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Sekunden zwischen den Scans", - "securitycode": "2FA Code (empfohlen, um Anmeldeprobleme zu vermeiden)", - "url": "Amazon Region (z.B., amazon.de)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/translations/en.json b/custom_components/alexa_media/translations/en.json index f52044a0..216f8f32 100644 --- a/custom_components/alexa_media/translations/en.json +++ b/custom_components/alexa_media/translations/en.json @@ -14,38 +14,10 @@ "unknown_error": "Unknown error, please enable advanced debugging and report log info" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" - }, - "authselect": { - "data": { - "authselectoption": "OTP method", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - One Time Password" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA Code (recommended to avoid login issues)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Verification method", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - Verification Method" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code" - }, - "description": "**{email} - alexa.{url}** \nEnter the One Time Password (OTP). \n{message}", - "title": "Alexa Media Player - Two Factor Authentication" - }, "user": { "data": { - "cookies_txt": "Cookies.txt data", "debug": "Advanced debugging", "email": "Email Address", "exclude_devices": "Excluded device (comma separated)", "hass_url": "Url to access Home Assistant", "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Seconds between scans", "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, - "description": "Please confirm the information below. For legacy configuration, disable `Use Login Proxy method` option.", + "description": "Please confirm the information below.", "title": "Alexa Media Player - Configuration" - }, - "user_legacy": { - "data": { - "cookies_txt": "Cookies.txt data", - "debug": "Advanced debugging", - "email": "Email Address", - "exclude_devices": "Excluded device (comma separated)", - "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Seconds between scans", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Amazon region domain (e.g., amazon.co.uk)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/translations/es.json b/custom_components/alexa_media/translations/es.json index d1ddc7a7..644bc547 100644 --- a/custom_components/alexa_media/translations/es.json +++ b/custom_components/alexa_media/translations/es.json @@ -11,41 +11,13 @@ "hass_url_invalid": "No se puede conectar a la url de Home Assistant. Compruebe la dirección URL externa en Configuración -> General", "identifier_exists": "Correo electrónico para la URL de Alexa ya registrado", "invalid_credentials": "Credenciales no válidas", - "unknown_error": "Error desconocido, por favor revisa los registros en Home Assistant y reporta el error si es necesario." + "unknown_error": "Error desconocido, habilite la depuración avanzada e informe la información de registro" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon enviará una notificación a tu dispositivo vinculado. Completa los pasos descritos antes de continuar.\n{message}", - "title": "Alexa Media Player - Acción requerida" - }, - "authselect": { - "data": { - "authselectoption": "Clave OTP", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Contraseña de un solo uso" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Contraseña", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Método de verificación", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nSeleccione el método de verificación por número. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - Método de verificación" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code" - }, - "description": "**{email} - alexa.{url}** \nIngrese la contraseña de un solo uso (OTP). \n{message}", - "title": "Alexa Media Player - Autenticación de dos factores" - }, "user": { "data": { - "cookies_txt": "Datos de Cookies.txt", "debug": "Depuración avanzada", "email": "Dirección de correo electrónico", "exclude_devices": "Dispositivo excluido (separado por comas)", - "hass_url": "Url to access Home Assistant", + "hass_url": "Url para acceder a Home Assistant", "include_devices": "Dispositivo incluido (separado por comas)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Contraseña", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Segundos entre escaneos", "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)", "url": "Región del dominio de Amazon (por ejemplo, amazon.es)" }, - "description": "Confirme la siguiente información. Para la configuración heredada, desactive la opción `Usar método de proxy de inicio de sesión`.", + "description": "Confirme la siguiente información.", "title": "Alexa Media Player - Configuración" - }, - "user_legacy": { - "data": { - "cookies_txt": "Datos de Cookies.txt", - "debug": "Depuración avanzada", - "email": "Dirección de correo electrónico", - "exclude_devices": "Dispositivo excluido (separado por comas)", - "include_devices": "Dispositivo incluido (separado por comas)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Contraseña", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Segundos entre escaneos", - "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)", - "url": "Región del dominio de Amazon (por ejemplo, amazon.es)" - }, - "description": "Por favor introduce tu [información](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **El método más rápido es [Importar cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import).** \n**ADVERTENCIA: Amazon informará 'Introduce un correo electrónico o número de teléfono válido' si tu cuenta utiliza [códigos 2FA - Segundo Factor de Autenticación](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/translations/fr.json b/custom_components/alexa_media/translations/fr.json index 6d73c4e9..f466b335 100644 --- a/custom_components/alexa_media/translations/fr.json +++ b/custom_components/alexa_media/translations/fr.json @@ -14,38 +14,10 @@ "unknown_error": "Erreur inconnue, veuillez signaler les informations du journal" }, "step": { - "action_required": { - "data": { - "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)" - }, - "description": "** {email} - alexa. {url} ** \n Amazon enverra une notification push conformément au message ci-dessous. Veuillez répondre complètement avant de continuer. \n {message}", - "title": "Alexa Media Player - Action requise" - }, - "authselect": { - "data": { - "authselectoption": "OTP méthode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Mot de passe unique" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Mot de passe", - "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)", "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Méthode de vérification", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nVeuillez sélectionner la méthode de vérification par numéro. (exemple., `0` ou `1`) \n{message}", - "title": "Alexa Media Player - Méthode de vérification" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "** {email} - alexa. {url} **\n Avez-vous confirmé avec succès un OTP à partir de la clé d'application 2FA intégrée avec Amazon?\n > Code OTP {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)", - "securitycode": "2FA Code" - }, - "description": "**{email} - alexa.{url}** \nEntrez le mot de passe unique (OTP). \n{message}", - "title": "Alexa Media Player - Authentification à deux facteurs" - }, "user": { "data": { - "cookies_txt": "Données cookies.txt", "debug": "Débogage avancé", "email": "Adresse Email", "exclude_devices": "Appareil exclu (séparé par des virgules)", "hass_url": "Url to access Home Assistant", "include_devices": "Appareil inclus (séparé par des virgules)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)", "password": "Mot de passe", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Secondes entre les analyses", "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)", "url": "Domaine de la région Amazon (exemple, amazon.fr)" }, - "description": "Veuillez confirmer les informations ci-dessous. Pour la configuration héritée, désactivez l'option `Utiliser la méthode proxy de connexion`.", + "description": "Veuillez confirmer les informations ci-dessous.", "title": "Alexa Media Player - Configuration" - }, - "user_legacy": { - "data": { - "cookies_txt": "Données cookies.txt", - "debug": "Débogage avancé", - "email": "Adresse Email", - "exclude_devices": "Appareil exclu (séparé par des virgules)", - "include_devices": "Appareil inclus (séparé par des virgules)", - "oauth_login": "Activer la méthode d'application oauth-token", - "otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)", - "password": "Mot de passe", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Secondes entre les analyses", - "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)", - "url": "Domaine de la région Amazon (exemple, amazon.fr)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/translations/it.json b/custom_components/alexa_media/translations/it.json index f48012a9..318c7b5a 100644 --- a/custom_components/alexa_media/translations/it.json +++ b/custom_components/alexa_media/translations/it.json @@ -14,38 +14,10 @@ "unknown_error": "Errore sconosciuto, si prega di abilitare il debug avanzato e riportare i log informativi" }, "step": { - "action_required": { - "data": { - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" - }, - "description": "**{email} - alexa. {url}**\n Amazon invierà una notifica push per il seguente messaggio. Si prega di rispondere completamente prima di continuare.\n {message}", - "title": "Alexa Media Player - Azione Richiesta" - }, - "authselect": { - "data": { - "authselectoption": "Metodo password usa e getta (OTP)", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" - }, - "description": "**{email} - alexa.{url}**\n{message}", - "title": "Alexa Media Player - Password Usa e Getta (One Time Password)" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Password", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Metodo di verifica", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" - }, - "description": "**{email} - alexa.{url}**\nSelezionare un metodo di verifica con un numero. (ad es., `0` o `1`)\n{message}", - "title": "Alexa Media Player - Metodo di verifica" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}**\nHai confermato con successo una chiave usa e getta (OTP) dall'applicazione 2FA integrata con Amazon?\n>Codice OTP {message}", "title": "Alexa Media Player - Conferma OTP" }, - "twofactor": { - "data": { - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", - "securitycode": "Codice autenticazione a due fattori (2FA)" - }, - "description": "**{email} - alexa.{url}** \nInserisci la password usa e getta (OTP). \n{message}", - "title": "Alexa Media Player - Autenticazione a Due Fattori" - }, "user": { "data": { - "cookies_txt": "Dati cookies.txt", "debug": "Debug avanzato", "email": "Indirizzo email", "exclude_devices": "Dispositivi da escludere (separati da virgola)", "hass_url": "URL per accedere a Home Assistant", "include_devices": "Dispositivi da includere (separati da virgola)", - "oauth_login": "Abilita metodo app oauth-token", "otp_secret": "Chiave dell'app 2FA integrata (generazione automatica di codici 2FA)", "password": "Password", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", "scan_interval": "Tempo in secondi fra le scansioni", "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)", "url": "Regione del dominio Amazon (ad es., amazon.it)" }, - "description": "Confermare le informazioni sottostanti. Per le vecchie configurazioni, disabilitare l'opzione `Usa Proxy come metodo di login`.", + "description": "Confermare le informazioni sottostanti.", "title": "Alexa Media Player - Configurazione" - }, - "user_legacy": { - "data": { - "cookies_txt": "Dati cookies.txt", - "debug": "Debug avanzato", - "email": "Indirizzo email", - "exclude_devices": "Dispositivi da escludere (separati da virgola)", - "include_devices": "Dispositivi da includere (separati da virgola)", - "oauth_login": "Abilitare il metodo oauth-token app", - "otp_secret": "Chiave 2FA integrata (genera automaticamente i codici 2FA)", - "password": "Password", - "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", - "scan_interval": "Tempo in secondi fra le scansioni", - "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)", - "url": "Regione del dominio Amazon (ad es., amazon.it)" - }, - "description": "Prego inserisci le tue [informazioni](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) potrebbe essere più semplice!** \n**ATTENZIONE: Amazon risponde incorrettamente 'Inserire una mail valida o un numero di telefono' quando è richiesto il codice 2FA](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Vecchia configurazione" } } }, diff --git a/custom_components/alexa_media/translations/nb.json b/custom_components/alexa_media/translations/nb.json index 333797c9..87f5b062 100644 --- a/custom_components/alexa_media/translations/nb.json +++ b/custom_components/alexa_media/translations/nb.json @@ -14,38 +14,10 @@ "unknown_error": "Ukjent feil, vennligst rapporter logginfo" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon vil sende et push-varsel i henhold til meldingen nedenfor. Fullfør svaret før du fortsetter. \n{message}", - "title": "Alexa Media Player - Handling påkrevd" - }, - "authselect": { - "data": { - "authselectoption": "OTP-metode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Engangspassord" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Passord", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)" - }, - "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Bekreftelsesmetode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nVelg bekreftelsesmetode etter nummer. (f.eks. \"0\" eller \"1\") \n{message}", - "title": "Alexa Media Player - Bekreftelsesmetode" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Kode" - }, - "description": "**{email} - alexa.{url}** \nSkriv inn engangspassordet (OTP). \n>{message}", - "title": "Alexa Media Player - Tofaktorautentisering" - }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Avansert feilsøking", "email": "Epostadresse", "exclude_devices": "Ekskludert enhet (kommaseparert)", "hass_url": "Url to access Home Assistant", "include_devices": "Inkluder enhet (kommaseparert)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Passord", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Sekunder mellom skanninger", "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)", "url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)" }, - "description": "Bekreft informasjonen nedenfor. For eldre konfigurasjon, deaktiver alternativet \"Bruk innlogging proxy-metode\".", + "description": "Bekreft informasjonen nedenfor.", "title": "Alexa Media Player - Konfigurasjon" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "Avansert feilsøking", - "email": "Epostadresse", - "exclude_devices": "Ekskludert enhet (kommaseparert)", - "include_devices": "Inkluder enhet (kommaseparert)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Passord", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Sekunder mellom skanninger", - "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)", - "url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/translations/nl.json b/custom_components/alexa_media/translations/nl.json index 9a4afdbe..77558398 100644 --- a/custom_components/alexa_media/translations/nl.json +++ b/custom_components/alexa_media/translations/nl.json @@ -14,38 +14,10 @@ "unknown_error": "Onbekende fout, meld de loggegevens" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" - }, - "authselect": { - "data": { - "authselectoption": "Eenmalige Pincode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Eenmalige Pincode" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Paswoord", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "config::step::captcha::data::securitycode" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Verificatiemethode", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nSelecteer de verificatiemethode (bv.`0` of `1`) \n{message}", - "title": "Alexa Media Player - Verificatiemethode" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "Verificatiecode" - }, - "description": "**{email} - alexa.{url}** \nGeef de verificatiecode in. \n{message}", - "title": "Alexa Media Player - Tweestapsverificatie" - }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Geavanceerd debuggen", "email": "E-mailadres", "exclude_devices": "Apparaten uitsluiten (Scheiding: komma)", "hass_url": "Url to access Home Assistant", "include_devices": "Apparaten toevoegen (Scheiding: komma)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Paswoord", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Aantal seconden tussen scans", "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Domeinnaam van Amazon regio (bv.amazon.co.uk)" }, "description": "Vul je gegevens in a.u.b.", "title": "Alexa Media Player - Configuratie" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "Geavanceerd debuggen", - "email": "E-mailadres", - "exclude_devices": "Apparaten uitsluiten (Scheiding: komma)", - "include_devices": "Apparaten toevoegen (Scheiding: komma)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Paswoord", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Aantal seconden tussen scans", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Domeinnaam van Amazon regio (bv.amazon.co.uk)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/translations/pl.json b/custom_components/alexa_media/translations/pl.json index e0121412..fcb2c462 100644 --- a/custom_components/alexa_media/translations/pl.json +++ b/custom_components/alexa_media/translations/pl.json @@ -14,38 +14,10 @@ "unknown_error": "Nieznany błąd, włącz zaawansowane debugowanie i zgłoś log z tego zdarzenia" }, "step": { - "action_required": { - "data": { - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" - }, - "description": "**{email} - alexa.{url}** \nAmazon wyśle powiadomienie push zgodnie z poniższą wiadomością. Przed kontynuowaniem odpowiedz na wiadomość. \n{message}", - "title": "Alexa Media Player — wymagane działanie" - }, - "authselect": { - "data": { - "authselectoption": "Metoda OTP", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player — hasło jednorazowe" - }, "captcha": { "data": { - "captcha": "Kod Captcha", - "password": "Hasło", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "securitycode": "Kod uwierzytelniania dwuskładnikowego" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player — kod Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Metoda weryfikacji", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" - }, - "description": "**{email} - alexa.{url}** \nWybierz metodę weryfikacji. (np., `0` lub `1`) \n{message}", - "title": "Alexa Media Player — metoda weryfikacji" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nCzy pomyślnie potwierdziłeś hasło jednorazowe z wbudowanej aplikacji uwierzytelniania dwuskładnikowego z Amazonu? \n >Kod hasła jednorazowego {message}", "title": "Alexa Media Player - Potwierdzanie hasła jednorazowego" }, - "twofactor": { - "data": { - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", - "securitycode": "Kod uwierzytelniania dwuskładnikowego" - }, - "description": "**{email} - alexa.{url}** \nWprowadź hasło jednorazowe (OTP). \n{message}", - "title": "Alexa Media Player — uwierzytelnianie dwuskładnikowe" - }, "user": { "data": { - "cookies_txt": "zawartość pliku cookies.txt", "debug": "Zaawansowane debugowanie", "email": "Adres email", "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", "hass_url": "URL dostępu do Home Assistanta", "include_devices": "Dodawane urządzenia (oddzielone przecinkami)", - "oauth_login": "Włącz metodę tokena OAuth aplikacji", "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)", "password": "Hasło", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "scan_interval": "Interwał skanowania (sekundy)", "securitycode": "Kod uwierzytelniania dwuskładnikowego (zalecany w celu uniknięcia problemów z logowaniem)", "url": "Region/domena Amazon (np. amazon.co.uk)" }, - "description": "Potwierdź poniższe informacje. W przypadku starszych konfiguracji wyłącz opcję 'Użyj metody logowania proxy'.", + "description": "Potwierdź poniższe informacje.", "title": "Alexa Media Player — konfiguracja" - }, - "user_legacy": { - "data": { - "cookies_txt": "zawartość pliku cookies.txt", - "debug": "Zaawansowane debugowanie", - "email": "Adres e-mail", - "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", - "include_devices": "Dodawane urządzenia (oddzielone przecinkami)", - "oauth_login": "Włącz metodę tokena OAuth aplikacji", - "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)", - "password": "Hasło", - "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", - "scan_interval": "Interwał skanowania (sekundy)", - "securitycode": "Kod uwierzytelniania dwuskładnikowego", - "url": "Region/domena Amazon (np. amazon.co.uk)" - }, - "description": "Wprowadź [dane](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Import pliku Cookie](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) może być najłatwiejszą metodą!** \n**OSTRZEŻENIE: Amazon nieprawidłowo zgłasza 'Wprowadź prawidłowy adres e-mail lub numer telefonu komórkowego', gdy wymagany jest [kod uwierzytelniania dwuskładnikowego](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player — starsza konfiguracja" } } }, diff --git a/custom_components/alexa_media/translations/pt-BR.json b/custom_components/alexa_media/translations/pt-BR.json index 1f3897e2..6d4b4804 100644 --- a/custom_components/alexa_media/translations/pt-BR.json +++ b/custom_components/alexa_media/translations/pt-BR.json @@ -14,38 +14,10 @@ "unknown_error": "Erro desconhecido, favor habilitar depuração avançada e informações de registro de relatório" }, "step": { - "action_required": { - "data": { - "proxy": "Usar método Login Proxy (Não requer 2FA)" - }, - "description": "** {email} - alexa. {url} **\n A Amazon enviará uma notificação por push de acordo com a mensagem abaixo. Por favor, responda completamente antes de continuar.\n {message}", - "title": "Alexa Media Player - Ação necessária" - }, - "authselect": { - "data": { - "authselectoption": "Método OTP", - "proxy": "Usar método Login Proxy (Não requer 2FA)" - }, - "description": "**{email} - alexa.{url}**\n{message}", - "title": "Alexa Media Player - Senha de uso único" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Senha", - "proxy": "Usar método Login Proxy (Não requer 2FA)", "securitycode": "Código 2FA (recomendado para evitar problemas de login)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Método de verificação", - "proxy": "Usar método Login Proxy (Não requer 2FA)" - }, - "description": "** {email} - alexa. {url} **\n Selecione o método de verificação por número. (por exemplo, `0` ou `1`)\n {message}", - "title": "Alexa Media Player - Método de verificação" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "** {email} - alexa. {url} **\n Você confirmou com sucesso um OTP da chave de aplicativo 2FA integrada com a Amazon?\n > Código OTP {message}", "title": "Alexa Media Player - Confirmação OTP" }, - "twofactor": { - "data": { - "proxy": "Usar método Login Proxy (Não requer 2FA)", - "securitycode": "Código 2FA" - }, - "description": "** {email} - alexa. {url} **\n Digite a senha de uso único (OTP).\n {message}", - "title": "Alexa Media Player - Autenticação de dois fatores" - }, "user": { "data": { - "cookies_txt": "Cookies.txt data", "debug": "Depuração avançada", "email": "Endereço de e-mail", "exclude_devices": "Dispositivos excluídos (separado por vírgula)", "hass_url": "Url para acesso ao Home Assistant", "include_devices": "Dispositivos incluídos (separado por vírgula)", - "oauth_login": "Habilitar o aplicativo para método auth-token", "otp_secret": "Chave de aplicativo 2FA integrada (gerar automaticamente códigos 2FA)", "password": "Senha", - "proxy": "Usar método Login Proxy (Não requer 2FA)", "scan_interval": "Segundos entre varreduras", "securitycode": "Código 2FA (recomendado para evitar problemas de login)", "url": "Domínio regional da Amazon (ex: amazon.co.uk)" }, - "description": "Por favor, confirme as informações abaixo. Para configuração legada, desative a opção `Usar método de proxy de login`.", + "description": "Por favor, confirme as informações abaixo.", "title": "Alexa Media Player - Configurações" - }, - "user_legacy": { - "data": { - "cookies_txt": "Cookies.txt data", - "debug": "Depuração avançada", - "email": "Endereço de e-mail", - "exclude_devices": "Dispositivos excluídos (separado por vírgula)", - "include_devices": "Dispositivos incluídos (separado por vírgula)", - "oauth_login": "Habilitar o aplicativo para método auth-token", - "otp_secret": "Chave de aplicativo 2FA integrada (gerar automaticamente códigos 2FA)", - "password": "Senha", - "proxy": "Usar método Login Proxy (Não requer 2FA)", - "scan_interval": "Segundos entre escaneamentos", - "securitycode": "Código 2FA (recomendado para evitar problemas de login)", - "url": "Domínio regional da Amazon (ex: amazon.co.uk)" - }, - "description": "Insira suas [informações](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Importação de cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) pode ser mais fácil!**\n **AVISO: A Amazon informa incorretamente 'Digite um e-mail ou número de celular válido' quando [o código 2FA é necessário](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication- para-sua-conta-amazon).**\n > {message}", - "title": "Alexa Media Player - Configurações legado" } } }, diff --git a/custom_components/alexa_media/translations/pt.json b/custom_components/alexa_media/translations/pt.json index 5cf76c7d..0ea685b6 100644 --- a/custom_components/alexa_media/translations/pt.json +++ b/custom_components/alexa_media/translations/pt.json @@ -14,38 +14,10 @@ "unknown_error": "Erro desconhecido, por favor habilite depuração avançada e informações de log de relatório" }, "step": { - "action_required": { - "data": { - "proxy": "Usar método de proxy de login (2FA não é necessário)" - }, - "description": "**{email} - alexa.{url}** \nA Amazon enviará uma notificação push de acordo com a mensagem abaixo. Por favor, responda completamente antes de continuar. \n{message}", - "title": "Alexa Media Player - Ação Necessária" - }, - "authselect": { - "data": { - "authselectoption": "Método OTP (senha de uso único)", - "proxy": "Usar método de proxy de login (2FA não é necessário)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Senha de uso único" - }, "captcha": { "data": { - "captcha": "Captcha", - "password": "Senha", - "proxy": "Usar método de proxy de login (2FA não é necessário)", "securitycode": "Código 2FA (recomendado para evitar problemas de login)" - }, - "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", - "title": "Alexa Media Player - Captcha" - }, - "claimspicker": { - "data": { - "authselectoption": "Método de verificação", - "proxy": "Usar método de proxy de login (2FA não é necessário)" - }, - "description": "**{email} - alexa.{url}** \nPor favor, selecione o método de verificação por número. (ex. '0' ou '1') \n{message}", - "title": "Alexa Media Player - Método de verificação" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "** {email} - alexa. {url} **\n Você confirmou com sucesso uma senha de uso único na aplicação 2FA integrada com a Amazon?\n > Código OTP {message}", "title": "Alexa Media Player - Confirmação OTP" }, - "twofactor": { - "data": { - "proxy": "Usar método de proxy de login (2FA não é necessário)", - "securitycode": "Código 2FA" - }, - "description": "**{email} - alexa.{url}** \nDigite a senha de uso único (OTP). \n{message}", - "title": "Alexa Media Player - autenticação de dois fatores" - }, "user": { "data": { - "cookies_txt": "Dados de cookies.txt", "debug": "Depuração avançada", "email": "Endereço de e-mail", "exclude_devices": "Dispositivo excluído (separado por vírgula)", "hass_url": "URL para aceder o Home Assistant", "include_devices": "Dispositivo incluído (separado por vírgula)", - "oauth_login": "Habilitar método de aplicativo oauth-token", "otp_secret": "Chave de aplicativo 2FA integrada (gerar códigos 2FA automaticamente)", "password": "Senha", - "proxy": "Usar método de proxy de login (2FA não é necessário)", "scan_interval": "Segundos entre análises", "securitycode": "Código 2FA (recomendado para evitar problemas de login)", "url": "Região do domínio Amazon (ex. amazon.com.br)" }, - "description": "Por favor, confirme as informações abaixo. Para configuração de compatibilidade, desative a opção 'Usar método de proxy de login'.", + "description": "Por favor, confirme as informações abaixo.", "title": "Alexa Media Player - Configuração" - }, - "user_legacy": { - "data": { - "cookies_txt": "Dados de cookies.txt", - "debug": "Depuração avançada", - "email": "Endereço de e-mail", - "exclude_devices": "Dispositivo excluído (separado por vírgula)", - "include_devices": "Dispositivo incluído (separado por vírgula)", - "oauth_login": "Habilitar método de aplicativo oauth-token", - "otp_secret": "Chave de aplicativo 2FA integrada (gerar códigos 2FA automaticamente)", - "password": "Senha", - "proxy": "Usar método de proxy de login (2FA não é necessário)", - "scan_interval": "Segundos entre análises", - "securitycode": "Código 2FA (recomendado para evitar problemas de login)", - "url": "Região do domínio Amazon (ex. amazon.com.br)" - }, - "description": "Por favor, introduza a sua [informação](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) pode ser mais fácil!** \n**Aviso: a Amazon informa incorretamente 'Insira um e-mail ou número de celular válido' quando [2FA Code é necessário](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Configuração de Compatibilidade" } } }, diff --git a/custom_components/alexa_media/translations/pt_BR.json b/custom_components/alexa_media/translations/pt_BR.json index 2696cc29..2e8d00b0 100644 --- a/custom_components/alexa_media/translations/pt_BR.json +++ b/custom_components/alexa_media/translations/pt_BR.json @@ -8,7 +8,7 @@ "error": { "2fa_key_invalid": "Chave integrada 2FA inválida", "connection_error": "Erro de conexão; Verifique a sua conexão e tente novamente", - "hass_url_invalid": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL externa em Configuração -> Geral", + "unable_to_connect_hass_url": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL externa em Configuração -> Geral", "identifier_exists": "Email para URL Alexa já registrado", "invalid_credentials": "Credenciais inválidas", "unknown_error": "Erro desconhecido, favor habilitar a depuração avançada e reporte as informações de registro" diff --git a/custom_components/alexa_media/translations/pt_PT.json b/custom_components/alexa_media/translations/pt_PT.json index 5cf76c7d..2d7b4382 100644 --- a/custom_components/alexa_media/translations/pt_PT.json +++ b/custom_components/alexa_media/translations/pt_PT.json @@ -8,7 +8,7 @@ "error": { "2fa_key_invalid": "Chave 2FA integrada inválida", "connection_error": "Erro ao conectar; verifique a rede e tente novamente", - "hass_url_invalid": "Não foi possível conectar ao URL do Home Assistant. Verifique o URL externo em Configuração - > Geral", + "unable_to_connect_hass_url": "Não foi possível conectar ao URL do Home Assistant. Verifique o URL externo em Configuração - > Geral", "identifier_exists": "E-mail para URL Alexa já registado", "invalid_credentials": "Credenciais inválidas", "unknown_error": "Erro desconhecido, por favor habilite depuração avançada e informações de log de relatório" diff --git a/custom_components/alexa_media/translations/ru.json b/custom_components/alexa_media/translations/ru.json index ee10f828..e6aa3c69 100644 --- a/custom_components/alexa_media/translations/ru.json +++ b/custom_components/alexa_media/translations/ru.json @@ -14,38 +14,10 @@ "unknown_error": "Неизвестная ошибка, пожалуйста, сообщите информацию журнала" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa. {url}**\nAmazon отправит push-уведомление в соответствии с нижеприведенным сообщением. Пожалуйста, полностью ответьте, прежде чем продолжить.\n{message}", - "title": "Alexa Media Player - Требуемое действие" - }, - "authselect": { - "data": { - "authselectoption": "Одноразовый пароль", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - Одноразовый Пароль" - }, "captcha": { "data": { - "captcha": "Капча", - "password": "Пароль", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA Code (recommended to avoid login issues)" - }, - "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}", - "title": "Alexa Media Player - Капча" - }, - "claimspicker": { - "data": { - "authselectoption": "Метод проверки", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}**\nПожалуйста, выберите способ проверки по номеру. (например, \" 0 \" или \" 1`)\n{message}", - "title": "Alexa Media Player - Метод проверки" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2-факторная авторизация" - }, - "description": "**{email} - alexa.{url}**\nВведите одноразовый пароль.\n{message}", - "title": "Alexa Media Player - Двух факторная идентификация" - }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", "debug": "Расширенные возможности отладки", "email": "Адрес электронной почты", "exclude_devices": "Исключенные устройства (через запятую)", "hass_url": "Url to access Home Assistant", "include_devices": "Включенные устройства (разделенное запятыми)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "Пароль", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Секунды между сканированиями", "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Домен региона Amazon (например, amazon.co.uk)" }, - "description": "Пожалуйста, подтвердите информацию ниже. Для старой конфигурации отключите опцию `Use Login Proxy method`.", + "description": "Пожалуйста, подтвердите информацию ниже.", "title": "Alexa Media Player - Конфигурация" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "Расширенные возможности отладки", - "email": "Адрес электронной почты", - "exclude_devices": "Исключенные устройства (через запятую)", - "include_devices": "Включенные устройства (разделенное запятыми)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Пароль", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Секунды между сканированиями", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Домен региона Amazon (например, amazon.co.uk)" - }, - "description": "Пожалуйста, введите свои данные.{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/alexa_media/translations/zh-Hans.json b/custom_components/alexa_media/translations/zh-Hans.json index 4af369e3..2658d0e6 100644 --- a/custom_components/alexa_media/translations/zh-Hans.json +++ b/custom_components/alexa_media/translations/zh-Hans.json @@ -14,38 +14,10 @@ "unknown_error": "Unknown error, please report log info" }, "step": { - "action_required": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" - }, - "authselect": { - "data": { - "authselectoption": "OTP方式", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}**\n{message}", - "title": "Alexa Media Player - 一次性密码" - }, "captcha": { "data": { - "captcha": "验证码", - "password": "密码", - "proxy": "Use Login Proxy method (2FA not required)", "securitycode": "2FA Code (recommended to avoid login issues)" - }, - "description": "**{email} - alexa.{url}** \n>{message} \n{captcha_image}", - "title": "Alexa Media Player-验证码" - }, - "claimspicker": { - "data": { - "authselectoption": "验证方式", - "proxy": "Use Login Proxy method (2FA not required)" - }, - "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - 验证方法" + } }, "totp_register": { "data": { @@ -54,50 +26,21 @@ "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", "title": "Alexa Media Player - OTP Confirmation" }, - "twofactor": { - "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA代码" - }, - "description": "**{email} - Alexa.{url} ** \n输入一次性密码(OTP)。 \n {message}", - "title": "Alexa Media Player - Two Factor Authentication" - }, "user": { "data": { - "cookies_txt": "Cookie.txt数据", "debug": "高级调试", "email": "电子邮件地址", "exclude_devices": "Excluded device (comma separated)", "hass_url": "Url to access Home Assistant", "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", "password": "密码", - "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Seconds between scans", "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, - "description": "请确认以下信息。对于旧版配置,请禁用“使用登录代理方法”选项。", + "description": "请确认以下信息。", "title": "Alexa Media Player-配置" - }, - "user_legacy": { - "data": { - "cookies_txt": "config::step::user::data::cookies_txt", - "debug": "高级调试", - "email": "电子邮件地址", - "exclude_devices": "Excluded device (comma separated)", - "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "密码", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Seconds between scans", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Amazon region domain (e.g., amazon.co.uk)" - }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" } } }, diff --git a/custom_components/garbage_collection/__init__.py b/custom_components/garbage_collection/__init__.py index 238475ae..239054a8 100644 --- a/custom_components/garbage_collection/__init__.py +++ b/custom_components/garbage_collection/__init__.py @@ -5,24 +5,18 @@ import logging from datetime import timedelta from types import MappingProxyType -from typing import Any +from typing import Any, Dict import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util import voluptuous as vol from dateutil.relativedelta import relativedelta -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - ATTR_HIDDEN, - CONF_ENTITIES, - CONF_ENTITY_ID, - CONF_NAME, - WEEKDAYS, -) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_HIDDEN, CONF_ENTITIES, CONF_ENTITY_ID, WEEKDAYS from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers.typing import ConfigType -from . import const, helpers, sensor +from . import const, helpers MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) @@ -52,7 +46,7 @@ cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(min=1, max=5))] ), vol.Optional(const.CONF_PERIOD): vol.All( - vol.Coerce(int), vol.Range(min=1, max=365) + vol.Coerce(int), vol.Range(min=1, max=1000) ), vol.Optional(const.CONF_FIRST_WEEK): vol.All( vol.Coerce(int), vol.Range(min=1, max=52) @@ -104,17 +98,13 @@ ) -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up this component using YAML.""" +async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool: + """Set up platform - register services, inicialize data structure.""" async def handle_add_date(call: ServiceCall) -> None: """Handle the add_date service call.""" - if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])): - _LOGGER.error("add_date - missing Entity ID.") - return - if (collection_date := call.data.get(const.CONF_DATE)) is None: - _LOGGER.error("add_date - missing collection date.") - return + entity_ids = call.data.get(CONF_ENTITY_ID, []) + collection_date = call.data.get(const.CONF_DATE) for entity_id in entity_ids: _LOGGER.debug("called add_date %s from %s", collection_date, entity_id) try: @@ -122,17 +112,16 @@ async def handle_add_date(call: ServiceCall) -> None: await entity.add_date(collection_date) except KeyError as err: _LOGGER.error( - "Failed adding date %s to %s (%s)", collection_date, entity_id, err + "Failed adding date %s to %s (%s)", + collection_date, + entity_id, + err, ) async def handle_remove_date(call: ServiceCall) -> None: """Handle the remove_date service call.""" - if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])): - _LOGGER.error("remove_date - missing Entity ID.") - return - if (collection_date := call.data.get(const.CONF_DATE)) is None: - _LOGGER.error("remove_date - missing collection date.") - return + entity_ids = call.data.get(CONF_ENTITY_ID, []) + collection_date = call.data.get(const.CONF_DATE) for entity_id in entity_ids: _LOGGER.debug("called remove_date %s from %s", collection_date, entity_id) try: @@ -140,9 +129,7 @@ async def handle_remove_date(call: ServiceCall) -> None: await entity.remove_date(collection_date) except KeyError as err: _LOGGER.error( - "Failed removing date %s from %s. Most likely, " - "it was removed by competing automation runing in parallel. " - "(%s)", + "Failed removing date %s from %s (%s)", collection_date, entity_id, err, @@ -150,15 +137,9 @@ async def handle_remove_date(call: ServiceCall) -> None: async def handle_offset_date(call: ServiceCall) -> None: """Handle the offset_date service call.""" - if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])): - _LOGGER.error("offset_date - missing Entity ID.") - return - if (offset := call.data.get(const.CONF_OFFSET)) is None: - _LOGGER.error("offset_date - missing offset.") - return - if (collection_date := call.data.get(const.CONF_DATE)) is None: - _LOGGER.error("offset_date - missing collection date.") - return + entity_ids = call.data.get(CONF_ENTITY_ID, []) + offset = call.data.get(const.CONF_OFFSET) + collection_date = call.data.get(const.CONF_DATE) for entity_id in entity_ids: _LOGGER.debug( "called offset_date %s by %d days for %s", @@ -167,7 +148,9 @@ async def handle_offset_date(call: ServiceCall) -> None: entity_id, ) try: - new_date = collection_date + relativedelta(days=offset) + new_date = collection_date + relativedelta( + days=offset + ) # pyright: reportOptionalOperand=false entity = hass.data[const.DOMAIN][const.SENSOR_PLATFORM][entity_id] await asyncio.gather( entity.remove_date(collection_date), entity.add_date(new_date) @@ -178,9 +161,7 @@ async def handle_offset_date(call: ServiceCall) -> None: async def handle_update_state(call: ServiceCall) -> None: """Handle the update_state service call.""" - if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])): - _LOGGER.error("update_state - missing Entity ID.") - return + entity_ids = call.data.get(CONF_ENTITY_ID, []) for entity_id in entity_ids: _LOGGER.debug("called update_state for %s", entity_id) try: @@ -191,10 +172,8 @@ async def handle_update_state(call: ServiceCall) -> None: async def handle_collect_garbage(call: ServiceCall) -> None: """Handle the collect_garbage service call.""" - if not (entity_ids := call.data.get(CONF_ENTITY_ID, [])): - _LOGGER.error("collect_garbage - missing Entity ID.") - return - last_collection = call.data.get(const.ATTR_LAST_COLLECTION, sensor.now()) + entity_ids = call.data.get(CONF_ENTITY_ID, []) + last_collection = call.data.get(const.ATTR_LAST_COLLECTION, helpers.now()) for entity_id in entity_ids: _LOGGER.debug("called collect_garbage for %s", entity_id) try: @@ -206,63 +185,32 @@ async def handle_collect_garbage(call: ServiceCall) -> None: "Failed setting last collection for %s - %s", entity_id, err ) - if const.DOMAIN not in hass.services.async_services(): - hass.services.async_register( - const.DOMAIN, - "collect_garbage", - handle_collect_garbage, - schema=COLLECT_NOW_SCHEMA, - ) - hass.services.async_register( - const.DOMAIN, - "update_state", - handle_update_state, - schema=UPDATE_STATE_SCHEMA, - ) - hass.services.async_register( - const.DOMAIN, "add_date", handle_add_date, schema=ADD_REMOVE_DATE_SCHEMA - ) - hass.services.async_register( - const.DOMAIN, - "remove_date", - handle_remove_date, - schema=ADD_REMOVE_DATE_SCHEMA, - ) - hass.services.async_register( - const.DOMAIN, "offset_date", handle_offset_date, schema=OFFSET_DATE_SCHEMA - ) - else: - _LOGGER.debug("Services already registered") - - if config.get(const.DOMAIN) is None: - # We get here if the integration is set up using config flow - return True - - # If platform is not enabled, skip. - if not (platform_config := config[const.DOMAIN].get(const.CONF_SENSORS, {})): - return False - - for entry in hass.config_entries.async_entries(const.DOMAIN): - if entry.source == SOURCE_IMPORT: - _LOGGER.error( - "garbage_collection already imported. " - "Remove it from configuration.yaml now!" - ) - return True - for entry in platform_config: - _LOGGER.debug( - "Importing %s(%s) from YAML configuration", - entry[CONF_NAME], - entry[const.CONF_FREQUENCY], - ) - # Import YAML to ConfigFlow - hass.async_create_task( - hass.config_entries.flow.async_init( - const.DOMAIN, - context={"source": SOURCE_IMPORT}, - data=entry, - ) - ) + hass.data.setdefault(const.DOMAIN, {}) + hass.data[const.DOMAIN].setdefault(const.SENSOR_PLATFORM, {}) + hass.services.async_register( + const.DOMAIN, + "collect_garbage", + handle_collect_garbage, + schema=COLLECT_NOW_SCHEMA, + ) + hass.services.async_register( + const.DOMAIN, + "update_state", + handle_update_state, + schema=UPDATE_STATE_SCHEMA, + ) + hass.services.async_register( + const.DOMAIN, "add_date", handle_add_date, schema=ADD_REMOVE_DATE_SCHEMA + ) + hass.services.async_register( + const.DOMAIN, + "remove_date", + handle_remove_date, + schema=ADD_REMOVE_DATE_SCHEMA, + ) + hass.services.async_register( + const.DOMAIN, "offset_date", handle_offset_date, schema=OFFSET_DATE_SCHEMA + ) return True @@ -271,10 +219,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b _LOGGER.debug( "Setting %s (%s) from ConfigFlow", config_entry.title, - config_entry.data[const.CONF_FREQUENCY], + config_entry.options[const.CONF_FREQUENCY], ) - # Backward compatibility - clean-up (can be removed later?) - config_entry.options = MappingProxyType({}) config_entry.add_update_listener(update_listener) # Add sensor hass.async_create_task( @@ -298,15 +244,15 @@ async def async_remove_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> pass -async def async_migrate_entry(_, config_entry: ConfigEntry) -> bool: +async def async_migrate_entry(_: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate old entry.""" _LOGGER.info( "Migrating %s from version %s", config_entry.title, config_entry.version ) - new_data = {**config_entry.data} - new_options = {**config_entry.options} - removed_data: dict[str, Any] = {} - removed_options: dict[str, Any] = {} + new_data: Dict[str, Any] = {**config_entry.data} + new_options: Dict[str, Any] = {**config_entry.options} + removed_data: Dict[str, Any] = {} + removed_options: Dict[str, Any] = {} _LOGGER.debug("new_data %s", new_data) _LOGGER.debug("new_options %s", new_options) if config_entry.version == 1: @@ -358,6 +304,40 @@ async def async_migrate_entry(_, config_entry: ConfigEntry) -> bool: new_options[const.CONF_WEEKDAY_ORDER_NUMBER] = list( map(str, new_options[const.CONF_WEEKDAY_ORDER_NUMBER]) ) + if config_entry.version <= 5: + for conf in [ + const.CONF_FREQUENCY, + const.CONF_ICON_NORMAL, + const.CONF_ICON_TODAY, + const.CONF_ICON_TOMORROW, + const.CONF_MANUAL, + const.CONF_OFFSET, + const.CONF_EXPIRE_AFTER, + const.CONF_VERBOSE_STATE, + const.CONF_FIRST_MONTH, + const.CONF_LAST_MONTH, + const.CONF_COLLECTION_DAYS, + const.CONF_WEEKDAY_ORDER_NUMBER, + const.CONF_FORCE_WEEK_NUMBERS, + const.CONF_WEEK_ORDER_NUMBER, + const.CONF_DATE, + const.CONF_PERIOD, + const.CONF_FIRST_WEEK, + const.CONF_FIRST_DATE, + const.CONF_SENSORS, + const.CONF_VERBOSE_FORMAT, + const.CONF_DATE_FORMAT, + ]: + if conf in new_data: + new_options[conf] = new_data.get(conf) + del new_data[conf] + if ( + const.CONF_EXPIRE_AFTER in new_options + and len(new_options[const.CONF_EXPIRE_AFTER]) == 5 + ): + new_options[const.CONF_EXPIRE_AFTER] = ( + new_options[const.CONF_EXPIRE_AFTER] + ":00" + ) config_entry.version = const.CONFIG_VERSION config_entry.data = MappingProxyType({**new_data}) config_entry.options = MappingProxyType({**new_options}) @@ -381,13 +361,8 @@ async def async_migrate_entry(_, config_entry: ConfigEntry) -> bool: return True -async def update_listener(hass, entry): - """Update listener.""" - # The OptionsFlow saves data to options. - # Move them back to data and clean options (dirty, but not sure how else to do that) - if len(entry.options) > 0: - entry.data = entry.options - entry.options = {} +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Update listener - to re-create device after options update.""" await hass.config_entries.async_forward_entry_unload(entry, const.SENSOR_PLATFORM) hass.async_add_job( hass.config_entries.async_forward_entry_setup(entry, const.SENSOR_PLATFORM) diff --git a/custom_components/garbage_collection/calendar.py b/custom_components/garbage_collection/calendar.py index 29934396..591df505 100644 --- a/custom_components/garbage_collection/calendar.py +++ b/custom_components/garbage_collection/calendar.py @@ -4,7 +4,9 @@ from datetime import datetime, timedelta from homeassistant.components.calendar import CalendarEntity, CalendarEvent +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import Throttle from .const import CALENDAR_NAME, CALENDAR_PLATFORM, DOMAIN, SENSOR_PLATFORM @@ -12,14 +14,12 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None +async def async_setup_entry( + _: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: - """Add calendar entities to HA, of there are calendar instances.""" # pylint: disable=unused-argument - # Only single instance allowed - if not GarbageCollectionCalendar.instances: - async_add_entities([GarbageCollectionCalendar()], True) + """Add calendar entity to HA.""" + async_add_entities([GarbageCollectionCalendar()], True) class GarbageCollectionCalendar(CalendarEntity): @@ -30,7 +30,7 @@ class GarbageCollectionCalendar(CalendarEntity): def __init__(self) -> None: """Create empty calendar.""" self._cal_data: dict = {} - self._name = CALENDAR_NAME + self._attr_name = CALENDAR_NAME GarbageCollectionCalendar.instances = True @property @@ -39,9 +39,9 @@ def event(self) -> CalendarEvent | None: return self.hass.data[DOMAIN][CALENDAR_PLATFORM].event @property - def name(self) -> str: + def name(self) -> str | None: """Return the name of the entity.""" - return self._name + return self._attr_name async def async_update(self) -> None: """Update all calendars.""" @@ -102,20 +102,25 @@ async def async_get_events( continue garbage_collection = hass.data[DOMAIN][SENSOR_PLATFORM][entity] start = garbage_collection.get_next_date(start_date, True) - while start is not None and start >= start_date and start <= end_date: + while start is not None and start_date <= start <= end_date: try: end = start + timedelta(days=1) except TypeError: end = start + name = ( + garbage_collection.name + if garbage_collection.name is not None + else "Unknown" + ) if garbage_collection.expire_after is None: event = CalendarEvent( - summary=garbage_collection.name, + summary=name, start=start, end=end, ) else: event = CalendarEvent( - summary=garbage_collection.name, + summary=name, start=datetime.combine(start, datetime.min.time()), end=datetime.combine(start, garbage_collection.expire_after), ) diff --git a/custom_components/garbage_collection/config_flow.py b/custom_components/garbage_collection/config_flow.py index 2919adb4..d9054a1c 100644 --- a/custom_components/garbage_collection/config_flow.py +++ b/custom_components/garbage_collection/config_flow.py @@ -2,356 +2,199 @@ from __future__ import annotations import logging -import uuid -from collections import OrderedDict -from typing import Any + +# import uuid +from collections.abc import Mapping +from typing import Any, Dict, cast import homeassistant.helpers.config_validation as cv import voluptuous as vol -from homeassistant import config_entries from homeassistant.const import ATTR_HIDDEN, CONF_ENTITIES, CONF_NAME, WEEKDAYS -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import callback +from homeassistant.helpers import selector +from homeassistant.helpers.schema_config_entry_flow import ( + SchemaConfigFlowHandler, + SchemaFlowError, + SchemaFlowFormStep, + SchemaFlowMenuStep, + SchemaOptionsFlowHandler, +) from . import const, helpers _LOGGER = logging.getLogger(__name__) -class GarbageCollectionShared: - """Store configuration for both YAML and config_flow.""" - - def __init__(self, data): - """Create class attributes and set initial values.""" - self._data = data.copy() - self.hass: HomeAssistant | None = None - self.name: str - self.errors: dict = {} - self.data_schema: OrderedDict = OrderedDict() - self._defaults = { - const.CONF_FREQUENCY: const.DEFAULT_FREQUENCY, - const.CONF_ICON_NORMAL: const.DEFAULT_ICON_NORMAL, - const.CONF_ICON_TODAY: const.DEFAULT_ICON_TODAY, - const.CONF_ICON_TOMORROW: const.DEFAULT_ICON_TOMORROW, - const.CONF_VERBOSE_STATE: const.DEFAULT_VERBOSE_STATE, - ATTR_HIDDEN: False, - const.CONF_MANUAL: False, - const.CONF_FIRST_MONTH: const.DEFAULT_FIRST_MONTH, - const.CONF_LAST_MONTH: const.DEFAULT_LAST_MONTH, - const.CONF_PERIOD: const.DEFAULT_PERIOD, - const.CONF_FIRST_WEEK: const.DEFAULT_FIRST_WEEK, - const.CONF_VERBOSE_FORMAT: const.DEFAULT_VERBOSE_FORMAT, - const.CONF_DATE_FORMAT: const.DEFAULT_DATE_FORMAT, - } - - def update_data(self, user_input: dict) -> None: - """Remove empty fields, and fields that should not be stored in the config.""" - self._data.update(user_input) - for key, value in user_input.items(): - if value == "": - del self._data[key] - if CONF_NAME in self._data: - self.name = self._data[CONF_NAME] - del self._data[CONF_NAME] - - def required(self, key: str, options: dict | None) -> vol.Required: - """Return vol.Required.""" - if isinstance(options, dict) and key in options: - suggested_value = options[key] - elif key in self._data: - suggested_value = self._data[key] - elif key in self._defaults: - suggested_value = self._defaults[key] - else: - return vol.Required(key) - return vol.Required(key, description={"suggested_value": suggested_value}) - - def optional(self, key: str, options: dict | None) -> vol.Optional: - """Return vol.Optional.""" - if isinstance(options, dict) and key in options: - suggested_value = options[key] - elif key in self._data: - suggested_value = self._data[key] - elif key in self._defaults: - suggested_value = self._defaults[key] - else: - return vol.Optional(key) - return vol.Optional(key, description={"suggested_value": suggested_value}) - - def step1_frequency(self, user_input: dict | None, options: bool = False) -> bool: - """Step 1 - choose frequency and common parameters.""" - self.errors.clear() - if user_input is not None: - try: - cv.icon( - user_input.get(const.CONF_ICON_NORMAL, const.DEFAULT_ICON_NORMAL) - ) - cv.icon(user_input.get(const.CONF_ICON_TODAY, const.DEFAULT_ICON_TODAY)) - cv.icon( - user_input.get( - const.CONF_ICON_TOMORROW, const.DEFAULT_ICON_TOMORROW - ) - ) - except vol.Invalid: - self.errors["base"] = "icon" - try: - helpers.time_text(user_input.get(const.CONF_EXPIRE_AFTER)) - except vol.Invalid: - self.errors["base"] = "time" - if not self.errors: - self.update_data(user_input) - return True - self.data_schema.clear() - # Do not show name for Options_Flow. The name cannot be changed here - if not options: - self.data_schema[self.required(CONF_NAME, user_input)] = str - self.data_schema[self.required(const.CONF_FREQUENCY, user_input)] = vol.In( - const.FREQUENCY_OPTIONS - ) - self.data_schema[self.optional(const.CONF_ICON_NORMAL, user_input)] = str - self.data_schema[self.optional(const.CONF_ICON_TODAY, user_input)] = str - self.data_schema[self.optional(const.CONF_ICON_TOMORROW, user_input)] = str - self.data_schema[self.optional(const.CONF_EXPIRE_AFTER, user_input)] = str - self.data_schema[self.optional(const.CONF_VERBOSE_STATE, user_input)] = bool - self.data_schema[self.optional(ATTR_HIDDEN, user_input)] = bool - self.data_schema[self.optional(const.CONF_MANUAL, user_input)] = bool - return False - - def step2_detail(self, user_input: dict) -> bool: - """Step 2 - enter detail that depend on frequency.""" - self.errors.clear() - # Skip second step on blank frequency - if self._data[ - const.CONF_FREQUENCY - ] in const.BLANK_FREQUENCY and not self._data.get( - const.CONF_VERBOSE_STATE, False - ): - return True - if user_input is not None and user_input: - # Validation - if user_input.get(const.CONF_FREQUENCY) in const.ANNUAL_FREQUENCY: - # "annual" - try: - helpers.month_day_text(user_input.get(const.CONF_DATE, "")) - except vol.Invalid: - self.errors["base"] = "month_day" - if user_input.get(const.CONF_FREQUENCY) in const.DAILY_FREQUENCY: - # "every-n-days" - try: - cv.date(user_input.get(const.CONF_FIRST_DATE, "")) - except vol.Invalid: - self.errors["base"] = "date" - if not self.errors: - self.update_data(user_input) - return True - self.data_schema.clear() - # Build form - if self._data[const.CONF_FREQUENCY] in const.ANNUAL_FREQUENCY: - # "annual" - self.data_schema[self.required(const.CONF_DATE, user_input)] = str - elif self._data[const.CONF_FREQUENCY] in const.GROUP_FREQUENCY: - # "group" - if self.hass is None: - _LOGGER.error( - "Cannot get list of garbage_collection entities for the group." - ) - else: - entities = self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM] - entity_ids = { - entity: entity - for entity in entities - if entities[entity].unique_id != self._data["unique_id"] - } - self.data_schema[ - self.required(CONF_ENTITIES, user_input) - ] = cv.multi_select(entity_ids) - elif self._data[const.CONF_FREQUENCY] not in const.BLANK_FREQUENCY: - # everything else except "blank" and every-n-days - if self._data[const.CONF_FREQUENCY] not in const.DAILY_FREQUENCY: - weekdays_dict = {weekday: weekday for weekday in WEEKDAYS} - self.data_schema[ - self.required(const.CONF_COLLECTION_DAYS, user_input) - ] = cv.multi_select(weekdays_dict) - # everything else except "blank" - self.data_schema[ - self.optional(const.CONF_FIRST_MONTH, user_input) - ] = vol.In(const.MONTH_OPTIONS) - self.data_schema[self.optional(const.CONF_LAST_MONTH, user_input)] = vol.In( - const.MONTH_OPTIONS - ) - if self._data[const.CONF_FREQUENCY] in const.MONTHLY_FREQUENCY: - # "monthly" - self.data_schema[ - self.optional(const.CONF_WEEKDAY_ORDER_NUMBER, user_input) - ] = vol.All( - cv.multi_select( - {"1": "1st", "2": "2nd", "3": "3rd", "4": "4th", "5": "5th"} - ), - ) - self.data_schema[ - self.optional(const.CONF_FORCE_WEEK_NUMBERS, user_input) - ] = bool - if self._data[const.CONF_FREQUENCY] in const.WEEKLY_DAILY_MONTHLY: - # "every-n-weeks", "every-n-days", "monthly" - self.data_schema[ - self.required(const.CONF_PERIOD, user_input) - ] = vol.All(vol.Coerce(int), vol.Range(min=1, max=365)) - if self._data[const.CONF_FREQUENCY] in const.WEEKLY_FREQUENCY_X: - # every-n-weeks - self.data_schema[ - self.required(const.CONF_FIRST_WEEK, user_input) - ] = vol.All(vol.Coerce(int), vol.Range(min=1, max=52)) - if self._data[const.CONF_FREQUENCY] in const.DAILY_FREQUENCY: - # every-n-days - self.data_schema[self.required(const.CONF_FIRST_DATE, user_input)] = str - if self._data.get(const.CONF_VERBOSE_STATE, False): - # "verbose_state" - self.data_schema[ - self.required(const.CONF_VERBOSE_FORMAT, user_input) - ] = cv.string - self.data_schema[ - self.required(const.CONF_DATE_FORMAT, user_input) - ] = cv.string - return False - - @property - def frequency(self): - """Return the collection frequency.""" +def _validate_config(data: Any) -> Any: + """Validate config.""" + if const.CONF_DATE in data: try: - return self._data[const.CONF_FREQUENCY] - except KeyError: - return None - - @property - def data(self): - """Return whole data store.""" - return self._data - - -@config_entries.HANDLERS.register(const.DOMAIN) -class GarbageCollectionFlowHandler(config_entries.ConfigFlow): - """Config flow for garbage_collection.""" - - VERSION = const.CONFIG_VERSION - CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + helpers.month_day_text(data[const.CONF_DATE]) + except vol.Invalid as exc: + raise SchemaFlowError("month_day") from exc + return data + + +def required( + key: str, options: Dict[str, Any], default: Any | None = None +) -> vol.Required: + """Return vol.Required.""" + if isinstance(options, dict) and key in options: + suggested_value = options[key] + elif default is not None: + suggested_value = default + else: + return vol.Required(key) + return vol.Required(key, description={"suggested_value": suggested_value}) + + +def optional( + key: str, options: Dict[str, Any], default: Any | None = None +) -> vol.Optional: + """Return vol.Optional.""" + if isinstance(options, dict) and key in options: + suggested_value = options[key] + elif default is not None: + suggested_value = default + else: + return vol.Optional(key) + return vol.Optional(key, description={"suggested_value": suggested_value}) + + +def general_options_schema( + _: SchemaConfigFlowHandler | SchemaOptionsFlowHandler, + options: Dict[str, Any], +) -> vol.Schema: + """Generate options schema.""" + return vol.Schema( + { + required(const.CONF_FREQUENCY, options, const.DEFAULT_FREQUENCY): vol.In( + const.FREQUENCY_OPTIONS + ), + optional( + const.CONF_ICON_NORMAL, options, const.DEFAULT_ICON_NORMAL + ): selector.IconSelector(), + optional( + const.CONF_ICON_TODAY, options, const.DEFAULT_ICON_TODAY + ): selector.IconSelector(), + optional( + const.CONF_ICON_TOMORROW, options, const.DEFAULT_ICON_TOMORROW + ): selector.IconSelector(), + optional(const.CONF_EXPIRE_AFTER, options): selector.TimeSelector(), + optional( + const.CONF_VERBOSE_STATE, options, const.DEFAULT_VERBOSE_STATE + ): bool, + optional(ATTR_HIDDEN, options, False): bool, + optional(const.CONF_MANUAL, options, False): bool, + } + ) - def __init__(self): - """Initialize.""" - self.shared_class = GarbageCollectionShared({"unique_id": str(uuid.uuid4())}) - async def async_step_user( - self, user_input: dict = {} - ): # pylint: disable=dangerous-default-value - """Step 1 - set general parameters.""" - next_step = self.shared_class.step1_frequency(user_input) - if next_step: - return await self.async_step_detail() - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - self.shared_class.data_schema, extra=vol.ALLOW_EXTRA +def general_config_schema( + handler: SchemaConfigFlowHandler | SchemaOptionsFlowHandler, + options: Dict[str, Any], +) -> vol.Schema: + """Generate config schema.""" + return vol.Schema( + { + optional(CONF_NAME, options): selector.TextSelector(), + } + ).extend(general_options_schema(handler, options).schema) + + +def detail_config_schema( + _, + options: Dict[str, Any], +) -> vol.Schema: + """Generate options schema.""" + options_schema: Dict[vol.Optional | vol.Required, Any] = {} + if options[const.CONF_FREQUENCY] in const.ANNUAL_FREQUENCY: + # "annual" + options_schema[required(const.CONF_DATE, options)] = str + elif options[const.CONF_FREQUENCY] in const.GROUP_FREQUENCY: + # "group" + options_schema[required(CONF_ENTITIES, options)] = selector.EntitySelector( + selector.EntitySelectorConfig( + domain="sensor", integration=const.DOMAIN, multiple=True ), - errors=self.shared_class.errors, ) - - async def async_step_detail( - self, user_input: dict = {} - ): # pylint: disable=dangerous-default-value - """Step 2 - enter detail depending on frequency.""" - self.shared_class.hass = self.hass - next_step = self.shared_class.step2_detail(user_input) - if next_step: - return self.async_create_entry( - title=self.shared_class.name, data=self.shared_class.data + elif options[const.CONF_FREQUENCY] not in const.BLANK_FREQUENCY: + # everything else except "blank" and every-n-days + if options[const.CONF_FREQUENCY] not in const.DAILY_FREQUENCY: + weekdays_dict = {weekday: weekday for weekday in WEEKDAYS} + options_schema[ + required(const.CONF_COLLECTION_DAYS, options) + ] = cv.multi_select(weekdays_dict) + # everything else except "blank" + options_schema[ + optional(const.CONF_FIRST_MONTH, options, const.DEFAULT_FIRST_MONTH) + ] = vol.In(const.MONTH_OPTIONS) + options_schema[ + optional(const.CONF_LAST_MONTH, options, const.DEFAULT_LAST_MONTH) + ] = vol.In(const.MONTH_OPTIONS) + if options[const.CONF_FREQUENCY] in const.MONTHLY_FREQUENCY: + # "monthly" + options_schema[ + optional(const.CONF_WEEKDAY_ORDER_NUMBER, options) + ] = vol.All( + cv.multi_select( + {"1": "1st", "2": "2nd", "3": "3rd", "4": "4th", "5": "5th"} + ), ) - return self.async_show_form( - step_id="detail", - data_schema=vol.Schema( - self.shared_class.data_schema, extra=vol.ALLOW_EXTRA - ), - errors=self.shared_class.errors, - ) - - async def async_step_import( - self, user_input: dict - ): # pylint: disable=unused-argument - """Import config from configuration.yaml.""" - _LOGGER.info("Importing config for %s", user_input) - to_remove = [ - "offset", - "move_country_holidays", - "holiday_in_week_move", - "holiday_pop_named", - "holiday_move_offset", - "prov", - "state", - "observed", - "exclude_dates", - "include_dates", - ] - removed_data: dict[str, Any] = {} - for remove in to_remove: - if remove in user_input: - removed_data[remove] = user_input[remove] - del user_input[remove] - if user_input.get(const.CONF_FREQUENCY) in const.MONTHLY_FREQUENCY: - if const.CONF_WEEK_ORDER_NUMBER in user_input: - user_input[const.CONF_WEEKDAY_ORDER_NUMBER] = list( - map(str, user_input[const.CONF_WEEK_ORDER_NUMBER]) - ) - user_input[const.CONF_FORCE_WEEK_NUMBERS] = True - del user_input[const.CONF_WEEK_ORDER_NUMBER] - _LOGGER.debug("Updated data config for week_order_number") - else: - user_input[const.CONF_WEEKDAY_ORDER_NUMBER] = list( - map(str, user_input[const.CONF_WEEKDAY_ORDER_NUMBER]) - ) - user_input[const.CONF_FORCE_WEEK_NUMBERS] = False - if removed_data: - _LOGGER.error( - "Removed obsolete config values: %s. " - "Please check the documentation how to configure the functionality.", - removed_data, + options_schema[optional(const.CONF_FORCE_WEEK_NUMBERS, options)] = bool + if options[const.CONF_FREQUENCY] in const.WEEKLY_DAILY_MONTHLY: + # "every-n-weeks", "every-n-days", "monthly" + options_schema[required(const.CONF_PERIOD, options)] = vol.All( + vol.Coerce(int), vol.Range(min=1, max=1000) ) - self.shared_class.update_data(user_input) - return self.async_create_entry( - title=self.shared_class.name, data=self.shared_class.data - ) + if options[const.CONF_FREQUENCY] in const.WEEKLY_FREQUENCY_X: + # every-n-weeks + options_schema[ + required(const.CONF_FIRST_WEEK, options, const.DEFAULT_FIRST_WEEK) + ] = vol.All(vol.Coerce(int), vol.Range(min=1, max=52)) + if options[const.CONF_FREQUENCY] in const.DAILY_FREQUENCY: + # every-n-days + options_schema[ + required(const.CONF_FIRST_DATE, options) + ] = selector.DateSelector() + if options.get(const.CONF_VERBOSE_STATE, False): + # "verbose_state" + options_schema[ + required(const.CONF_VERBOSE_FORMAT, options, const.DEFAULT_VERBOSE_FORMAT) + ] = cv.string + options_schema[ + required(const.CONF_DATE_FORMAT, options, const.DEFAULT_DATE_FORMAT) + ] = cv.string + return vol.Schema(options_schema) + + +CONFIG_FLOW: Dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = { + "user": SchemaFlowFormStep(general_config_schema, next_step=lambda x: "detail"), + "detail": SchemaFlowFormStep( + detail_config_schema, validate_user_input=_validate_config + ), +} +OPTIONS_FLOW: Dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = { + "init": SchemaFlowFormStep(general_options_schema, next_step=lambda x: "detail"), + "detail": SchemaFlowFormStep( + detail_config_schema, validate_user_input=_validate_config + ), +} + + +# mypy: ignore-errors +class GarbageCollectionConfigFlowHandler(SchemaConfigFlowHandler, domain=const.DOMAIN): + """Handle a config or options flow for GarbageCollection.""" + + config_flow = CONFIG_FLOW + options_flow = OPTIONS_FLOW + VERSION = const.CONFIG_VERSION - @staticmethod @callback - def async_get_options_flow(config_entry): - """Return options flow handler, or empty options flow if no unique_id.""" - return OptionsFlowHandler(config_entry) - - -class OptionsFlowHandler(config_entries.OptionsFlow): - """Options flow handler.""" - - def __init__(self, config_entry): - """Create and initualize class variables.""" - self.shared_class = GarbageCollectionShared(config_entry.data) + def async_config_entry_title(self, options: Mapping[str, Any]) -> str: + """Return config entry title. - async def async_step_init(self, user_input: dict | None = None): - """Set genral parameters.""" - next_step = self.shared_class.step1_frequency(user_input, options=True) - if next_step: - return await self.async_step_detail() - return self.async_show_form( - step_id="init", - data_schema=vol.Schema(self.shared_class.data_schema), - errors=self.shared_class.errors, - ) - - async def async_step_detail( - self, user_input: dict = {} - ): # pylint: disable=dangerous-default-value - """Step 2 - annual or group (no week days).""" - self.shared_class.hass = self.hass - next_step = self.shared_class.step2_detail(user_input) - if next_step: - return self.async_create_entry(title="", data=self.shared_class.data) - return self.async_show_form( - step_id="detail", - data_schema=vol.Schema(self.shared_class.data_schema), - errors=self.shared_class.errors, - ) + The options parameter contains config entry options, which is the union of user + input from the config flow steps. + """ + return cast(str, options["name"]) if "name" in options else "" diff --git a/custom_components/garbage_collection/const.py b/custom_components/garbage_collection/const.py index 0a770371..9f565d2e 100644 --- a/custom_components/garbage_collection/const.py +++ b/custom_components/garbage_collection/const.py @@ -8,7 +8,7 @@ SENSOR_PLATFORM = "sensor" CALENDAR_PLATFORM = "calendar" ATTRIBUTION = "Data from this is provided by garbage_collection." -CONFIG_VERSION = 5 +CONFIG_VERSION = 6 ATTR_NEXT_DATE = "next_date" ATTR_DAYS = "days" diff --git a/custom_components/garbage_collection/diagnostics.py b/custom_components/garbage_collection/diagnostics.py index 3bfa6c56..738fc684 100644 --- a/custom_components/garbage_collection/diagnostics.py +++ b/custom_components/garbage_collection/diagnostics.py @@ -1,7 +1,7 @@ """Diagnostics support for Garbage Collection.""" from __future__ import annotations -from typing import Any +from typing import Any, Dict from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -12,7 +12,7 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry, -) -> dict[str, Any]: +) -> Dict[str, Any]: """Return diagnostics for a config entry.""" entities = hass.data[const.DOMAIN][const.SENSOR_PLATFORM] entity_data = [ diff --git a/custom_components/garbage_collection/helpers.py b/custom_components/garbage_collection/helpers.py index 7ed2d5c3..1bbd6b17 100644 --- a/custom_components/garbage_collection/helpers.py +++ b/custom_components/garbage_collection/helpers.py @@ -4,10 +4,16 @@ from datetime import date, datetime from typing import Any +import homeassistant.util.dt as dt_util import voluptuous as vol from dateutil.parser import ParserError, parse +def now() -> datetime: + """Return current date and time. Needed for testing.""" + return dt_util.now() + + def to_date(day: Any) -> date: """Convert datetime or text to date, if not already datetime. @@ -41,16 +47,6 @@ def dates_to_texts(dates: list[date]) -> list[str]: return converted -def date_text(value: Any) -> str: - """Have to store date as text - datetime is not JSON serialisable.""" - if value is None or value == "": - return "" - try: - return datetime.strptime(value, "%Y-%m-%d").date().strftime("%Y-%m-%d") - except ValueError as error: - raise vol.Invalid(f"Invalid date: {value}") from error - - def time_text(value: Any) -> str: """Have to store time as text - datetime is not JSON serialisable.""" if value is None or value == "": @@ -61,15 +57,6 @@ def time_text(value: Any) -> str: raise vol.Invalid(f"Invalid date: {value}") from error -def string_to_list(string) -> list: - """Convert comma separated text to list.""" - if isinstance(string, list): - return string # Already list - if string is None or string == "": - return [] - return list(map(lambda x: x.strip("'\" "), string.split(","))) - - def month_day_text(value: Any) -> str: """Validate format month/day.""" if value is None or value == "": @@ -78,34 +65,3 @@ def month_day_text(value: Any) -> str: return datetime.strptime(value, "%m/%d").date().strftime("%m/%d") except ValueError as error: raise vol.Invalid(f"Invalid date: {value}") from error - - -def is_date(record: str) -> bool: - """Validate yyyy-mm-dd format.""" - if date == "": - return True - try: - datetime.strptime(record, "%Y-%m-%d") - return True - except ValueError: - return False - - -def is_dates(dates: list) -> bool: - """Validate list of dates (yyyy-mm-dd, yyyy-mm-dd).""" - if not dates: - return True - check = True - for record in dates: - if not is_date(record): - check = False - return check - - -def is_month_day(record) -> bool: - """Validate mm/dd format.""" - try: - datetime.strptime(record, "%m/%d") - return True - except ValueError: - return False diff --git a/custom_components/garbage_collection/manifest.json b/custom_components/garbage_collection/manifest.json index 6076cb87..59e36407 100644 --- a/custom_components/garbage_collection/manifest.json +++ b/custom_components/garbage_collection/manifest.json @@ -1,10 +1,11 @@ { "domain": "garbage_collection", "name": "Garbage Collection", - "version": "4.7.8", + "version": "4.8.3", "documentation": "https://github.com/bruxy70/Garbage-Collection/", "issue_tracker": "https://github.com/bruxy70/Garbage-Collection/issues", "iot_class": "calculated", + "integration_type": "helper", "dependencies": [], "config_flow": true, "codeowners": [ diff --git a/custom_components/garbage_collection/sensor.py b/custom_components/garbage_collection/sensor.py index 7793e7a0..dccc0afc 100644 --- a/custom_components/garbage_collection/sensor.py +++ b/custom_components/garbage_collection/sensor.py @@ -1,11 +1,10 @@ """Sensor platform for garbage_collection.""" from __future__ import annotations -import asyncio import logging from datetime import date, datetime, time, timedelta +from typing import Any, Dict, Generator -import homeassistant.util.dt as dt_util from dateutil.relativedelta import relativedelta from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -15,8 +14,9 @@ CONF_NAME, WEEKDAYS, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.discovery import async_load_platform +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity @@ -29,16 +29,11 @@ THROTTLE_INTERVAL = timedelta(seconds=60) -def now() -> datetime: - """Return current date and time. Needed for testing.""" - return dt_util.now() - - async def async_setup_entry( - _, config_entry: ConfigEntry, async_add_devices: AddEntitiesCallback + _: HomeAssistant, config_entry: ConfigEntry, async_add_devices: AddEntitiesCallback ) -> None: """Create garbage collection entities defined in config_flow and add them to HA.""" - frequency = config_entry.data.get(const.CONF_FREQUENCY) + frequency = config_entry.options.get(const.CONF_FREQUENCY) name = ( config_entry.title if config_entry.title is not None @@ -65,6 +60,9 @@ class GarbageCollection(RestoreEntity): """GarbageCollection Sensor class.""" __slots__ = ( + "_attr_icon", + "_attr_name", + "_attr_state", "_collection_dates", "_date_format", "_days", @@ -73,13 +71,10 @@ class GarbageCollection(RestoreEntity): "_icon_normal", "_icon_today", "_icon_tomorrow", - "_icon", "_last_month", "_last_updated", "_manual", - "_name", "_next_date", - "_state", "_verbose_format", "_verbose_state", "config_entry", @@ -89,22 +84,22 @@ class GarbageCollection(RestoreEntity): def __init__(self, config_entry: ConfigEntry) -> None: """Read configuration and initialise class variables.""" - config = config_entry.data + config = config_entry.options self.config_entry = config_entry - self._name = ( + self._attr_name = ( config_entry.title if config_entry.title is not None else config.get(CONF_NAME) ) self._hidden = config.get(ATTR_HIDDEN, False) self._manual = config.get(const.CONF_MANUAL) - first_month = config.get(const.CONF_FIRST_MONTH) + first_month = config.get(const.CONF_FIRST_MONTH, const.DEFAULT_FIRST_MONTH) self._first_month: int = ( const.MONTH_OPTIONS.index(first_month) + 1 if first_month in const.MONTH_OPTIONS else 1 ) - last_month = config.get(const.CONF_LAST_MONTH) + last_month = config.get(const.CONF_LAST_MONTH, const.DEFAULT_LAST_MONTH) self._last_month: int = ( const.MONTH_OPTIONS.index(last_month) + 1 if last_month in const.MONTH_OPTIONS @@ -115,9 +110,13 @@ def __init__(self, config_entry: ConfigEntry) -> None: self._icon_today = config.get(const.CONF_ICON_TODAY) self._icon_tomorrow = config.get(const.CONF_ICON_TOMORROW) exp = config.get(const.CONF_EXPIRE_AFTER) - self.expire_after: time | None - self.expire_after = ( - None if exp is None else datetime.strptime(exp, "%H:%M").time() + self.expire_after: time | None = ( + None + if ( + exp is None + or datetime.strptime(exp, "%H:%M:%S").time() == time(0, 0, 0) + ) + else datetime.strptime(exp, "%H:%M:%S").time() ) self._date_format = config.get( const.CONF_DATE_FORMAT, const.DEFAULT_DATE_FORMAT @@ -130,51 +129,50 @@ def __init__(self, config_entry: ConfigEntry) -> None: self._last_updated: datetime | None = None self.last_collection: datetime | None = None self._days: int | None = None - self._state = "" if bool(self._verbose_state) else 2 - self._icon = self._icon_normal + self._attr_state = "" if bool(self._verbose_state) else 2 + self._attr_icon = self._icon_normal - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """When sensor is added to hassio, add it to calendar.""" await super().async_added_to_hass() - if const.DOMAIN not in self.hass.data: - self.hass.data[const.DOMAIN] = {} - if const.SENSOR_PLATFORM not in self.hass.data[const.DOMAIN]: - self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM] = {} self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM][self.entity_id] = self + # Restore stored state if (state := await self.async_get_last_state()) is not None: + self._last_updated = None # Unblock update - after options change + self._attr_state = state.state + self._days = state.attributes[const.ATTR_DAYS] + next_date = helpers.parse_datetime(state.attributes[const.ATTR_NEXT_DATE]) + self._next_date = None if next_date is None else next_date.date() self.last_collection = helpers.parse_datetime( - state.attributes.get(const.ATTR_LAST_COLLECTION) + state.attributes[const.ATTR_LAST_COLLECTION] ) + # Create device device_registry = dr.async_get(self.hass) device_registry.async_get_or_create( config_entry_id=self.config_entry.entry_id, identifiers={(const.DOMAIN, self.unique_id)}, - name=self.name, + name=self._attr_name, manufacturer="bruxy70", ) + # Create or add to calendar if not self.hidden: if const.CALENDAR_PLATFORM not in self.hass.data[const.DOMAIN]: self.hass.data[const.DOMAIN][ const.CALENDAR_PLATFORM ] = EntitiesCalendarData(self.hass) _LOGGER.debug("Creating garbage_collection calendar") - self.hass.async_create_task( - async_load_platform( - self.hass, - const.CALENDAR_PLATFORM, - const.DOMAIN, - {"name": const.CALENDAR_NAME}, - {"name": const.CALENDAR_NAME}, - ) + await self.hass.config_entries.async_forward_entry_setup( + self.config_entry, const.CALENDAR_PLATFORM ) + self.hass.data[const.DOMAIN][const.CALENDAR_PLATFORM].add_entity( self.entity_id ) - async def async_will_remove_from_hass(self): + async def async_will_remove_from_hass(self) -> None: """When sensor is added to hassio, remove it.""" await super().async_will_remove_from_hass() del self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM][self.entity_id] @@ -183,12 +181,14 @@ async def async_will_remove_from_hass(self): ) @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique ID to use for this sensor.""" - return self.config_entry.data.get("unique_id", None) + if "unique_id" in self.config_entry.data: # From legacy config + return self.config_entry.data["unique_id"] + return self.config_entry.entry_id @property - def device_info(self): + def device_info(self) -> DeviceInfo | None: """Return device info.""" return { "identifiers": {(const.DOMAIN, self.unique_id)}, @@ -197,62 +197,72 @@ def device_info(self): } @property - def name(self): + def name(self) -> str | None: """Return the name of the sensor.""" - return self._name + return self._attr_name @property - def next_date(self): + def next_date(self) -> date | None: """Return next date attribute.""" return self._next_date @property - def hidden(self): + def hidden(self) -> bool: """Return the hidden attribute.""" return self._hidden @property - def state(self): + def native_unit_of_measurement(self) -> str | None: + """Return unit of measurement - None for numerical value.""" + return None + + @property + def native_value(self) -> object: """Return the state of the sensor.""" - return self._state + return self._attr_state + + @property + def last_updated(self) -> datetime | None: + """Return when the sensor was last updated.""" + return self._last_updated @property - def icon(self): + def icon(self) -> str: """Return the entity icon.""" - return self._icon + return self._attr_icon @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> Dict[str, Any]: """Return the state attributes.""" - res = {} - if self._next_date is None: - res[const.ATTR_NEXT_DATE] = None - else: - res[const.ATTR_NEXT_DATE] = datetime( + state_attr = { + const.ATTR_DAYS: self._days, + const.ATTR_LAST_COLLECTION: self.last_collection, + const.ATTR_LAST_UPDATED: self._last_updated, + const.ATTR_NEXT_DATE: None + if self._next_date is None + else datetime( self._next_date.year, self._next_date.month, self._next_date.day - ).astimezone() - res[const.ATTR_DAYS] = self._days - res[const.ATTR_LAST_COLLECTION] = self.last_collection - res[const.ATTR_LAST_UPDATED] = self._last_updated - # Needed for translations to work - res[ATTR_DEVICE_CLASS] = self.DEVICE_CLASS - return res + ).astimezone(), + # Needed for translations to work + ATTR_DEVICE_CLASS: self.DEVICE_CLASS, + } + return state_attr @property - def DEVICE_CLASS(self): # pylint: disable=C0103 + def DEVICE_CLASS(self) -> str: # pylint: disable=C0103 """Return the class of the sensor.""" return const.DEVICE_CLASS - def __repr__(self): + def __repr__(self) -> str: """Return main sensor parameters.""" return ( - f"{self.__class__.__name__}(name={self._name}, " + f"{self.__class__.__name__}(name={self._attr_name}, " f"entity_id={self.entity_id}, " - f"state={self.state}\n" + f"state={self.state}, " f"attributes={self.extra_state_attributes})" ) - async def _async_find_candidate_date(self, day1: date) -> date | None: + def _find_candidate_date(self, day1: date) -> date | None: """Find the next possible date starting from day1. Only based on calendar, not looking at include/exclude days. @@ -266,12 +276,12 @@ async def _async_ready_for_update(self) -> bool: Skip the update if the sensor was updated today Except for the sensors with with next date today and after the expiration time """ - current_date_time = now() + current_date_time = helpers.now() today = current_date_time.date() try: ready_for_update = bool(self._last_updated.date() != today) # type: ignore except AttributeError: - ready_for_update = True + return True try: if self._next_date == today and ( ( @@ -283,7 +293,7 @@ async def _async_ready_for_update(self) -> bool: and self.last_collection.date() == today ) ): - ready_for_update = True + return True except (AttributeError, TypeError): pass return ready_for_update @@ -292,75 +302,58 @@ def date_inside(self, dat: date) -> bool: """Check if the date is inside first and last date.""" month = dat.month if self._first_month <= self._last_month: - return bool(month >= self._first_month and month <= self._last_month) - return bool(month <= self._last_month or month >= self._first_month) + return bool(self._first_month <= month <= self._last_month) + return bool(self._first_month <= month or month <= self._last_month) def move_to_range(self, day: date) -> date: """If the date is not in range, move to the range.""" if not self.date_inside(day): year = day.year month = day.month - if self._first_month <= self._last_month and month > self._last_month: + if self._first_month <= self._last_month < month: _LOGGER.debug( "(%s) %s outside the range, lookig from %s next year", - self._name, + self._attr_name, day, const.MONTH_OPTIONS[self._first_month - 1], ) return date(year + 1, self._first_month, 1) - else: - _LOGGER.debug( - "(%s) %s outside the range, searching from %s", - self._name, - day, - const.MONTH_OPTIONS[self._first_month - 1], - ) - return date(year, self._first_month, 1) + _LOGGER.debug( + "(%s) %s outside the range, searching from %s", + self._attr_name, + day, + const.MONTH_OPTIONS[self._first_month - 1], + ) + return date(year, self._first_month, 1) return day - async def _async_find_next_date(self, first_date: date) -> date | None: - """Get date within configured date range.""" - # Today's collection can be triggered by past collection with offset - # Move starting date if today is out of range - day1 = self.move_to_range(first_date) - next_date = None - while next_date is None: + def collection_schedule( + self, date1: date | None = None, date2: date | None = None + ) -> Generator[date, None, None]: + """Get dates within configured date range.""" + today = helpers.now().date() + first_date: date = date(today.year - 1, 1, 1) if date1 is None else date1 + last_date: date = date(today.year + 1, 12, 31) if date2 is None else date2 + first_date = self.move_to_range(first_date) + while True: try: - next_date = await self._async_find_candidate_date(day1) + next_date = self._find_candidate_date(first_date) except (TypeError, ValueError): - return None - if next_date is None: - return None + return + if next_date is None or next_date > last_date: + return if (new_date := self.move_to_range(next_date)) != next_date: - day1 = new_date # continue from next year - next_date = None + first_date = new_date # continue from next year else: - # Date is before starting date - if next_date < first_date: - next_date = None - day1 += relativedelta(days=1) # look from the next day - return next_date + yield next_date + first_date = next_date + relativedelta(days=1) # look from the next day async def _async_load_collection_dates(self) -> None: """Fill the collection dates list.""" self._collection_dates.clear() - today = now().date() - start_date = end_date = date(today.year - 1, 1, 1) - end_date = date(today.year + 1, 12, 31) - try: - next_date = await self._async_find_next_date(start_date) - except asyncio.TimeoutError: - _LOGGER.error("(%s) Timeout loading collection dates", self._name) - return - - while ( - next_date is not None and next_date >= start_date and next_date <= end_date - ): - self._collection_dates.append(next_date) - next_date = await self._async_find_next_date( - next_date + relativedelta(days=1) - ) - self._collection_dates.sort() + for collection_date in self.collection_schedule(): + self._collection_dates.append(collection_date) + # self._collection_dates.sort() async def add_date(self, collection_date: date) -> None: """Add date to _collection_dates.""" @@ -368,7 +361,7 @@ async def add_date(self, collection_date: date) -> None: self._collection_dates.append(collection_date) self._collection_dates.sort() else: - _LOGGER.error( + _LOGGER.warning( "%s not added to %s - already on the collection schedule", collection_date, self.name, @@ -379,7 +372,7 @@ async def remove_date(self, collection_date: date) -> None: try: self._collection_dates.remove(collection_date) except ValueError: - _LOGGER.error( + _LOGGER.warning( "%s not removed from %s - not in the collection schedule", collection_date, self.name, @@ -387,7 +380,7 @@ async def remove_date(self, collection_date: date) -> None: def get_next_date(self, first_date: date, ignore_today=False) -> date | None: """Get next date from self._collection_dates.""" - current_date_time = now() + current_date_time = helpers.now() for d in self._collection_dates: # pylint: disable=invalid-name if d < first_date: continue @@ -411,10 +404,11 @@ async def async_update(self) -> None: if not await self._async_ready_for_update() or not self.hass.is_running: return - _LOGGER.debug("(%s) Calling update", self._name) + _LOGGER.debug("(%s) Calling update", self._attr_name) await self._async_load_collection_dates() _LOGGER.debug( - "(%s) Dates loaded, firing a garbage_collection_loaded event", self._name + "(%s) Dates loaded, firing a garbage_collection_loaded event", + self._attr_name, ) event_data = { "entity_id": self.entity_id, @@ -426,44 +420,47 @@ async def async_update(self) -> None: def update_state(self) -> None: """Pick the first event from collection dates, update attributes.""" - _LOGGER.debug("(%s) Looking for next collection", self._name) - self._last_updated = now() + _LOGGER.debug("(%s) Looking for next collection", self._attr_name) + self._last_updated = helpers.now() today = self._last_updated.date() self._next_date = self.get_next_date(today) if self._next_date is not None: _LOGGER.debug( - "(%s) next_date (%s), today (%s)", self._name, self._next_date, today + "(%s) next_date (%s), today (%s)", + self._attr_name, + self._next_date, + today, ) self._days = (self._next_date - today).days next_date_txt = self._next_date.strftime(self._date_format) _LOGGER.debug( "(%s) Found next collection date: %s, that is in %d days", - self._name, + self._attr_name, next_date_txt, self._days, ) if self._days > 1: if bool(self._verbose_state): - self._state = self._verbose_format.format( + self._attr_state = self._verbose_format.format( date=next_date_txt, days=self._days ) - # self._state = "on_date" + # self._attr_state = "on_date" else: - self._state = 2 - self._icon = self._icon_normal + self._attr_state = 2 + self._attr_icon = self._icon_normal else: if self._days == 0: if bool(self._verbose_state): - self._state = const.STATE_TODAY + self._attr_state = const.STATE_TODAY else: - self._state = self._days - self._icon = self._icon_today + self._attr_state = self._days + self._attr_icon = self._icon_today elif self._days == 1: if bool(self._verbose_state): - self._state = const.STATE_TOMORROW + self._attr_state = const.STATE_TOMORROW else: - self._state = self._days - self._icon = self._icon_tomorrow + self._attr_state = self._days + self._attr_icon = self._icon_tomorrow else: self._days = None @@ -476,7 +473,7 @@ class WeeklyCollection(GarbageCollection): def __init__(self, config_entry: ConfigEntry) -> None: """Read parameters specific for Weekly Collection Frequency.""" super().__init__(config_entry) - config = config_entry.data + config = config_entry.options self._collection_days = config.get(const.CONF_COLLECTION_DAYS, []) self._period: int self._first_week: int @@ -494,7 +491,7 @@ def __init__(self, config_entry: ConfigEntry) -> None: self._period = config.get(const.CONF_PERIOD, 1) self._first_week = config.get(const.CONF_FIRST_WEEK, 1) - async def _async_find_candidate_date(self, day1: date) -> date | None: + def _find_candidate_date(self, day1: date) -> date | None: """Calculate possible date, for weekly frequency.""" week = day1.isocalendar()[1] weekday = day1.weekday() @@ -524,7 +521,7 @@ class DailyCollection(GarbageCollection): def __init__(self, config_entry: ConfigEntry) -> None: """Read parameters specific for Daily Collection Frequency.""" super().__init__(config_entry) - config = config_entry.data + config = config_entry.options self._period = config.get(const.CONF_PERIOD) self._first_date: date | None try: @@ -532,7 +529,7 @@ def __init__(self, config_entry: ConfigEntry) -> None: except ValueError: self._first_date = None - async def _async_find_candidate_date(self, day1: date) -> date | None: + def _find_candidate_date(self, day1: date) -> date | None: """Calculate possible date, for every-n-days frequency.""" try: if (day1 - self._first_date).days % self._period == 0: # type: ignore @@ -542,7 +539,7 @@ async def _async_find_candidate_date(self, day1: date) -> date | None: ) except TypeError as error: raise ValueError( - f"({self._name}) Please configure first_date and period " + f"({self._attr_name}) Please configure first_date and period " "for every-n-days collection frequency." ) from error return day1 + relativedelta(days=offset) @@ -562,7 +559,7 @@ class MonthlyCollection(GarbageCollection): def __init__(self, config_entry: ConfigEntry) -> None: """Read parameters specific for Monthly Collection Frequency.""" super().__init__(config_entry) - config = config_entry.data + config = config_entry.options self._collection_days = config.get(const.CONF_COLLECTION_DAYS, []) self._monthly_force_week_numbers = config.get( const.CONF_FORCE_WEEK_NUMBERS, False @@ -571,7 +568,7 @@ def __init__(self, config_entry: ConfigEntry) -> None: self._week_order_numbers: list order_numbers: list = [] if const.CONF_WEEKDAY_ORDER_NUMBER in config: - order_numbers = list(map(int, config.get(const.CONF_WEEKDAY_ORDER_NUMBER))) + order_numbers = list(map(int, config[const.CONF_WEEKDAY_ORDER_NUMBER])) if self._monthly_force_week_numbers: self._weekday_order_numbers = [] self._week_order_numbers = order_numbers @@ -611,7 +608,7 @@ def nth_weekday_date( + (weekday_number - 1) * 7 ) - async def _async_monthly_candidate(self, day1: date) -> date: + def _monthly_candidate(self, day1: date) -> date: """Calculate possible date, for monthly frequency.""" if self._monthly_force_week_numbers: for week_order_number in self._week_order_numbers: @@ -647,13 +644,13 @@ async def _async_monthly_candidate(self, day1: date) -> date: WEEKDAYS.index(self._collection_days[0]), ) - async def _async_find_candidate_date(self, day1: date) -> date | None: + def _find_candidate_date(self, day1: date) -> date | None: if self._period is None or self._period == 1: - return await self._async_monthly_candidate(day1) + return self._monthly_candidate(day1) else: - candidate_date = await self._async_monthly_candidate(day1) + candidate_date = self._monthly_candidate(day1) while (candidate_date.month - self._first_month) % self._period != 0: - candidate_date = await self._async_monthly_candidate( + candidate_date = self._monthly_candidate( candidate_date + relativedelta(days=1) ) return candidate_date @@ -667,17 +664,17 @@ class AnnualCollection(GarbageCollection): def __init__(self, config_entry: ConfigEntry) -> None: """Read parameters specific for Annual Collection Frequency.""" super().__init__(config_entry) - config = config_entry.data + config = config_entry.options self._date = config.get(const.CONF_DATE) - async def _async_find_candidate_date(self, day1: date) -> date | None: + def _find_candidate_date(self, day1: date) -> date | None: """Calculate possible date, for annual frequency.""" year = day1.year try: conf_date = datetime.strptime(self._date, "%m/%d").date() except TypeError as error: raise ValueError( - f"({self._name}) Please configure the date " + f"({self._attr_name}) Please configure the date " "for annual collection frequency." ) from error if (candidate_date := date(year, conf_date.month, conf_date.day)) < day1: @@ -693,15 +690,17 @@ class GroupCollection(GarbageCollection): def __init__(self, config_entry: ConfigEntry) -> None: """Read parameters specific for Group Collection Frequency.""" super().__init__(config_entry) - config = config_entry.data + config = config_entry.options self._entities = config.get(CONF_ENTITIES, []) - async def _async_find_candidate_date(self, day1: date) -> date | None: + def _find_candidate_date(self, day1: date) -> date | None: """Calculate possible date, for group frequency.""" candidate_date = None try: for entity_id in self._entities: - entity = self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM][entity_id] + entity: GarbageCollection = self.hass.data[const.DOMAIN][ + const.SENSOR_PLATFORM + ][entity_id] next_date = entity.get_next_date(day1) if next_date is not None and ( candidate_date is None or next_date < candidate_date @@ -710,7 +709,7 @@ async def _async_find_candidate_date(self, day1: date) -> date | None: except KeyError as error: raise ValueError from error except TypeError as error: - _LOGGER.error("(%s) Please add entities for the group.", self._name) + _LOGGER.error("(%s) Please add entities for the group.", self._attr_name) raise ValueError from error return candidate_date @@ -719,7 +718,7 @@ async def _async_ready_for_update(self) -> bool: For group sensors wait for update of the sensors in the group """ - current_date_time = now() + current_date_time = helpers.now() today = current_date_time.date() try: ready_for_update = bool(self._last_updated.date() != today) # type: ignore @@ -728,17 +727,14 @@ async def _async_ready_for_update(self) -> bool: members_ready = True for entity_id in self._entities: try: - entity = self.hass.data[const.DOMAIN][const.SENSOR_PLATFORM][entity_id] + entity: GarbageCollection = self.hass.data[const.DOMAIN][ + const.SENSOR_PLATFORM + ][entity_id] await entity.async_update() except KeyError: - pass - state_object = self.hass.states.get(entity_id) - if state_object is None: members_ready = False break - if ( - last_updated := state_object.attributes.get(const.ATTR_LAST_UPDATED) - ) is None: + if (last_updated := entity.last_updated) is None: ready_for_update = True continue # Wait for all members to get updated @@ -756,15 +752,10 @@ async def _async_ready_for_update(self) -> bool: class BlankCollection(GarbageCollection): """No collection - for mnual update.""" - async def _async_find_candidate_date(self, day1: date) -> date | None: + def _find_candidate_date(self, day1: date) -> date | None: """Do not return any date for blank frequency.""" return None - async def _async_find_next_date(self, first_date: date) -> date | None: - """Get date within configured date range.""" - # Blank frequency always returns None. - return None - async def _async_load_collection_dates(self) -> None: """Clear collection dates (filled in by the blueprint).""" self._collection_dates.clear() @@ -775,10 +766,11 @@ async def async_update(self) -> None: if not await self._async_ready_for_update() or not self.hass.is_running: return - _LOGGER.debug("(%s) Calling update", self._name) + _LOGGER.debug("(%s) Calling update", self._attr_name) await self._async_load_collection_dates() _LOGGER.debug( - "(%s) Dates loaded, firing a garbage_collection_loaded event", self._name + "(%s) Dates loaded, firing a garbage_collection_loaded event", + self._attr_name, ) event_data = { "entity_id": self.entity_id, diff --git a/custom_components/garbage_collection/translations/cs.json b/custom_components/garbage_collection/translations/cs.json index 33224994..014c23bb 100644 --- a/custom_components/garbage_collection/translations/cs.json +++ b/custom_components/garbage_collection/translations/cs.json @@ -25,7 +25,7 @@ "collection_days": "Dny svozu", "first_month": "První měsíc svozu", "last_month": "Poslední měsíc svozu", - "period": "Perioda (svoz každých n týdnů/dnů): (1-365)", + "period": "Perioda (svoz každých n týdnů/dnů): (1-1000)", "first_week": "První týden svozu (1-52)", "first_date": "První datum", "weekday_order_number": "Pořadí dne v měsící (např. první středa v měsíci)", @@ -44,7 +44,7 @@ "time": "Špatný formát času!", "weekday_order_number": "Vyber jeden nebo více dní!", "week_order_number": "Vyber jeden nebo více týdnů!", - "period": "Perioda musí být číslo mezi 1 a 365", + "period": "Perioda musí být číslo mezi 1 a 1000", "first_week": "První týden musí být číslo mezi 1 a 52", "date": "Špatný formát data!" }, @@ -77,7 +77,7 @@ "collection_days": "Dny svozu", "first_month": "První měsíc svozu", "last_month": "Poslední měsíc scozu", - "period": "Perioda (svoz každých n týdnů-dnů): (1-365)", + "period": "Perioda (svoz každých n týdnů-dnů): (1-1000)", "first_week": "První týden svozu (1-52)", "first_date": "První datum", "weekday_order_number": "Pořadí dne v měsící (např. první středa v měsíci)", @@ -96,7 +96,7 @@ "time": "Špatný formát času!", "weekday_order_number": "Vyber jeden nebo více dní!", "week_order_number": "Vyber jeden nebo více týdnů!", - "period": "Perioda musí být číslo mezi 1 a 365", + "period": "Perioda musí být číslo mezi 1 a 1000", "first_week": "První týden musí být číslo mezi 1 a 52", "date": "Špatný formát data!" } diff --git a/custom_components/garbage_collection/translations/da.json b/custom_components/garbage_collection/translations/da.json new file mode 100644 index 00000000..774f0267 --- /dev/null +++ b/custom_components/garbage_collection/translations/da.json @@ -0,0 +1,105 @@ +{ + "config": { + "step": { + "user": { + "title": "Garbage Collection - Afhentningsfrekvens (1/2)", + "description": "Angiv navnet på sensoren og konfigurer sensorparametre. For mere info se: https://github.com/bruxy70/Garbage-Collection", + "data": { + "name": "Visningsnavn", + "hidden": "Skjul i kalender", + "frequency": "Frekvens", + "manual_update": "Manuel opdatering - sensortilstand opdateres manuelt af en service (blueprint)", + "icon_normal": "Ikon (mdi:trash-can) - ikke påkrævet", + "icon_tomorrow": "Ikon ved afhentning i morgen (mdi:delete-restore) - ikke påkrævet", + "icon_today": "Ikon ved afhentning i dag (mdi:delete-circle) - ikke påkrævet", + "expire_after": "Forældes efter (HH:MM) - ikke påkrævet", + "verbose_state": "Verbos tilstand (tekst i stedet for tal)" + } + }, + "detail": { + "title": "Garbage Collection - Yderligere parametre (2/2)", + "description": "For mere info se: https://github.com/bruxy70/Garbage-Collection", + "data": { + "date": "Dato (mm/dd)", + "entities": "Liste af enheder (kommasepareret)", + "collection_days": "Afhentningsdage", + "first_month": "Første afhentningsmåned", + "last_month": "Sidste afhentningsmåned", + "period": "Afhentning sker hver n uger/dage: (1-1000)", + "first_week": "Første afhentningsuge (1-52)", + "first_date": "Første dato", + "weekday_order_number": "Specifikke ugedage i måneden (fx første onsdag i måneden)", + "force_week_order_numbers": "Brug specifik uge i måneden i stedet for specifik ugedag (fx onsdag i første uge i måneden)", + "verbose_format": "Verbost format (anvend `date` og `days` variablerne (i tuborgklammer))", + "date_format": "Datoformat (se http://strftime.org/)" + } + } + }, + "error": { + "value": "Ugyldig værdi. Check dit input!", + "icon": "Ikoner skal angives i formen 'præfiks:navn'.", + "days": "Vælg en eller flere dage!", + "entities": "Enheden findes ikke!", + "month_day": "Ugyldigt dato format!", + "time": "Ugyldigt tids format!", + "weekday_order_number": "Vælg en eller flere dage", + "week_order_number": "Vælg en eller flere uger", + "period": "Afhentningsperioden skal være et tal mellem 1 og 1000", + "first_week": "Første afhentningsuge skal være et tal mellem 1 and 52", + "date": "Ugyldigt dato format!" + }, + "abort": { + "single_instance_allowed": "Det er kun tilladt at have én konfiguration af Garbage Collection." + } + }, + "options": { + "step": { + "init": { + "title": "Garbage Collection - Afhentningsfrekvens (1/2)", + "description": "Ændr sensorparametre. More info on https://github.com/bruxy70/Garbage-Collection", + "data": { + "hidden": "Skjul i kalender", + "frequency": "Frekvens", + "manual_update": "Manuel opdatering - sensortilstand opdateres manuelt af en service (blueprint)", + "icon_normal": "Ikon (mdi:trash-can) - ikke påkrævet", + "icon_tomorrow": "Ikon ved afhentning i morgen (mdi:delete-restore) - ikke påkrævet", + "icon_today": "Ikon ved afhentning i dag (mdi:delete-circle) - ikke påkrævet", + "expire_after": "Forældes efter (HH:MM) - ikke påkrævet", + "verbose_state": "Verbos tilstand (tekst i stedet for tal)" + } + }, + "detail": { + "title": "Garbage Collection - Yderligere parametre (2/2)", + "description": "For mere info se: https://github.com/bruxy70/Garbage-Collection", + "data": { + "date": "Dato (mm/dd)", + "entities": "Liste af enheder (kommasepareret)", + "collection_days": "Afhentningsdage", + "first_month": "Første afhentningsmåned", + "last_month": "Sidste afhentningsmåned", + "period": "Afhentning sker hver n uger/dage: (1-1000)", + "first_week": "Første afhentningsuge (1-52)", + "first_date": "Første dato", + "weekday_order_number": "Specifikke ugedage i måneden (fx første onsdag i måneden)", + "force_week_order_numbers": "Brug specifik uge i måneden i stedet for specifik ugedag (fx onsdag i første uge i måneden)", + "verbose_format": "Verbost format (anvend `date` og `days` variablerne (i tuborgklammer))", + "date_format": "Datoformat (se http://strftime.org/)" + } + } + }, + "error": { + "value": "Ugyldig værdi. Check dit input!", + "icon": "Ikoner skal angives i formen 'præfiks:navn'.", + "days": "Vælg en eller flere dage!", + "entities": "Enheden findes ikke!", + "month_day": "Ugyldigt dato format!", + "time": "Ugyldigt tids format!", + "weekday_order_number": "Vælg en eller flere dage", + "week_order_number": "Vælg en eller flere uger", + "period": "Afhentningsperioden skal være et tal mellem 1 og 1000", + "first_week": "Første afhentningsuge skal være et tal mellem 1 and 52", + "date": "Ugyldigt dato format!" + } + } + +} \ No newline at end of file diff --git a/custom_components/garbage_collection/translations/en.json b/custom_components/garbage_collection/translations/en.json index 619d4138..c1f18da3 100644 --- a/custom_components/garbage_collection/translations/en.json +++ b/custom_components/garbage_collection/translations/en.json @@ -25,7 +25,7 @@ "collection_days": "Collection days", "first_month": "First collection month", "last_month": "Last collection month", - "period": "Collection every n weeks/days: (1-365)", + "period": "Collection every n weeks/days: (1-1000)", "first_week": "First collection week (1-52)", "first_date": "First date", "weekday_order_number": "Order of the weekday in the month (e.g. first Wednesday of the month)", @@ -44,7 +44,7 @@ "time": "Invalid time format!", "weekday_order_number": "Select 1 or more days", "week_order_number": "Select 1 or more weeks", - "period": "Period must be a number between 1 and 365", + "period": "Period must be a number between 1 and 1000", "first_week": "First week must be a number between 1 and 52", "date": "Invalid date format!" }, @@ -77,7 +77,7 @@ "collection_days": "Collection days", "first_month": "First collection month", "last_month": "Last collection month", - "period": "Collection every n weeks/days: (1-365)", + "period": "Collection every n weeks/days: (1-1000)", "first_week": "First collection week (1-52)", "first_date": "First date", "weekday_order_number": "Order of the weekday in the month (e.g. first Wednesday of the month)", @@ -96,7 +96,7 @@ "time": "Invalid time format!", "weekday_order_number": "Select 1 or more days", "week_order_number": "Select 1 or more weeks", - "period": "Period must be a number between 1 and 365", + "period": "Period must be a number between 1 and 1000", "first_week": "First week must be a number between 1 and 52", "date": "Invalid date format!" } diff --git a/custom_components/garbage_collection/translations/es.json b/custom_components/garbage_collection/translations/es.json index 496c4eda..6d65d3de 100644 --- a/custom_components/garbage_collection/translations/es.json +++ b/custom_components/garbage_collection/translations/es.json @@ -25,7 +25,7 @@ "collection_days": "Días de recogida", "first_month": "Primer mes de recogida", "last_month": "Último mes de recogida", - "period": "Recolección cada n semanas / días: (1-365)", + "period": "Recolección cada n semanas / días: (1-1000)", "first_week": "Primera semana de recolección (1-52)", "first_date": "Primera fecha", "weekday_order_number": "Orden del día de la semana en el mes (por ejemplo, primer miércoles del mes)", @@ -44,7 +44,7 @@ "time": "Formato de hora inválido!", "weekday_order_number": "Seleccione 1 o más días", "week_order_number": "Seleccione 1 o más semanas", - "period": "El período debe ser un número entre 1 y 365", + "period": "El período debe ser un número entre 1 y 1000", "first_week": "La primera semana debe ser un número entre 1 y 52", "date": "Formato de fecha inválido!" }, @@ -77,7 +77,7 @@ "collection_days": "Días de recogida", "first_month": "Primer mes de recogida", "last_month": "Último mes de recogida", - "period": "Recolección cada n semanas / días: (1-365)", + "period": "Recolección cada n semanas / días: (1-1000)", "first_week": "Primera semana de recolección (1-52)", "first_date": "Primera fecha", "weekday_order_number": "Orden del día de la semana en el mes (por ejemplo, primer miércoles del mes)", @@ -96,7 +96,7 @@ "time": "Formato de hora inválido!", "weekday_order_number": "Seleccione 1 o más días", "week_order_number": "Seleccione 1 o más semanas", - "period": "El período debe ser un número entre 1 y 365", + "period": "El período debe ser un número entre 1 y 1000", "first_week": "La primera semana debe ser un número entre 1 y 52", "date": "Formato de fecha inválido!" } diff --git a/custom_components/garbage_collection/translations/et.json b/custom_components/garbage_collection/translations/et.json index 5cf720b8..bd68ee31 100644 --- a/custom_components/garbage_collection/translations/et.json +++ b/custom_components/garbage_collection/translations/et.json @@ -25,7 +25,7 @@ "collection_days": "Tühjendamise nädalapäevad", "first_month": "Esimene prügiveo kuu", "last_month": "Viimane prügiveo kuu", - "period": "Prügivedu iga n päeva/nädala tagant: (1-365)", + "period": "Prügivedu iga n päeva/nädala tagant: (1-1000)", "first_week": "Esimese prügiveo nädal (1-52)", "first_date": "Esimese prügiveo kuupäev", "weekday_order_number": "Kuu nädalapäevade järjekord (nt kuu esimene kolmapäev)", @@ -44,7 +44,7 @@ "time": "Vigane kellaaja formaat!", "weekday_order_number": "Valige üks või rohkem päevi", "week_order_number": "Valige üks või rohkem nädalat", - "period": "Välp peab olema number 1 ja 365 vahel", + "period": "Välp peab olema number 1 ja 1000 vahel", "first_week": "Esimene nädal peab olema number 1 ja 52 vahel", "date": "Vigane kuupäeva formaat!!" }, @@ -77,7 +77,7 @@ "collection_days": "Tühjendamise nädalapäevad", "first_month": "Esimene prügiveo kuu", "last_month": "Viimane prügiveo kuu", - "period": "Prügivedu iga n päeva/nädala tagant: (1-365)", + "period": "Prügivedu iga n päeva/nädala tagant: (1-1000)", "first_week": "Esimese prügiveo nädal (1-52)", "first_date": "Esimese prügiveo kuupäev", "weekday_order_number": "Kuu nädalapäevade järjekord (nt kuu esimene kolmapäev)", @@ -96,7 +96,7 @@ "time": "Vigane kellaaja formaat!", "weekday_order_number": "Valige üks või rohkem päevi", "week_order_number": "Valige üks või rohkem nädalat", - "period": "Välp peab olema number 1 ja 365 vahel", + "period": "Välp peab olema number 1 ja 1000 vahel", "first_week": "Esimene nädal peab olema number 1 ja 52 vahel", "date": "Vigane kuupäeva formaat!!" } diff --git a/custom_components/garbage_collection/translations/fr.json b/custom_components/garbage_collection/translations/fr.json index 96101a14..16618d15 100644 --- a/custom_components/garbage_collection/translations/fr.json +++ b/custom_components/garbage_collection/translations/fr.json @@ -26,7 +26,7 @@ "collection_days": "Jours de collecte", "first_month": "Premier mois de la collecte", "last_month": "Dernier mois de la collecte", - "period": "Collecte toutes les n semaines/jours: (1-365)", + "period": "Collecte toutes les n semaines/jours: (1-1000)", "first_week": "Première semaine de la collecte (1-52)", "first_date": "Première date", "weekday_order_number": "Ordre du jour de la semaine dans le mois (par exemple, premier mercredi du mois)", @@ -45,7 +45,7 @@ "time": "Format d'heure invalide !", "weekday_order_number": "Choisir un ou plusieurs jours", "week_order_number": "Choisir une ou plusieurs semaines", - "period": "La semaine doit être un nombre entre 1 et 365", + "period": "La semaine doit être un nombre entre 1 et 1000", "first_week": "La première semaine doit être un nombre entre 1 et 52", "date": "Format de date invalide !" }, @@ -79,7 +79,7 @@ "collection_days": "Jours de collecte", "first_month": "Premier mois de la collecte", "last_month": "Dernier mois de la collecte", - "period": "Collecte toutes les n semaines/jours: (1-365)", + "period": "Collecte toutes les n semaines/jours: (1-1000)", "first_week": "Première semaine de la collecte (1-52)", "first_date": "Première date", "weekday_order_number": "Ordre du jour de la semaine dans le mois (par exemple, premier mercredi du mois)", @@ -98,7 +98,7 @@ "time": "Format d'heure invalide !", "weekday_order_number": "Choisir un ou plusieurs jours", "week_order_number": "Choisir une ou plusieurs semaines", - "period": "La semaine doit être un nombre entre 1 et 365", + "period": "La semaine doit être un nombre entre 1 et 1000", "first_week": "La première semaine doit être un nombre entre 1 et 52", "date": "Format de date invalide !" } diff --git a/custom_components/garbage_collection/translations/it.json b/custom_components/garbage_collection/translations/it.json index 8bcb5858..418c020e 100644 --- a/custom_components/garbage_collection/translations/it.json +++ b/custom_components/garbage_collection/translations/it.json @@ -25,7 +25,7 @@ "collection_days": "Giorni di raccolta", "first_month": "Prima raccolta del mese", "last_month": "Ultima raccolta del mese", - "period": "Raccolta ogni n settimane/giorno: (1-365)", + "period": "Raccolta ogni n settimane/giorno: (1-1000)", "first_week": "Prima raccolta della settimana (1-52)", "first_date": "Prima date", "weekday_order_number": "Ordine del giorno della settimana nel mese (ad esempio il primo mercoledì del mese)", @@ -44,7 +44,7 @@ "time": "Formato ora non valido!", "weekday_order_number": "Seleziona uno o più giorni", "week_order_number": "Seleziona uno o più settimani", - "period": "Il periodo deve essere un numero compreso tra 1 e 365", + "period": "Il periodo deve essere un numero compreso tra 1 e 1000", "first_week": "La prima settimana deve essere un numero tra 1 e 52", "date": "Formato data non valido!" }, @@ -77,7 +77,7 @@ "collection_days": "Giorni di raccolta", "first_month": "Prima raccolta del mese", "last_month": "Ultima raccolta del mese", - "period": "Raccolta ogni n settimane/giorno: (1-365)", + "period": "Raccolta ogni n settimane/giorno: (1-1000)", "first_week": "Prima raccolta della settimana (1-52)", "first_date": "Prima date", "weekday_order_number": "Ordine del giorno della settimana nel mese (ad esempio il primo mercoledì del mese)", @@ -96,7 +96,7 @@ "time": "Formato ora non valido!", "weekday_order_number": "Seleziona uno o più giorni", "week_order_number": "Seleziona uno o più settimani", - "period": "Il periodo deve essere un numero compreso tra 1 e 365", + "period": "Il periodo deve essere un numero compreso tra 1 e 1000", "first_week": "La prima settimana deve essere un numero tra 1 e 52", "date": "Formato data non valido!" } diff --git a/custom_components/garbage_collection/translations/pl.json b/custom_components/garbage_collection/translations/pl.json index ab6488f9..175d24ba 100644 --- a/custom_components/garbage_collection/translations/pl.json +++ b/custom_components/garbage_collection/translations/pl.json @@ -25,7 +25,7 @@ "collection_days": "Dni wywozu", "first_month": "Miesiąc pierwszego wywozu", "last_month": "Miesiąc ostatniego wywozu", - "period": "Wywóz co n dni/tygodni: (1-365)", + "period": "Wywóz co n dni/tygodni: (1-1000)", "first_week": "Tydzień pierwszego wywozu: (1-52)", "first_date": "Pierwsza data", "weekday_order_number": "Kolejność dni tygodnia w miesiącu (np. pierwsza środa miesiąca)", @@ -44,7 +44,7 @@ "time": "Nieprawidłowy format czasu!", "weekday_order_number": "Wybierz 1 lub więcej dni!", "week_order_number": "Wybierz 1 lub więcej tydzieńi!", - "period": "Okres musi być liczbą od 1 do 365.", + "period": "Okres musi być liczbą od 1 do 1000.", "first_week": "Pierwszy tydzień musi być liczbą między 1 a 52.", "date": "Nieprawidłowy format daty!" }, @@ -77,7 +77,7 @@ "collection_days": "Dni wywozu", "first_month": "Miesiąc pierwszego wywozu", "last_month": "Miesiąc ostatniego wywozu", - "period": "Wywóz co n dni/tygodni: (1-365)", + "period": "Wywóz co n dni/tygodni: (1-1000)", "first_week": "Tydzień pierwszego wywozu: (1-52)", "first_date": "Pierwsza data", "weekday_order_number": "Kolejność dni tygodnia w miesiącu (np. pierwsza środa miesiąca)", @@ -96,7 +96,7 @@ "time": "Nieprawidłowy format czasu!", "weekday_order_number": "Wybierz 1 lub więcej dni!", "week_order_number": "Wybierz 1 lub więcej tydzieńi!", - "period": "Okres musi być liczbą od 1 do 365.", + "period": "Okres musi być liczbą od 1 do 1000.", "first_week": "Pierwszy tydzień musi być liczbą między 1 a 52.", "date": "Nieprawidłowy format daty!" } diff --git a/custom_components/garbage_collection/translations/pt-BR.json b/custom_components/garbage_collection/translations/pt-BR.json index 2c7c1a85..8475e88e 100644 --- a/custom_components/garbage_collection/translations/pt-BR.json +++ b/custom_components/garbage_collection/translations/pt-BR.json @@ -25,7 +25,7 @@ "collection_days": "Dias de coleta", "first_month": "Primeiro mês de coletah", "last_month": "Último mês de coleta", - "period": "Coleta a cada n semanas/dias: (1-365)", + "period": "Coleta a cada n semanas/dias: (1-1000)", "first_week": "Primeira semana de coleta (1-52)", "first_date": "Primeira data", "weekday_order_number": "Ordem do dia da semana no mês (por exemplo, primeira quarta-feira do mês)", @@ -44,7 +44,7 @@ "time": "Formato de hora inválido!", "weekday_order_number": "Selecione 1 ou mais dias", "week_order_number": "Selecione 1 ou mais semanas", - "period": "O período deve ser um número entre 1 e 365", + "period": "O período deve ser um número entre 1 e 1000", "first_week": "A primeira semana deve ser um número entre 1 e 52", "date": "Formato de data inválido!" }, @@ -77,7 +77,7 @@ "collection_days": "Dias de coleta", "first_month": "Primeiro mês de coleta", "last_month": "Último mês de coleta", - "period": "Coleta a cada n semanas/dias: (1-365)", + "period": "Coleta a cada n semanas/dias: (1-1000)", "first_week": "Primeira semana de coleta (1-52)", "first_date": "Primeira data", "weekday_order_number": "Ordem do dia da semana no mês (por exemplo, primeira quarta-feira do mês)", @@ -96,7 +96,7 @@ "time": "Formato de hora inválido!", "weekday_order_number": "Selecione 1 ou mais dias", "week_order_number": "Selecione 1 ou mais semanas", - "period": "O período deve ser um número entre 1 e 365", + "period": "O período deve ser um número entre 1 e 1000", "first_week": "A primeira semana deve ser um número entre 1 e 52", "date": "Formato de data inválido!" } diff --git a/custom_components/garbage_collection/translations/sensor.dk.json b/custom_components/garbage_collection/translations/sensor.da.json similarity index 53% rename from custom_components/garbage_collection/translations/sensor.dk.json rename to custom_components/garbage_collection/translations/sensor.da.json index 4dffc6c6..2141243a 100644 --- a/custom_components/garbage_collection/translations/sensor.dk.json +++ b/custom_components/garbage_collection/translations/sensor.da.json @@ -1,8 +1,8 @@ { "state": { "garbage_collection__schedule": { - "today": "i dag", - "tomorrow": "i morgen" + "today": "I dag", + "tomorrow": "I morgen" } } } \ No newline at end of file diff --git a/custom_components/garbage_collection/translations/sk.json b/custom_components/garbage_collection/translations/sk.json index c698d26b..c4a03029 100644 --- a/custom_components/garbage_collection/translations/sk.json +++ b/custom_components/garbage_collection/translations/sk.json @@ -48,7 +48,7 @@ "data": { "first_month": "Prvý mesiac zvozu", "last_month": "Posledný mesiac zvozu", - "period": "Perióda (zvoz každých n týždňov/dní): (1-365)", + "period": "Perióda (zvoz každých n týždňov/dní): (1-1000)", "first_week": "Prvý týždeň zvozu (1-52)", "first_date": "Prvý dátum", "weekday_order_number_1": "Prvý deň v mesiaci", @@ -81,7 +81,7 @@ "time": "Zlý formát času!", "weekday_order_number": "Vyber jeden nebo viacej dní!", "week_order_number": "Vyber jeden nebo viacej týždňov!", - "period": "Perióda musí být číslo medzi 1 a 365", + "period": "Perióda musí být číslo medzi 1 a 1000", "first_week": "Prvý týždeň musí být číslo medzi 1 a 52", "date": "Zlý formát dátumu!" }, @@ -138,7 +138,7 @@ "data": { "first_month": "Prvý mesiac zvozu", "last_month": "Posledný mesiac zvozu", - "period": "Perióda (zvoz každých n týždňov/dní): (1-365)", + "period": "Perióda (zvoz každých n týždňov/dní): (1-1000)", "first_week": "Prvý týždeň zvozu (1-52)", "first_date": "Prvý dátum", "weekday_order_number_1": "Prvý deň v mesiaci", @@ -170,7 +170,7 @@ "time": "Zlý formát času!", "weekday_order_number": "Vyber jeden nebo viacej dní!", "week_order_number": "Vyber jeden nebo viacej týždňov!", - "period": "Perióda musí být číslo medzi 1 a 365", + "period": "Perióda musí být číslo medzi 1 a 1000", "first_week": "Prvý týždeň musí být číslo medzi 1 a 52", "date": "Zlý formát dátumu!" } diff --git a/custom_components/garbage_collection/translations/sl.json b/custom_components/garbage_collection/translations/sl.json index 44c63387..592e71d1 100644 --- a/custom_components/garbage_collection/translations/sl.json +++ b/custom_components/garbage_collection/translations/sl.json @@ -49,7 +49,7 @@ "data": { "first_month": "Prvi mesec odvoza", "last_month": "Zadnji mesec odvoza", - "period": "Odvoz vsakih v tednov/dni: (1-365)", + "period": "Odvoz vsakih v tednov/dni: (1-1000)", "first_week": "Prvi teden odvoza (1-52)", "first_date": "Prvi datum", "weekday_order_number_1": "1. dan tedna v mesecu", @@ -81,7 +81,7 @@ "time": "Neveljaven format časa!", "weekday_order_number": "Izberite enega ali več dni", "week_order_number": "Izberite enega ali več tednov", - "period": "Interval mora biti številka med 1 in 365", + "period": "Interval mora biti številka med 1 in 1000", "first_week": "Prvi teden mora biti številka med 1 in 52", "date": "Neveljaven format datuma!" }, @@ -138,7 +138,7 @@ "data": { "first_month": "Prvi mesec odvoza", "last_month": "Zdnji mesec odvoza", - "period": "Odvoz vsakih n tednov/dni: (1-365)", + "period": "Odvoz vsakih n tednov/dni: (1-1000)", "first_week": "Prvi teden odvoza (1-52)", "first_date": "Prvi datum", "weekday_order_number_1": "1. dan tedna v mesecu", @@ -170,7 +170,7 @@ "time": "Neveljaven format časa!", "weekday_order_number": "Izberite enega ali več dni", "week_order_number": "Izberite enega ali več tednov", - "period": "Interval mora biti številka med 1 in 365", + "period": "Interval mora biti številka med 1 in 1000", "first_week": "Prvi teden mora biti številka med 1 in 52", "date": "Neveljaven format datuma!" } diff --git a/custom_components/grocy/__init__.py b/custom_components/grocy/__init__.py index 7790f816..2f16a702 100644 --- a/custom_components/grocy/__init__.py +++ b/custom_components/grocy/__init__.py @@ -4,17 +4,17 @@ For more details about this integration, please refer to https://github.com/custom-components/grocy """ -import asyncio import logging from datetime import timedelta -from typing import List +from typing import Any, List from homeassistant.config_entries import ConfigEntry from homeassistant.core import Config, HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from pygrocy import Grocy +from .helpers import extract_base_url_and_path + from .const import ( CONF_API_KEY, CONF_PORT, @@ -33,11 +33,6 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup(_hass: HomeAssistant, _config: Config): - """Set up this integration using YAML is not supported.""" - return True - - async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Set up this integration using UI.""" hass.data.setdefault(DOMAIN, {}) @@ -51,10 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): config_entry.data[CONF_VERIFY_SSL], ) - await coordinator.async_refresh() - - if not coordinator.last_update_success: - raise ConfigEntryNotReady + await coordinator.async_config_entry_first_refresh() hass.data[DOMAIN] = coordinator @@ -68,7 +60,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): # Setup http endpoint for proxying images from grocy await async_setup_image_api(hass, config_entry.data) - config_entry.add_update_listener(async_reload_entry) return True @@ -78,7 +69,10 @@ class GrocyDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass, url, api_key, port_number, verify_ssl): """Initialize.""" super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) - self.api = Grocy(url, api_key, port_number, verify_ssl) + (base_url, path) = extract_base_url_and_path(url) + self.api = Grocy( + base_url, api_key, path=path, port=port_number, verify_ssl=verify_ssl + ) self.entities = [] self.data = {} @@ -86,82 +80,75 @@ async def _async_update_data(self): """Update data via library.""" grocy_data = GrocyData(self.hass, self.api) data = {} - features = [] - try: - features = await async_supported_features(grocy_data) - if not features: - raise UpdateFailed("No features enabled") - except Exception as exception: - raise UpdateFailed(exception) + features = await async_supported_features(grocy_data) + if not features: + raise UpdateFailed("No features enabled") for entity in self.entities: - if entity.enabled and entity.entity_type in features: - try: - data[entity.entity_type] = await grocy_data.async_update_data( - entity.entity_type - ) - except Exception as exception: - _LOGGER.error( - f"Update of {entity.entity_type} failed with {exception}" - ) - elif entity.entity_type not in features: - _LOGGER.warning( - f"You have enabled the entity for {entity.name}, but this feature is not enabled in Grocy", + if not entity.enabled: + continue + if not entity.entity_type in features: + _LOGGER.debug( + "You have enabled the entity for '%s', but this feature is not enabled in Grocy", + entity.name, + ) + continue + + try: + data[entity.entity_type] = await grocy_data.async_update_data( + entity.entity_type + ) + except Exception as exception: # pylint: disable=broad-except + _LOGGER.error( + "Update of %s failed with %s", + entity.entity_type, + exception, ) return data -async def async_supported_features(grocy_data) -> List[str]: +async def async_supported_features(grocy_data: GrocyData) -> List[str]: """Return a list of supported features.""" features = [] config = await grocy_data.async_get_config() if config: - if config["FEATURE_FLAG_STOCK"] != "0": + if is_enabled_grocy_feature(config, "FEATURE_FLAG_STOCK"): features.append(GrocyEntityType.STOCK) features.append(GrocyEntityType.PRODUCTS) features.append(GrocyEntityType.MISSING_PRODUCTS) features.append(GrocyEntityType.EXPIRED_PRODUCTS) features.append(GrocyEntityType.EXPIRING_PRODUCTS) - if config["FEATURE_FLAG_SHOPPINGLIST"] != "0": + if is_enabled_grocy_feature(config, "FEATURE_FLAG_SHOPPINGLIST"): features.append(GrocyEntityType.SHOPPING_LIST) - if config["FEATURE_FLAG_TASKS"] != "0": + if is_enabled_grocy_feature(config, "FEATURE_FLAG_TASKS"): features.append(GrocyEntityType.TASKS) features.append(GrocyEntityType.OVERDUE_TASKS) - if config["FEATURE_FLAG_CHORES"] != "0": + if is_enabled_grocy_feature(config, "FEATURE_FLAG_CHORES"): features.append(GrocyEntityType.CHORES) features.append(GrocyEntityType.OVERDUE_CHORES) - if config["FEATURE_FLAG_RECIPES"] != "0": + if is_enabled_grocy_feature(config, "FEATURE_FLAG_RECIPES"): features.append(GrocyEntityType.MEAL_PLAN) return features -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): - """Handle removal of an entry.""" - _LOGGER.debug("Unloading with state %s", entry.state) - if entry.state == "loaded": - unloaded = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in PLATFORMS - ] - ) - ) - _LOGGER.debug("Unloaded? %s", unloaded) - del hass.data[DOMAIN] - return unloaded - return False +def is_enabled_grocy_feature(grocy_config: Any, feature_setting_key: str) -> bool: + """ + Return whether the Grocy feature is enabled or not, default is enabled. + Setting value received from Grocy can be a str or bool. + """ + feature_setting_value = grocy_config[feature_setting_key] + return feature_setting_value not in (False, "0") + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + await async_unload_services(hass) + if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + del hass.data[DOMAIN] -async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry): - """Reload config entry.""" - unloaded = await async_unload_entry(hass, entry) - _LOGGER.error("Unloaded successfully: %s", unloaded) - if unloaded: - await async_setup_entry(hass, entry) - await async_unload_services(hass) + return unloaded diff --git a/custom_components/grocy/config_flow.py b/custom_components/grocy/config_flow.py index 36d31839..f0ad7994 100644 --- a/custom_components/grocy/config_flow.py +++ b/custom_components/grocy/config_flow.py @@ -6,6 +6,8 @@ from homeassistant import config_entries from pygrocy import Grocy +from .helpers import extract_base_url_and_path + from .const import ( CONF_API_KEY, CONF_PORT, # pylint: disable=unused-import @@ -59,25 +61,35 @@ async def _show_config_form(self, user_input): # pylint: disable=unused-argumen """Show the configuration form to edit the data.""" data_schema = OrderedDict() data_schema[vol.Required(CONF_URL, default="")] = str - data_schema[vol.Required(CONF_API_KEY, default="",)] = str + data_schema[ + vol.Required( + CONF_API_KEY, + default="", + ) + ] = str data_schema[vol.Optional(CONF_PORT, default=DEFAULT_PORT)] = int data_schema[vol.Optional(CONF_VERIFY_SSL, default=False)] = bool _LOGGER.debug("config form") return self.async_show_form( - step_id="user", data_schema=vol.Schema(data_schema), errors=self._errors, + step_id="user", + data_schema=vol.Schema(data_schema), + errors=self._errors, ) async def _test_credentials(self, url, api_key, port, verify_ssl): """Return true if credentials is valid.""" try: - client = Grocy(url, api_key, port, verify_ssl) + (base_url, path) = extract_base_url_and_path(url) + client = Grocy( + base_url, api_key, port=port, path=path, verify_ssl=verify_ssl + ) _LOGGER.debug("Testing credentials") def system_info(): """Get system information from Grocy.""" - client._api_client._do_get_request("/api/system/info") + client._api_client._do_get_request("system/info") await self.hass.async_add_executor_job(system_info) return True diff --git a/custom_components/grocy/const.py b/custom_components/grocy/const.py index 4abdf53c..da482250 100644 --- a/custom_components/grocy/const.py +++ b/custom_components/grocy/const.py @@ -4,7 +4,7 @@ # Base component constants NAME = "Grocy" DOMAIN = "grocy" -VERSION = "v4.2.2b" +VERSION = "4.3.4" ISSUE_URL = "https://github.com/custom-components/grocy/issues" @@ -73,4 +73,3 @@ class GrocyEntityIcon(str, Enum): SHOPPING_LIST = "mdi:cart-outline" STOCK = "mdi:fridge-outline" TASKS = "mdi:checkbox-marked-circle-outline" - diff --git a/custom_components/grocy/entity.py b/custom_components/grocy/entity.py index adc15075..f5c0fe0a 100644 --- a/custom_components/grocy/entity.py +++ b/custom_components/grocy/entity.py @@ -1,9 +1,8 @@ """GrocyEntity class""" import json -from homeassistant.const import MAJOR_VERSION, MINOR_VERSION -from homeassistant.helpers import entity -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.update_coordinator import CoordinatorEntity # pylint: disable=relative-beyond-top-level from .const import ( @@ -17,50 +16,11 @@ from .json_encode import GrocyJSONEncoder -class GrocyCoordinatorEntity(entity.Entity): - """ - CoordinatorEntity was added to HA in 0.115, this is a copy of the - class CoordinatorEntity from homeassistant.helpers.update_coordinator +class GrocyEntity(CoordinatorEntity): + """Base class for Grocy entities.""" - Remove this class and use CoordinatorEntity instead when grocy require min version 0.115 - """ - - def __init__(self, coordinator: DataUpdateCoordinator) -> None: - """Create the entity with a DataUpdateCoordinator.""" - self.coordinator = coordinator - - @property - def should_poll(self) -> bool: - """No need to poll. Coordinator notifies entity of updates.""" - return False - - @property - def available(self) -> bool: - """Return if entity is available.""" - return self.coordinator.last_update_success - - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - await super().async_added_to_hass() - self.async_on_remove( - self.coordinator.async_add_listener(self.async_write_ha_state) - ) - - async def async_update(self) -> None: - """Update the entity. - - Only used by the generic entity update service. - """ - - # Ignore manual update requests if the entity is disabled - if not self.enabled: - return - - await self.coordinator.async_request_refresh() - - -class GrocyEntity(GrocyCoordinatorEntity): def __init__(self, coordinator, config_entry, entity_type): + """Initialize generic Grocy entity.""" super().__init__(coordinator) self.config_entry = config_entry self.entity_type = entity_type @@ -103,22 +63,13 @@ def icon(self): @property def device_info(self): - info = { - # "identifiers": {(DOMAIN, self.unique_id)}, + return { "identifiers": {(DOMAIN, self.config_entry.entry_id)}, "name": NAME, "model": VERSION, "manufacturer": NAME, + "entry_type": DeviceEntryType.SERVICE, } - # LEGACY can be removed when min HA version is 2021.12 - if (MAJOR_VERSION > 2021) or (MAJOR_VERSION == 2021 and MINOR_VERSION >= 12): - # pylint: disable=import-outside-toplevel - from homeassistant.helpers.device_registry import DeviceEntryType - - info["entry_type"] = DeviceEntryType.SERVICE - else: - info["entry_type"] = "service" - return info @property def extra_state_attributes(self): diff --git a/custom_components/grocy/grocy_data.py b/custom_components/grocy/grocy_data.py index 7dc7e38c..de274a65 100644 --- a/custom_components/grocy/grocy_data.py +++ b/custom_components/grocy/grocy_data.py @@ -1,7 +1,6 @@ from aiohttp import hdrs, web -from datetime import timedelta, datetime +from datetime import datetime import logging -import pytz from homeassistant.components.http import HomeAssistantView from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -12,13 +11,10 @@ CONF_PORT, GrocyEntityType, ) -from .helpers import MealPlanItem +from .helpers import MealPlanItem, extract_base_url_and_path -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) _LOGGER = logging.getLogger(__name__) -utc = pytz.UTC - class GrocyData: """This class handle communication and stores the data.""" @@ -87,8 +83,8 @@ def wrapper(): overdue_chores = [] for chore in chores: if chore.next_estimated_execution_time: - now = datetime.now().replace(tzinfo=utc) - due = chore.next_estimated_execution_time.replace(tzinfo=utc) + now = datetime.now() + due = chore.next_estimated_execution_time if due < now: overdue_chores.append(chore) return overdue_chores @@ -97,7 +93,7 @@ async def async_get_config(self): """Get the configuration from Grocy.""" def wrapper(): - return self.client._api_client._do_get_request("/api/system/config") + return self.client._api_client._do_get_request("system/config") return await self.hass.async_add_executor_job(wrapper) @@ -132,7 +128,7 @@ async def async_update_expiring_products(self): """Update data.""" # This is where the main logic to update platform data goes. def wrapper(): - return self.client.expiring_products(True) + return self.client.due_products(True) return await self.hass.async_add_executor_job(wrapper) @@ -165,13 +161,20 @@ def wrapper(): async def async_setup_image_api(hass, config): + """Setup and register the image api for grocy images with HA.""" session = async_get_clientsession(hass) url = config.get(CONF_URL) + (grocy_base_url, grocy_path) = extract_base_url_and_path(url) api_key = config.get(CONF_API_KEY) port_number = config.get(CONF_PORT) - base_url = f"{url}:{port_number}" - hass.http.register_view(GrocyPictureView(session, base_url, api_key)) + if grocy_path: + grocy_full_url = f"{grocy_base_url}:{port_number}/{grocy_path}" + else: + grocy_full_url = f"{grocy_base_url}:{port_number}" + + _LOGGER.debug("Generated image api url to grocy: '%s'", grocy_full_url) + hass.http.register_view(GrocyPictureView(session, grocy_full_url, api_key)) class GrocyPictureView(HomeAssistantView): diff --git a/custom_components/grocy/helpers.py b/custom_components/grocy/helpers.py index 168e21e5..100c98e9 100644 --- a/custom_components/grocy/helpers.py +++ b/custom_components/grocy/helpers.py @@ -1,4 +1,12 @@ import base64 +from urllib.parse import urlparse +from typing import Tuple + + +def extract_base_url_and_path(url: str) -> Tuple[str, str]: + """Extract the base url and path from a given URL.""" + parsed_url = urlparse(url) + return (f"{parsed_url.scheme}://{parsed_url.netloc}", parsed_url.path.strip("/")) class MealPlanItem(object): diff --git a/custom_components/grocy/manifest.json b/custom_components/grocy/manifest.json index 9b0a6785..03a6048c 100644 --- a/custom_components/grocy/manifest.json +++ b/custom_components/grocy/manifest.json @@ -12,10 +12,8 @@ "@isabellaalstrom" ], "requirements": [ - "pygrocy==1.0.0", - "iso8601==0.1.12", - "integrationhelper==0.2.2" + "pygrocy==1.3.0" ], - "version": "v3.0.1", + "version": "4.3.4", "iot_class": "local_polling" } diff --git a/custom_components/grocy/services.py b/custom_components/grocy/services.py index 946b9bad..de18f0de 100644 --- a/custom_components/grocy/services.py +++ b/custom_components/grocy/services.py @@ -1,23 +1,22 @@ """Grocy services.""" +from __future__ import annotations + import asyncio import voluptuous as vol -import iso8601 -from homeassistant.helpers import config_validation as cv +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import entity_component -from pygrocy import TransactionType -from pygrocy import EntityType -from datetime import datetime +from pygrocy import TransactionType, EntityType # pylint: disable=relative-beyond-top-level from .const import DOMAIN -GROCY_SERVICES = "grocy_services" - SERVICE_PRODUCT_ID = "product_id" SERVICE_AMOUNT = "amount" SERVICE_PRICE = "price" SERVICE_SPOILED = "spoiled" +SERVICE_SUBPRODUCT_SUBSTITUTION = "allow_subproduct_substitution" SERVICE_TRANSACTION_TYPE = "transaction_type" SERVICE_CHORE_ID = "chore_id" SERVICE_DONE_BY = "done_by" @@ -47,6 +46,7 @@ vol.Required(SERVICE_PRODUCT_ID): vol.Coerce(int), vol.Required(SERVICE_AMOUNT): vol.Coerce(float), vol.Optional(SERVICE_SPOILED): bool, + vol.Optional(SERVICE_SUBPRODUCT_SUBSTITUTION): bool, vol.Optional(SERVICE_TRANSACTION_TYPE): str, } ) @@ -78,16 +78,22 @@ ) ) +SERVICES_WITH_ACCOMPANYING_SCHEMA: list[tuple[str, vol.Schema]] = [ + (SERVICE_ADD_PRODUCT, SERVICE_ADD_PRODUCT_SCHEMA), + (SERVICE_CONSUME_PRODUCT, SERVICE_CONSUME_PRODUCT_SCHEMA), + (SERVICE_EXECUTE_CHORE, SERVICE_EXECUTE_CHORE_SCHEMA), + (SERVICE_COMPLETE_TASK, SERVICE_COMPLETE_TASK_SCHEMA), + (SERVICE_ADD_GENERIC, SERVICE_ADD_GENERIC_SCHEMA), +] -async def async_setup_services(hass, entry): + +async def async_setup_services(hass: HomeAssistant, entry: ConfigEntry) -> None: """Set up services for Grocy integration.""" coordinator = hass.data[DOMAIN] - if hass.data.get(GROCY_SERVICES, False): + if hass.services.async_services().get(DOMAIN): return - hass.data[GROCY_SERVICES] = True - - async def async_call_grocy_service(service_call): + async def async_call_grocy_service(service_call: ServiceCall) -> None: """Call correct Grocy service.""" service = service_call.service service_data = service_call.data @@ -107,53 +113,17 @@ async def async_call_grocy_service(service_call): elif service == SERVICE_ADD_GENERIC: await async_add_generic_service(hass, coordinator, service_data) - hass.services.async_register( - DOMAIN, - SERVICE_ADD_PRODUCT, - async_call_grocy_service, - schema=SERVICE_ADD_PRODUCT_SCHEMA, - ) - - hass.services.async_register( - DOMAIN, - SERVICE_CONSUME_PRODUCT, - async_call_grocy_service, - schema=SERVICE_CONSUME_PRODUCT_SCHEMA, - ) - - hass.services.async_register( - DOMAIN, - SERVICE_EXECUTE_CHORE, - async_call_grocy_service, - schema=SERVICE_EXECUTE_CHORE_SCHEMA, - ) + for service, schema in SERVICES_WITH_ACCOMPANYING_SCHEMA: + hass.services.async_register(DOMAIN, service, async_call_grocy_service, schema) - hass.services.async_register( - DOMAIN, - SERVICE_COMPLETE_TASK, - async_call_grocy_service, - schema=SERVICE_COMPLETE_TASK_SCHEMA, - ) - hass.services.async_register( - DOMAIN, - SERVICE_ADD_GENERIC, - async_call_grocy_service, - schema=SERVICE_ADD_GENERIC_SCHEMA, - ) - - -async def async_unload_services(hass): +async def async_unload_services(hass: HomeAssistant) -> None: """Unload Grocy services.""" - if not hass.data.get(GROCY_SERVICES): + if not hass.services.async_services().get(DOMAIN): return - hass.data[GROCY_SERVICES] = False - - hass.services.async_remove(DOMAIN, SERVICE_ADD_PRODUCT) - hass.services.async_remove(DOMAIN, SERVICE_CONSUME_PRODUCT) - hass.services.async_remove(DOMAIN, SERVICE_EXECUTE_CHORE) - hass.services.async_remove(DOMAIN, SERVICE_COMPLETE_TASK) + for service, _ in SERVICES_WITH_ACCOMPANYING_SCHEMA: + hass.services.async_remove(DOMAIN, service) async def async_add_product_service(hass, coordinator, data): @@ -173,6 +143,7 @@ async def async_consume_product_service(hass, coordinator, data): product_id = data[SERVICE_PRODUCT_ID] amount = data[SERVICE_AMOUNT] spoiled = data.get(SERVICE_SPOILED, False) + allow_subproduct_substitution = data.get(SERVICE_SUBPRODUCT_SUBSTITUTION, False) transaction_type_raw = data.get(SERVICE_TRANSACTION_TYPE, None) transaction_type = TransactionType.CONSUME @@ -182,7 +153,11 @@ async def async_consume_product_service(hass, coordinator, data): def wrapper(): coordinator.api.consume_product( - product_id, amount, spoiled=spoiled, transaction_type=transaction_type + product_id, + amount, + spoiled=spoiled, + transaction_type=transaction_type, + allow_subproduct_substitution=allow_subproduct_substitution, ) await hass.async_add_executor_job(wrapper) diff --git a/custom_components/grocy/services.yaml b/custom_components/grocy/services.yaml index 9bfd8dbb..72c44f90 100644 --- a/custom_components/grocy/services.yaml +++ b/custom_components/grocy/services.yaml @@ -54,6 +54,13 @@ consume_product_from_stock: default: false selector: boolean: + allow_subproduct_substitution: + name: Subproduct substitution + description: If subproduct substitution is allowed + example: false + default: false + selector: + boolean: transaction_type: name: Transaction Type description: The type of the transaction. diff --git a/custom_components/hacs/__init__.py b/custom_components/hacs/__init__.py index c5f625e0..770a56ff 100644 --- a/custom_components/hacs/__init__.py +++ b/custom_components/hacs/__init__.py @@ -23,13 +23,13 @@ from homeassistant.loader import async_get_integration import voluptuous as vol -from custom_components.hacs.frontend import async_register_frontend - from .base import HacsBase from .const import DOMAIN, MINIMUM_HA_VERSION, STARTUP from .enums import ConfigurationType, HacsDisabledReason, HacsStage, LovelaceMode +from .frontend import async_register_frontend from .utils.configuration_schema import hacs_config_combined from .utils.data import HacsData +from .utils.platform_setup import async_setup_entity_platforms from .utils.queue_manager import QueueManager from .utils.version import version_left_higher_or_equal_then_right from .websocket import async_register_websocket_commands @@ -169,14 +169,14 @@ async def async_startup(): hacs.log.info("Update entities are only supported when using UI configuration") else: - if hacs.configuration.experimental: - hass.config_entries.async_setup_platforms( - hacs.configuration.config_entry, [Platform.SENSOR, Platform.UPDATE] - ) - else: - hass.config_entries.async_setup_platforms( - hacs.configuration.config_entry, [Platform.SENSOR] - ) + await async_setup_entity_platforms( + hacs, + hass, + config_entry, + [Platform.SENSOR, Platform.UPDATE] + if hacs.configuration.experimental + else [Platform.SENSOR], + ) hacs.set_stage(HacsStage.SETUP) if hacs.system.disabled: diff --git a/custom_components/hacs/base.py b/custom_components/hacs/base.py index 7c32e35a..ebb0356b 100644 --- a/custom_components/hacs/base.py +++ b/custom_components/hacs/base.py @@ -5,7 +5,6 @@ from dataclasses import asdict, dataclass, field from datetime import timedelta import gzip -import json import logging import math import os @@ -53,7 +52,9 @@ ) from .repositories import RERPOSITORY_CLASSES from .utils.decode import decode_content -from .utils.logger import get_hacs_logger +from .utils.json import json_loads +from .utils.logger import LOGGER +from .utils.platform_setup import async_setup_entity_platforms from .utils.queue_manager import QueueManager from .utils.store import async_load_from_store, async_save_to_store @@ -163,8 +164,6 @@ class HacsStatus: startup: bool = True new: bool = False - reloading_data: bool = False - upgrading_all: bool = False @dataclass @@ -347,7 +346,7 @@ class HacsBase: githubapi: GitHubAPI | None = None hass: HomeAssistant | None = None integration: Integration | None = None - log: logging.Logger = get_hacs_logger() + log: logging.Logger = LOGGER queue: QueueManager | None = None recuring_tasks = [] repositories: HacsRepositories = HacsRepositories() @@ -473,7 +472,7 @@ async def async_github_get_hacs_default_file(self, filename: str) -> list: if response is None: return [] - return json.loads(decode_content(response.data.content)) + return json_loads(decode_content(response.data.content)) async def async_github_api_method( self, @@ -732,7 +731,9 @@ async def async_recreate_entities(self) -> None: platforms=platforms, ) - self.hass.config_entries.async_setup_platforms(self.configuration.config_entry, platforms) + await async_setup_entity_platforms( + self, self.hass, self.configuration.config_entry, platforms + ) @callback def async_dispatch(self, signal: HacsDispatchEvent, data: dict | None = None) -> None: diff --git a/custom_components/hacs/config_flow.py b/custom_components/hacs/config_flow.py index 3529af3b..1227e2ab 100644 --- a/custom_components/hacs/config_flow.py +++ b/custom_components/hacs/config_flow.py @@ -14,7 +14,7 @@ from .const import CLIENT_ID, DOMAIN, MINIMUM_HA_VERSION from .enums import ConfigurationType from .utils.configuration_schema import RELEASE_LIMIT, hacs_config_option_schema -from .utils.logger import get_hacs_logger +from .utils.logger import LOGGER class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -28,7 +28,7 @@ def __init__(self): self._errors = {} self.device = None self.activation = None - self.log = get_hacs_logger() + self.log = LOGGER self._progress_task = None self._login_device = None self._reauth = False diff --git a/custom_components/hacs/hacs_frontend/c.01f18260.js b/custom_components/hacs/hacs_frontend/c.01f18260.js new file mode 100644 index 00000000..a583ec91 --- /dev/null +++ b/custom_components/hacs/hacs_frontend/c.01f18260.js @@ -0,0 +1,147 @@ +import{u as e,v as t,G as i,M as c,_ as o,i as r,e as n,t as a,B as d,$ as s,o as p,I as l,y as m,p as h,q as u,r as f,n as b,a as g,h as k,J as x,K as _,g as y,w as v,R as T,j as w,A as E}from"./main-7bc9a818.js";import{c as O,o as I}from"./c.5d9598b2.js";import{o as C}from"./c.8e28b461.js";var A,R,S={ANCHOR:"mdc-menu-surface--anchor",ANIMATING_CLOSED:"mdc-menu-surface--animating-closed",ANIMATING_OPEN:"mdc-menu-surface--animating-open",FIXED:"mdc-menu-surface--fixed",IS_OPEN_BELOW:"mdc-menu-surface--is-open-below",OPEN:"mdc-menu-surface--open",ROOT:"mdc-menu-surface"},F={CLOSED_EVENT:"MDCMenuSurface:closed",CLOSING_EVENT:"MDCMenuSurface:closing",OPENED_EVENT:"MDCMenuSurface:opened",FOCUSABLE_ELEMENTS:["button:not(:disabled)",'[href]:not([aria-disabled="true"])',"input:not(:disabled)","select:not(:disabled)","textarea:not(:disabled)",'[tabindex]:not([tabindex="-1"]):not([aria-disabled="true"])'].join(", ")},B={TRANSITION_OPEN_DURATION:120,TRANSITION_CLOSE_DURATION:75,MARGIN_TO_EDGE:32,ANCHOR_TO_MENU_SURFACE_WIDTH_RATIO:.67,TOUCH_EVENT_WAIT_MS:30};!function(e){e[e.BOTTOM=1]="BOTTOM",e[e.CENTER=2]="CENTER",e[e.RIGHT=4]="RIGHT",e[e.FLIP_RTL=8]="FLIP_RTL"}(A||(A={})),function(e){e[e.TOP_LEFT=0]="TOP_LEFT",e[e.TOP_RIGHT=4]="TOP_RIGHT",e[e.BOTTOM_LEFT=1]="BOTTOM_LEFT",e[e.BOTTOM_RIGHT=5]="BOTTOM_RIGHT",e[e.TOP_START=8]="TOP_START",e[e.TOP_END=12]="TOP_END",e[e.BOTTOM_START=9]="BOTTOM_START",e[e.BOTTOM_END=13]="BOTTOM_END"}(R||(R={}));var M=function(c){function o(e){var i=c.call(this,t(t({},o.defaultAdapter),e))||this;return i.isSurfaceOpen=!1,i.isQuickOpen=!1,i.isHoistedElement=!1,i.isFixedPosition=!1,i.isHorizontallyCenteredOnViewport=!1,i.maxHeight=0,i.openBottomBias=0,i.openAnimationEndTimerId=0,i.closeAnimationEndTimerId=0,i.animationRequestId=0,i.anchorCorner=R.TOP_START,i.originCorner=R.TOP_START,i.anchorMargin={top:0,right:0,bottom:0,left:0},i.position={x:0,y:0},i}return e(o,c),Object.defineProperty(o,"cssClasses",{get:function(){return S},enumerable:!1,configurable:!0}),Object.defineProperty(o,"strings",{get:function(){return F},enumerable:!1,configurable:!0}),Object.defineProperty(o,"numbers",{get:function(){return B},enumerable:!1,configurable:!0}),Object.defineProperty(o,"Corner",{get:function(){return R},enumerable:!1,configurable:!0}),Object.defineProperty(o,"defaultAdapter",{get:function(){return{addClass:function(){},removeClass:function(){},hasClass:function(){return!1},hasAnchor:function(){return!1},isElementInContainer:function(){return!1},isFocused:function(){return!1},isRtl:function(){return!1},getInnerDimensions:function(){return{height:0,width:0}},getAnchorDimensions:function(){return null},getWindowDimensions:function(){return{height:0,width:0}},getBodyDimensions:function(){return{height:0,width:0}},getWindowScroll:function(){return{x:0,y:0}},setPosition:function(){},setMaxHeight:function(){},setTransformOrigin:function(){},saveFocus:function(){},restoreFocus:function(){},notifyClose:function(){},notifyOpen:function(){},notifyClosing:function(){}}},enumerable:!1,configurable:!0}),o.prototype.init=function(){var e=o.cssClasses,t=e.ROOT,i=e.OPEN;if(!this.adapter.hasClass(t))throw new Error(t+" class required in root element.");this.adapter.hasClass(i)&&(this.isSurfaceOpen=!0)},o.prototype.destroy=function(){clearTimeout(this.openAnimationEndTimerId),clearTimeout(this.closeAnimationEndTimerId),cancelAnimationFrame(this.animationRequestId)},o.prototype.setAnchorCorner=function(e){this.anchorCorner=e},o.prototype.flipCornerHorizontally=function(){this.originCorner=this.originCorner^A.RIGHT},o.prototype.setAnchorMargin=function(e){this.anchorMargin.top=e.top||0,this.anchorMargin.right=e.right||0,this.anchorMargin.bottom=e.bottom||0,this.anchorMargin.left=e.left||0},o.prototype.setIsHoisted=function(e){this.isHoistedElement=e},o.prototype.setFixedPosition=function(e){this.isFixedPosition=e},o.prototype.isFixed=function(){return this.isFixedPosition},o.prototype.setAbsolutePosition=function(e,t){this.position.x=this.isFinite(e)?e:0,this.position.y=this.isFinite(t)?t:0},o.prototype.setIsHorizontallyCenteredOnViewport=function(e){this.isHorizontallyCenteredOnViewport=e},o.prototype.setQuickOpen=function(e){this.isQuickOpen=e},o.prototype.setMaxHeight=function(e){this.maxHeight=e},o.prototype.setOpenBottomBias=function(e){this.openBottomBias=e},o.prototype.isOpen=function(){return this.isSurfaceOpen},o.prototype.open=function(){var e=this;this.isSurfaceOpen||(this.adapter.saveFocus(),this.isQuickOpen?(this.isSurfaceOpen=!0,this.adapter.addClass(o.cssClasses.OPEN),this.dimensions=this.adapter.getInnerDimensions(),this.autoposition(),this.adapter.notifyOpen()):(this.adapter.addClass(o.cssClasses.ANIMATING_OPEN),this.animationRequestId=requestAnimationFrame((function(){e.dimensions=e.adapter.getInnerDimensions(),e.autoposition(),e.adapter.addClass(o.cssClasses.OPEN),e.openAnimationEndTimerId=setTimeout((function(){e.openAnimationEndTimerId=0,e.adapter.removeClass(o.cssClasses.ANIMATING_OPEN),e.adapter.notifyOpen()}),B.TRANSITION_OPEN_DURATION)})),this.isSurfaceOpen=!0))},o.prototype.close=function(e){var t=this;if(void 0===e&&(e=!1),this.isSurfaceOpen){if(this.adapter.notifyClosing(),this.isQuickOpen)return this.isSurfaceOpen=!1,e||this.maybeRestoreFocus(),this.adapter.removeClass(o.cssClasses.OPEN),this.adapter.removeClass(o.cssClasses.IS_OPEN_BELOW),void this.adapter.notifyClose();this.adapter.addClass(o.cssClasses.ANIMATING_CLOSED),requestAnimationFrame((function(){t.adapter.removeClass(o.cssClasses.OPEN),t.adapter.removeClass(o.cssClasses.IS_OPEN_BELOW),t.closeAnimationEndTimerId=setTimeout((function(){t.closeAnimationEndTimerId=0,t.adapter.removeClass(o.cssClasses.ANIMATING_CLOSED),t.adapter.notifyClose()}),B.TRANSITION_CLOSE_DURATION)})),this.isSurfaceOpen=!1,e||this.maybeRestoreFocus()}},o.prototype.handleBodyClick=function(e){var t=e.target;this.adapter.isElementInContainer(t)||this.close()},o.prototype.handleKeydown=function(e){var t=e.keyCode;("Escape"===e.key||27===t)&&this.close()},o.prototype.autoposition=function(){var e;this.measurements=this.getAutoLayoutmeasurements();var t=this.getoriginCorner(),i=this.getMenuSurfaceMaxHeight(t),c=this.hasBit(t,A.BOTTOM)?"bottom":"top",r=this.hasBit(t,A.RIGHT)?"right":"left",n=this.getHorizontalOriginOffset(t),a=this.getVerticalOriginOffset(t),d=this.measurements,s=d.anchorSize,p=d.surfaceSize,l=((e={})[r]=n,e[c]=a,e);s.width/p.width>B.ANCHOR_TO_MENU_SURFACE_WIDTH_RATIO&&(r="center"),(this.isHoistedElement||this.isFixedPosition)&&this.adjustPositionForHoistedElement(l),this.adapter.setTransformOrigin(r+" "+c),this.adapter.setPosition(l),this.adapter.setMaxHeight(i?i+"px":""),this.hasBit(t,A.BOTTOM)||this.adapter.addClass(o.cssClasses.IS_OPEN_BELOW)},o.prototype.getAutoLayoutmeasurements=function(){var e=this.adapter.getAnchorDimensions(),t=this.adapter.getBodyDimensions(),i=this.adapter.getWindowDimensions(),c=this.adapter.getWindowScroll();return e||(e={top:this.position.y,right:this.position.x,bottom:this.position.y,left:this.position.x,width:0,height:0}),{anchorSize:e,bodySize:t,surfaceSize:this.dimensions,viewportDistance:{top:e.top,right:i.width-e.right,bottom:i.height-e.bottom,left:e.left},viewportSize:i,windowScroll:c}},o.prototype.getoriginCorner=function(){var e,t,i=this.originCorner,c=this.measurements,r=c.viewportDistance,n=c.anchorSize,a=c.surfaceSize,d=o.numbers.MARGIN_TO_EDGE;this.hasBit(this.anchorCorner,A.BOTTOM)?(e=r.top-d+this.anchorMargin.bottom,t=r.bottom-d-this.anchorMargin.bottom):(e=r.top-d+this.anchorMargin.top,t=r.bottom-d+n.height-this.anchorMargin.top),!(t-a.height>0)&&e>t+this.openBottomBias&&(i=this.setBit(i,A.BOTTOM));var s,p,l=this.adapter.isRtl(),m=this.hasBit(this.anchorCorner,A.FLIP_RTL),h=this.hasBit(this.anchorCorner,A.RIGHT)||this.hasBit(i,A.RIGHT),u=!1;(u=l&&m?!h:h)?(s=r.left+n.width+this.anchorMargin.right,p=r.right-this.anchorMargin.right):(s=r.left+this.anchorMargin.left,p=r.right+n.width-this.anchorMargin.left);var f=s-a.width>0,b=p-a.width>0,g=this.hasBit(i,A.FLIP_RTL)&&this.hasBit(i,A.RIGHT);return b&&g&&l||!f&&g?i=this.unsetBit(i,A.RIGHT):(f&&u&&l||f&&!u&&h||!b&&s>=p)&&(i=this.setBit(i,A.RIGHT)),i},o.prototype.getMenuSurfaceMaxHeight=function(e){if(this.maxHeight>0)return this.maxHeight;var t=this.measurements.viewportDistance,i=0,c=this.hasBit(e,A.BOTTOM),r=this.hasBit(this.anchorCorner,A.BOTTOM),n=o.numbers.MARGIN_TO_EDGE;return c?(i=t.top+this.anchorMargin.top-n,r||(i+=this.measurements.anchorSize.height)):(i=t.bottom-this.anchorMargin.bottom+this.measurements.anchorSize.height-n,r&&(i-=this.measurements.anchorSize.height)),i},o.prototype.getHorizontalOriginOffset=function(e){var t=this.measurements.anchorSize,i=this.hasBit(e,A.RIGHT),c=this.hasBit(this.anchorCorner,A.RIGHT);if(i){var o=c?t.width-this.anchorMargin.left:this.anchorMargin.right;return this.isHoistedElement||this.isFixedPosition?o-(this.measurements.viewportSize.width-this.measurements.bodySize.width):o}return c?t.width-this.anchorMargin.right:this.anchorMargin.left},o.prototype.getVerticalOriginOffset=function(e){var t=this.measurements.anchorSize,i=this.hasBit(e,A.BOTTOM),c=this.hasBit(this.anchorCorner,A.BOTTOM);return i?c?t.height-this.anchorMargin.top:-this.anchorMargin.bottom:c?t.height+this.anchorMargin.bottom:this.anchorMargin.top},o.prototype.adjustPositionForHoistedElement=function(e){var t,c,o=this.measurements,r=o.windowScroll,n=o.viewportDistance,a=o.surfaceSize,d=o.viewportSize,s=Object.keys(e);try{for(var p=i(s),l=p.next();!l.done;l=p.next()){var m=l.value,h=e[m]||0;!this.isHorizontallyCenteredOnViewport||"left"!==m&&"right"!==m?(h+=n[m],this.isFixedPosition||("top"===m?h+=r.y:"bottom"===m?h-=r.y:"left"===m?h+=r.x:h-=r.x),e[m]=h):e[m]=(d.width-a.width)/2}}catch(e){t={error:e}}finally{try{l&&!l.done&&(c=p.return)&&c.call(p)}finally{if(t)throw t.error}}},o.prototype.maybeRestoreFocus=function(){var e=this,t=this.adapter.isFocused(),i=document.activeElement&&this.adapter.isElementInContainer(document.activeElement);(t||i)&&setTimeout((function(){e.adapter.restoreFocus()}),B.TOUCH_EVENT_WAIT_MS)},o.prototype.hasBit=function(e,t){return Boolean(e&t)},o.prototype.setBit=function(e,t){return e|t},o.prototype.unsetBit=function(e,t){return e^t},o.prototype.isFinite=function(e){return"number"==typeof e&&isFinite(e)},o}(c),z=M;const L={TOP_LEFT:R.TOP_LEFT,TOP_RIGHT:R.TOP_RIGHT,BOTTOM_LEFT:R.BOTTOM_LEFT,BOTTOM_RIGHT:R.BOTTOM_RIGHT,TOP_START:R.TOP_START,TOP_END:R.TOP_END,BOTTOM_START:R.BOTTOM_START,BOTTOM_END:R.BOTTOM_END};class N extends d{constructor(){super(...arguments),this.mdcFoundationClass=z,this.absolute=!1,this.fullwidth=!1,this.fixed=!1,this.x=null,this.y=null,this.quick=!1,this.open=!1,this.stayOpenOnBodyClick=!1,this.bitwiseCorner=R.TOP_START,this.previousMenuCorner=null,this.menuCorner="START",this.corner="TOP_START",this.styleTop="",this.styleLeft="",this.styleRight="",this.styleBottom="",this.styleMaxHeight="",this.styleTransformOrigin="",this.anchor=null,this.previouslyFocused=null,this.previousAnchor=null,this.onBodyClickBound=()=>{}}render(){const e={"mdc-menu-surface--fixed":this.fixed,"mdc-menu-surface--fullwidth":this.fullwidth},t={top:this.styleTop,left:this.styleLeft,right:this.styleRight,bottom:this.styleBottom,"max-height":this.styleMaxHeight,"transform-origin":this.styleTransformOrigin};return s` +
+ +
`}createAdapter(){return Object.assign(Object.assign({},m(this.mdcRoot)),{hasAnchor:()=>!!this.anchor,notifyClose:()=>{const e=new CustomEvent("closed",{bubbles:!0,composed:!0});this.open=!1,this.mdcRoot.dispatchEvent(e)},notifyClosing:()=>{const e=new CustomEvent("closing",{bubbles:!0,composed:!0});this.mdcRoot.dispatchEvent(e)},notifyOpen:()=>{const e=new CustomEvent("opened",{bubbles:!0,composed:!0});this.open=!0,this.mdcRoot.dispatchEvent(e)},isElementInContainer:()=>!1,isRtl:()=>!!this.mdcRoot&&"rtl"===getComputedStyle(this.mdcRoot).direction,setTransformOrigin:e=>{this.mdcRoot&&(this.styleTransformOrigin=e)},isFocused:()=>h(this),saveFocus:()=>{const e=u(),t=e.length;t||(this.previouslyFocused=null),this.previouslyFocused=e[t-1]},restoreFocus:()=>{this.previouslyFocused&&"focus"in this.previouslyFocused&&this.previouslyFocused.focus()},getInnerDimensions:()=>{const e=this.mdcRoot;return e?{width:e.offsetWidth,height:e.offsetHeight}:{width:0,height:0}},getAnchorDimensions:()=>{const e=this.anchor;return e?e.getBoundingClientRect():null},getBodyDimensions:()=>({width:document.body.clientWidth,height:document.body.clientHeight}),getWindowDimensions:()=>({width:window.innerWidth,height:window.innerHeight}),getWindowScroll:()=>({x:window.pageXOffset,y:window.pageYOffset}),setPosition:e=>{this.mdcRoot&&(this.styleLeft="left"in e?`${e.left}px`:"",this.styleRight="right"in e?`${e.right}px`:"",this.styleTop="top"in e?`${e.top}px`:"",this.styleBottom="bottom"in e?`${e.bottom}px`:"")},setMaxHeight:async e=>{this.mdcRoot&&(this.styleMaxHeight=e,await this.updateComplete,this.styleMaxHeight=`var(--mdc-menu-max-height, ${e})`)}})}onKeydown(e){this.mdcFoundation&&this.mdcFoundation.handleKeydown(e)}onBodyClick(e){if(this.stayOpenOnBodyClick)return;-1===e.composedPath().indexOf(this)&&this.close()}registerBodyClick(){this.onBodyClickBound=this.onBodyClick.bind(this),document.body.addEventListener("click",this.onBodyClickBound,{passive:!0,capture:!0})}deregisterBodyClick(){document.body.removeEventListener("click",this.onBodyClickBound,{capture:!0})}close(){this.open=!1}show(){this.open=!0}}o([r(".mdc-menu-surface")],N.prototype,"mdcRoot",void 0),o([r("slot")],N.prototype,"slotElement",void 0),o([n({type:Boolean}),C((function(e){this.mdcFoundation&&!this.fixed&&this.mdcFoundation.setIsHoisted(e)}))],N.prototype,"absolute",void 0),o([n({type:Boolean})],N.prototype,"fullwidth",void 0),o([n({type:Boolean}),C((function(e){this.mdcFoundation&&!this.absolute&&this.mdcFoundation.setFixedPosition(e)}))],N.prototype,"fixed",void 0),o([n({type:Number}),C((function(e){this.mdcFoundation&&null!==this.y&&null!==e&&(this.mdcFoundation.setAbsolutePosition(e,this.y),this.mdcFoundation.setAnchorMargin({left:e,top:this.y,right:-e,bottom:this.y}))}))],N.prototype,"x",void 0),o([n({type:Number}),C((function(e){this.mdcFoundation&&null!==this.x&&null!==e&&(this.mdcFoundation.setAbsolutePosition(this.x,e),this.mdcFoundation.setAnchorMargin({left:this.x,top:e,right:-this.x,bottom:e}))}))],N.prototype,"y",void 0),o([n({type:Boolean}),C((function(e){this.mdcFoundation&&this.mdcFoundation.setQuickOpen(e)}))],N.prototype,"quick",void 0),o([n({type:Boolean,reflect:!0}),C((function(e,t){this.mdcFoundation&&(e?this.mdcFoundation.open():void 0!==t&&this.mdcFoundation.close())}))],N.prototype,"open",void 0),o([n({type:Boolean})],N.prototype,"stayOpenOnBodyClick",void 0),o([a(),C((function(e){this.mdcFoundation&&this.mdcFoundation.setAnchorCorner(e)}))],N.prototype,"bitwiseCorner",void 0),o([n({type:String}),C((function(e){if(this.mdcFoundation){const t="START"===e||"END"===e,i=null===this.previousMenuCorner,c=!i&&e!==this.previousMenuCorner,o=i&&"END"===e;t&&(c||o)&&(this.bitwiseCorner=this.bitwiseCorner^A.RIGHT,this.mdcFoundation.flipCornerHorizontally(),this.previousMenuCorner=e)}}))],N.prototype,"menuCorner",void 0),o([n({type:String}),C((function(e){if(this.mdcFoundation&&e){let t=L[e];"END"===this.menuCorner&&(t^=A.RIGHT),this.bitwiseCorner=t}}))],N.prototype,"corner",void 0),o([a()],N.prototype,"styleTop",void 0),o([a()],N.prototype,"styleLeft",void 0),o([a()],N.prototype,"styleRight",void 0),o([a()],N.prototype,"styleBottom",void 0),o([a()],N.prototype,"styleMaxHeight",void 0),o([a()],N.prototype,"styleTransformOrigin",void 0);const D=f`.mdc-menu-surface{display:none;position:absolute;box-sizing:border-box;max-width:calc(100vw - 32px);max-width:var(--mdc-menu-max-width, calc(100vw - 32px));max-height:calc(100vh - 32px);max-height:var(--mdc-menu-max-height, calc(100vh - 32px));margin:0;padding:0;transform:scale(1);transform-origin:top left;opacity:0;overflow:auto;will-change:transform,opacity;z-index:8;transition:opacity .03s linear,transform .12s cubic-bezier(0, 0, 0.2, 1),height 250ms cubic-bezier(0, 0, 0.2, 1);box-shadow:0px 5px 5px -3px rgba(0, 0, 0, 0.2),0px 8px 10px 1px rgba(0, 0, 0, 0.14),0px 3px 14px 2px rgba(0,0,0,.12);background-color:#fff;background-color:var(--mdc-theme-surface, #fff);color:#000;color:var(--mdc-theme-on-surface, #000);border-radius:4px;border-radius:var(--mdc-shape-medium, 4px);transform-origin-left:top left;transform-origin-right:top right}.mdc-menu-surface:focus{outline:none}.mdc-menu-surface--animating-open{display:inline-block;transform:scale(0.8);opacity:0}.mdc-menu-surface--open{display:inline-block;transform:scale(1);opacity:1}.mdc-menu-surface--animating-closed{display:inline-block;opacity:0;transition:opacity .075s linear}[dir=rtl] .mdc-menu-surface,.mdc-menu-surface[dir=rtl]{transform-origin-left:top right;transform-origin-right:top left}.mdc-menu-surface--anchor{position:relative;overflow:visible}.mdc-menu-surface--fixed{position:fixed}.mdc-menu-surface--fullwidth{width:100%}:host(:not([open])){display:none}.mdc-menu-surface{z-index:8;z-index:var(--mdc-menu-z-index, 8);min-width:112px;min-width:var(--mdc-menu-min-width, 112px)}`;let H=class extends N{};H.styles=[D],H=o([b("mwc-menu-surface")],H);var P,$={MENU_SELECTED_LIST_ITEM:"mdc-menu-item--selected",MENU_SELECTION_GROUP:"mdc-menu__selection-group",ROOT:"mdc-menu"},G={ARIA_CHECKED_ATTR:"aria-checked",ARIA_DISABLED_ATTR:"aria-disabled",CHECKBOX_SELECTOR:'input[type="checkbox"]',LIST_SELECTOR:".mdc-list,.mdc-deprecated-list",SELECTED_EVENT:"MDCMenu:selected",SKIP_RESTORE_FOCUS:"data-menu-item-skip-restore-focus"},U={FOCUS_ROOT_INDEX:-1};!function(e){e[e.NONE=0]="NONE",e[e.LIST_ROOT=1]="LIST_ROOT",e[e.FIRST_ITEM=2]="FIRST_ITEM",e[e.LAST_ITEM=3]="LAST_ITEM"}(P||(P={}));var j=function(i){function c(e){var o=i.call(this,t(t({},c.defaultAdapter),e))||this;return o.closeAnimationEndTimerId=0,o.defaultFocusState=P.LIST_ROOT,o.selectedIndex=-1,o}return e(c,i),Object.defineProperty(c,"cssClasses",{get:function(){return $},enumerable:!1,configurable:!0}),Object.defineProperty(c,"strings",{get:function(){return G},enumerable:!1,configurable:!0}),Object.defineProperty(c,"numbers",{get:function(){return U},enumerable:!1,configurable:!0}),Object.defineProperty(c,"defaultAdapter",{get:function(){return{addClassToElementAtIndex:function(){},removeClassFromElementAtIndex:function(){},addAttributeToElementAtIndex:function(){},removeAttributeFromElementAtIndex:function(){},getAttributeFromElementAtIndex:function(){return null},elementContainsClass:function(){return!1},closeSurface:function(){},getElementIndex:function(){return-1},notifySelected:function(){},getMenuItemCount:function(){return 0},focusItemAtIndex:function(){},focusListRoot:function(){},getSelectedSiblingOfItemAtIndex:function(){return-1},isSelectableItemAtIndex:function(){return!1}}},enumerable:!1,configurable:!0}),c.prototype.destroy=function(){this.closeAnimationEndTimerId&&clearTimeout(this.closeAnimationEndTimerId),this.adapter.closeSurface()},c.prototype.handleKeydown=function(e){var t=e.key,i=e.keyCode;("Tab"===t||9===i)&&this.adapter.closeSurface(!0)},c.prototype.handleItemAction=function(e){var t=this,i=this.adapter.getElementIndex(e);if(!(i<0)){this.adapter.notifySelected({index:i});var c="true"===this.adapter.getAttributeFromElementAtIndex(i,G.SKIP_RESTORE_FOCUS);this.adapter.closeSurface(c),this.closeAnimationEndTimerId=setTimeout((function(){var i=t.adapter.getElementIndex(e);i>=0&&t.adapter.isSelectableItemAtIndex(i)&&t.setSelectedIndex(i)}),M.numbers.TRANSITION_CLOSE_DURATION)}},c.prototype.handleMenuSurfaceOpened=function(){switch(this.defaultFocusState){case P.FIRST_ITEM:this.adapter.focusItemAtIndex(0);break;case P.LAST_ITEM:this.adapter.focusItemAtIndex(this.adapter.getMenuItemCount()-1);break;case P.NONE:break;default:this.adapter.focusListRoot()}},c.prototype.setDefaultFocusState=function(e){this.defaultFocusState=e},c.prototype.getSelectedIndex=function(){return this.selectedIndex},c.prototype.setSelectedIndex=function(e){if(this.validatedIndex(e),!this.adapter.isSelectableItemAtIndex(e))throw new Error("MDCMenuFoundation: No selection group at specified index.");var t=this.adapter.getSelectedSiblingOfItemAtIndex(e);t>=0&&(this.adapter.removeAttributeFromElementAtIndex(t,G.ARIA_CHECKED_ATTR),this.adapter.removeClassFromElementAtIndex(t,$.MENU_SELECTED_LIST_ITEM)),this.adapter.addClassToElementAtIndex(e,$.MENU_SELECTED_LIST_ITEM),this.adapter.addAttributeToElementAtIndex(e,G.ARIA_CHECKED_ATTR,"true"),this.selectedIndex=e},c.prototype.setEnabled=function(e,t){this.validatedIndex(e),t?(this.adapter.removeClassFromElementAtIndex(e,O.LIST_ITEM_DISABLED_CLASS),this.adapter.addAttributeToElementAtIndex(e,G.ARIA_DISABLED_ATTR,"false")):(this.adapter.addClassToElementAtIndex(e,O.LIST_ITEM_DISABLED_CLASS),this.adapter.addAttributeToElementAtIndex(e,G.ARIA_DISABLED_ATTR,"true"))},c.prototype.validatedIndex=function(e){var t=this.adapter.getMenuItemCount();if(!(e>=0&&e + + + + `}createAdapter(){return{addClassToElementAtIndex:(e,t)=>{const i=this.listElement;if(!i)return;const c=i.items[e];c&&("mdc-menu-item--selected"===t?this.forceGroupSelection&&!c.selected&&i.toggle(e,!0):c.classList.add(t))},removeClassFromElementAtIndex:(e,t)=>{const i=this.listElement;if(!i)return;const c=i.items[e];c&&("mdc-menu-item--selected"===t?c.selected&&i.toggle(e,!1):c.classList.remove(t))},addAttributeToElementAtIndex:(e,t,i)=>{const c=this.listElement;if(!c)return;const o=c.items[e];o&&o.setAttribute(t,i)},removeAttributeFromElementAtIndex:(e,t)=>{const i=this.listElement;if(!i)return;const c=i.items[e];c&&c.removeAttribute(t)},getAttributeFromElementAtIndex:(e,t)=>{const i=this.listElement;if(!i)return null;const c=i.items[e];return c?c.getAttribute(t):null},elementContainsClass:(e,t)=>e.classList.contains(t),closeSurface:()=>{this.open=!1},getElementIndex:e=>{const t=this.listElement;return t?t.items.indexOf(e):-1},notifySelected:()=>{},getMenuItemCount:()=>{const e=this.listElement;return e?e.items.length:0},focusItemAtIndex:e=>{const t=this.listElement;if(!t)return;const i=t.items[e];i&&i.focus()},focusListRoot:()=>{this.listElement&&this.listElement.focus()},getSelectedSiblingOfItemAtIndex:e=>{const t=this.listElement;if(!t)return-1;const i=t.items[e];if(!i||!i.group)return-1;for(let c=0;c{const t=this.listElement;if(!t)return!1;const i=t.items[e];return!!i&&i.hasAttribute("group")}}}onKeydown(e){this.mdcFoundation&&this.mdcFoundation.handleKeydown(e)}onAction(e){const t=this.listElement;if(this.mdcFoundation&&t){const i=e.detail.index,c=t.items[i];c&&this.mdcFoundation.handleItemAction(c)}}onOpened(){this.open=!0,this.mdcFoundation&&this.mdcFoundation.handleMenuSurfaceOpened()}onClosed(){this.open=!1}async getUpdateComplete(){await this._listUpdateComplete;return await super.getUpdateComplete()}async firstUpdated(){super.firstUpdated();const e=this.listElement;e&&(this._listUpdateComplete=e.updateComplete,await this._listUpdateComplete)}select(e){const t=this.listElement;t&&t.select(e)}close(){this.open=!1}show(){this.open=!0}getFocusedItemIndex(){const e=this.listElement;return e?e.getFocusedItemIndex():-1}focusItemAtIndex(e){const t=this.listElement;t&&t.focusItemAtIndex(e)}layout(e=!0){const t=this.listElement;t&&t.layout(e)}}o([r(".mdc-menu")],W.prototype,"mdcRoot",void 0),o([r("slot")],W.prototype,"slotElement",void 0),o([n({type:Object})],W.prototype,"anchor",void 0),o([n({type:Boolean,reflect:!0})],W.prototype,"open",void 0),o([n({type:Boolean})],W.prototype,"quick",void 0),o([n({type:Boolean})],W.prototype,"wrapFocus",void 0),o([n({type:String})],W.prototype,"innerRole",void 0),o([n({type:String})],W.prototype,"innerAriaLabel",void 0),o([n({type:String})],W.prototype,"corner",void 0),o([n({type:Number})],W.prototype,"x",void 0),o([n({type:Number})],W.prototype,"y",void 0),o([n({type:Boolean})],W.prototype,"absolute",void 0),o([n({type:Boolean})],W.prototype,"multi",void 0),o([n({type:Boolean})],W.prototype,"activatable",void 0),o([n({type:Boolean})],W.prototype,"fixed",void 0),o([n({type:Boolean})],W.prototype,"forceGroupSelection",void 0),o([n({type:Boolean})],W.prototype,"fullwidth",void 0),o([n({type:String})],W.prototype,"menuCorner",void 0),o([n({type:Boolean})],W.prototype,"stayOpenOnBodyClick",void 0),o([n({type:String}),C((function(e){this.mdcFoundation&&this.mdcFoundation.setDefaultFocusState(P[e])}))],W.prototype,"defaultFocus",void 0);const q=f`mwc-list ::slotted([mwc-list-item]:not([twoline])),mwc-list ::slotted([noninteractive]:not([twoline])){height:var(--mdc-menu-item-height, 48px)}`;let K=class extends W{};var V,X;K.styles=[q],K=o([b("mwc-menu")],K);const Q=null!==(X=null===(V=window.ShadyDOM)||void 0===V?void 0:V.inUse)&&void 0!==X&&X;class Y extends d{constructor(){super(...arguments),this.disabled=!1,this.containingForm=null,this.formDataListener=e=>{this.disabled||this.setFormData(e.formData)}}findFormElement(){if(!this.shadowRoot||Q)return null;const e=this.getRootNode().querySelectorAll("form");for(const t of Array.from(e))if(t.contains(this))return t;return null}connectedCallback(){var e;super.connectedCallback(),this.containingForm=this.findFormElement(),null===(e=this.containingForm)||void 0===e||e.addEventListener("formdata",this.formDataListener)}disconnectedCallback(){var e;super.disconnectedCallback(),null===(e=this.containingForm)||void 0===e||e.removeEventListener("formdata",this.formDataListener),this.containingForm=null}click(){this.formElement&&!this.disabled&&(this.formElement.focus(),this.formElement.click())}firstUpdated(){super.firstUpdated(),this.shadowRoot&&this.mdcRoot.addEventListener("change",(e=>{this.dispatchEvent(new Event("change",e))}))}}Y.shadowRootOptions={mode:"open",delegatesFocus:!0},o([n({type:Boolean})],Y.prototype,"disabled",void 0);var J='/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/material-components/material-components-web/blob/master/LICENSE\n */\n.mdc-touch-target-wrapper{display:inline}.mdc-deprecated-chip-trailing-action__touch{position:absolute;top:50%;height:48px;left:50%;width:48px;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%)}.mdc-deprecated-chip-trailing-action{border:none;display:inline-flex;position:relative;align-items:center;justify-content:center;box-sizing:border-box;padding:0;outline:none;cursor:pointer;-webkit-appearance:none;background:none}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__icon{height:18px;width:18px;font-size:18px}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__touch{width:26px}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__icon{fill:currentColor;color:inherit}@-webkit-keyframes mdc-ripple-fg-radius-in{from{-webkit-animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);-webkit-transform:translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);transform:translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1)}to{-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}}@keyframes mdc-ripple-fg-radius-in{from{-webkit-animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);animation-timing-function:cubic-bezier(0.4, 0, 0.2, 1);-webkit-transform:translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1);transform:translate(var(--mdc-ripple-fg-translate-start, 0)) scale(1)}to{-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}}@-webkit-keyframes mdc-ripple-fg-opacity-in{from{-webkit-animation-timing-function:linear;animation-timing-function:linear;opacity:0}to{opacity:var(--mdc-ripple-fg-opacity, 0)}}@keyframes mdc-ripple-fg-opacity-in{from{-webkit-animation-timing-function:linear;animation-timing-function:linear;opacity:0}to{opacity:var(--mdc-ripple-fg-opacity, 0)}}@-webkit-keyframes mdc-ripple-fg-opacity-out{from{-webkit-animation-timing-function:linear;animation-timing-function:linear;opacity:var(--mdc-ripple-fg-opacity, 0)}to{opacity:0}}@keyframes mdc-ripple-fg-opacity-out{from{-webkit-animation-timing-function:linear;animation-timing-function:linear;opacity:var(--mdc-ripple-fg-opacity, 0)}to{opacity:0}}.mdc-deprecated-chip-trailing-action{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--unbounded .mdc-deprecated-chip-trailing-action__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--foreground-activation .mdc-deprecated-chip-trailing-action__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--foreground-deactivation .mdc-deprecated-chip-trailing-action__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after{top:calc(50% - 50%);left:calc(50% - 50%);width:100%;height:100%}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after{top:var(--mdc-ripple-top, calc(50% - 50%));left:var(--mdc-ripple-left, calc(50% - 50%));width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded .mdc-deprecated-chip-trailing-action__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, var(--mdc-theme-on-surface, #000))}.mdc-deprecated-chip-trailing-action:hover .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action.mdc-ripple-surface--hover .mdc-deprecated-chip-trailing-action__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded--background-focused .mdc-deprecated-chip-trailing-action__ripple::before,.mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded):focus .mdc-deprecated-chip-trailing-action__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded) .mdc-deprecated-chip-trailing-action__ripple::after{transition:opacity 150ms linear}.mdc-deprecated-chip-trailing-action:not(.mdc-ripple-upgraded):active .mdc-deprecated-chip-trailing-action__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-deprecated-chip-trailing-action.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-deprecated-chip-trailing-action .mdc-deprecated-chip-trailing-action__ripple{position:absolute;box-sizing:content-box;width:100%;height:100%;overflow:hidden}.mdc-chip__icon--leading{color:rgba(0,0,0,.54)}.mdc-deprecated-chip-trailing-action{color:#000}.mdc-chip__icon--trailing{color:rgba(0,0,0,.54)}.mdc-chip__icon--trailing:hover{color:rgba(0,0,0,.62)}.mdc-chip__icon--trailing:focus{color:rgba(0,0,0,.87)}.mdc-chip__icon.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden){width:20px;height:20px;font-size:20px}.mdc-deprecated-chip-trailing-action__icon{height:18px;width:18px;font-size:18px}.mdc-chip__icon.mdc-chip__icon--trailing{width:18px;height:18px;font-size:18px}.mdc-deprecated-chip-trailing-action{margin-left:4px;margin-right:-4px}[dir=rtl] .mdc-deprecated-chip-trailing-action,.mdc-deprecated-chip-trailing-action[dir=rtl]{margin-left:-4px;margin-right:4px}.mdc-chip__icon--trailing{margin-left:4px;margin-right:-4px}[dir=rtl] .mdc-chip__icon--trailing,.mdc-chip__icon--trailing[dir=rtl]{margin-left:-4px;margin-right:4px}.mdc-elevation-overlay{position:absolute;border-radius:inherit;pointer-events:none;opacity:0;opacity:var(--mdc-elevation-overlay-opacity, 0);transition:opacity 280ms cubic-bezier(0.4, 0, 0.2, 1);background-color:#fff;background-color:var(--mdc-elevation-overlay-color, #fff)}.mdc-chip{border-radius:16px;background-color:#e0e0e0;color:rgba(0, 0, 0, 0.87);-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;-webkit-text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);height:32px;position:relative;display:inline-flex;align-items:center;box-sizing:border-box;padding:0 12px;border-width:0;outline:none;cursor:pointer;-webkit-appearance:none}.mdc-chip .mdc-chip__ripple{border-radius:16px}.mdc-chip:hover{color:rgba(0, 0, 0, 0.87)}.mdc-chip.mdc-chip--selected .mdc-chip__checkmark,.mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden){margin-left:-4px;margin-right:4px}[dir=rtl] .mdc-chip.mdc-chip--selected .mdc-chip__checkmark,[dir=rtl] .mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden),.mdc-chip.mdc-chip--selected .mdc-chip__checkmark[dir=rtl],.mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden)[dir=rtl]{margin-left:4px;margin-right:-4px}.mdc-chip .mdc-elevation-overlay{width:100%;height:100%;top:0;left:0}.mdc-chip::-moz-focus-inner{padding:0;border:0}.mdc-chip:hover{color:#000;color:var(--mdc-theme-on-surface, #000)}.mdc-chip .mdc-chip__touch{position:absolute;top:50%;height:48px;left:0;right:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.mdc-chip--exit{transition:opacity 75ms cubic-bezier(0.4, 0, 0.2, 1),width 150ms cubic-bezier(0, 0, 0.2, 1),padding 100ms linear,margin 100ms linear;opacity:0}.mdc-chip__overflow{text-overflow:ellipsis;overflow:hidden}.mdc-chip__text{white-space:nowrap}.mdc-chip__icon{border-radius:50%;outline:none;vertical-align:middle}.mdc-chip__checkmark{height:20px}.mdc-chip__checkmark-path{transition:stroke-dashoffset 150ms 50ms cubic-bezier(0.4, 0, 0.6, 1);stroke-width:2px;stroke-dashoffset:29.7833385;stroke-dasharray:29.7833385}.mdc-chip__primary-action:focus{outline:none}.mdc-chip--selected .mdc-chip__checkmark-path{stroke-dashoffset:0}.mdc-chip__icon--leading,.mdc-chip__icon--trailing{position:relative}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__icon--leading{color:rgba(98,0,238,.54)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:hover{color:#6200ee;color:var(--mdc-theme-primary, #6200ee)}.mdc-chip-set--choice .mdc-chip .mdc-chip__checkmark-path{stroke:#6200ee;stroke:var(--mdc-theme-primary, #6200ee)}.mdc-chip-set--choice .mdc-chip--selected{background-color:#fff;background-color:var(--mdc-theme-surface, #fff)}.mdc-chip__checkmark-svg{width:0;height:20px;transition:width 150ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-chip--selected .mdc-chip__checkmark-svg{width:20px}.mdc-chip-set--filter .mdc-chip__icon--leading{transition:opacity 75ms linear;transition-delay:-50ms;opacity:1}.mdc-chip-set--filter .mdc-chip__icon--leading+.mdc-chip__checkmark{transition:opacity 75ms linear;transition-delay:80ms;opacity:0}.mdc-chip-set--filter .mdc-chip__icon--leading+.mdc-chip__checkmark .mdc-chip__checkmark-svg{transition:width 0ms}.mdc-chip-set--filter .mdc-chip--selected .mdc-chip__icon--leading{opacity:0}.mdc-chip-set--filter .mdc-chip--selected .mdc-chip__icon--leading+.mdc-chip__checkmark{width:0;opacity:1}.mdc-chip-set--filter .mdc-chip__icon--leading-hidden.mdc-chip__icon--leading{width:0;opacity:0}.mdc-chip-set--filter .mdc-chip__icon--leading-hidden.mdc-chip__icon--leading+.mdc-chip__checkmark{width:20px}.mdc-chip{--mdc-ripple-fg-size: 0;--mdc-ripple-left: 0;--mdc-ripple-top: 0;--mdc-ripple-fg-scale: 1;--mdc-ripple-fg-translate-end: 0;--mdc-ripple-fg-translate-start: 0;-webkit-tap-highlight-color:rgba(0,0,0,0);will-change:transform,opacity}.mdc-chip .mdc-chip__ripple::before,.mdc-chip .mdc-chip__ripple::after{position:absolute;border-radius:50%;opacity:0;pointer-events:none;content:""}.mdc-chip .mdc-chip__ripple::before{transition:opacity 15ms linear,background-color 15ms linear;z-index:1;z-index:var(--mdc-ripple-z-index, 1)}.mdc-chip .mdc-chip__ripple::after{z-index:0;z-index:var(--mdc-ripple-z-index, 0)}.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::before{-webkit-transform:scale(var(--mdc-ripple-fg-scale, 1));transform:scale(var(--mdc-ripple-fg-scale, 1))}.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::after{top:0;left:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:center center;transform-origin:center center}.mdc-chip.mdc-ripple-upgraded--unbounded .mdc-chip__ripple::after{top:var(--mdc-ripple-top, 0);left:var(--mdc-ripple-left, 0)}.mdc-chip.mdc-ripple-upgraded--foreground-activation .mdc-chip__ripple::after{-webkit-animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards;animation:mdc-ripple-fg-radius-in 225ms forwards,mdc-ripple-fg-opacity-in 75ms forwards}.mdc-chip.mdc-ripple-upgraded--foreground-deactivation .mdc-chip__ripple::after{-webkit-animation:mdc-ripple-fg-opacity-out 150ms;animation:mdc-ripple-fg-opacity-out 150ms;-webkit-transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));transform:translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1))}.mdc-chip .mdc-chip__ripple::before,.mdc-chip .mdc-chip__ripple::after{top:calc(50% - 100%);left:calc(50% - 100%);width:200%;height:200%}.mdc-chip.mdc-ripple-upgraded .mdc-chip__ripple::after{width:var(--mdc-ripple-fg-size, 100%);height:var(--mdc-ripple-fg-size, 100%)}.mdc-chip .mdc-chip__ripple::before,.mdc-chip .mdc-chip__ripple::after{background-color:rgba(0, 0, 0, 0.87);background-color:var(--mdc-ripple-color, rgba(0, 0, 0, 0.87))}.mdc-chip:hover .mdc-chip__ripple::before,.mdc-chip.mdc-ripple-surface--hover .mdc-chip__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-chip.mdc-ripple-upgraded--background-focused .mdc-chip__ripple::before,.mdc-chip.mdc-ripple-upgraded:focus-within .mdc-chip__ripple::before,.mdc-chip:not(.mdc-ripple-upgraded):focus .mdc-chip__ripple::before,.mdc-chip:not(.mdc-ripple-upgraded):focus-within .mdc-chip__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-chip:not(.mdc-ripple-upgraded) .mdc-chip__ripple::after{transition:opacity 150ms linear}.mdc-chip:not(.mdc-ripple-upgraded):active .mdc-chip__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-chip.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-chip .mdc-chip__ripple{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:hidden}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::before{opacity:0.08;opacity:var(--mdc-ripple-selected-opacity, 0.08)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected .mdc-chip__ripple::after{background-color:#6200ee;background-color:var(--mdc-ripple-color, var(--mdc-theme-primary, #6200ee))}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:hover .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-surface--hover .mdc-chip__ripple::before{opacity:0.12;opacity:var(--mdc-ripple-hover-opacity, 0.12)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded--background-focused .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded:focus-within .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):focus .mdc-chip__ripple::before,.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):focus-within .mdc-chip__ripple::before{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-focus-opacity, 0.2)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded) .mdc-chip__ripple::after{transition:opacity 150ms linear}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected:not(.mdc-ripple-upgraded):active .mdc-chip__ripple::after{transition-duration:75ms;opacity:0.2;opacity:var(--mdc-ripple-press-opacity, 0.2)}.mdc-chip-set--choice .mdc-chip.mdc-chip--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.2)}@-webkit-keyframes mdc-chip-entry{from{-webkit-transform:scale(0.8);transform:scale(0.8);opacity:.4}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes mdc-chip-entry{from{-webkit-transform:scale(0.8);transform:scale(0.8);opacity:.4}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.mdc-chip-set{padding:4px;display:flex;flex-wrap:wrap;box-sizing:border-box}.mdc-chip-set .mdc-chip{margin:4px}.mdc-chip-set .mdc-chip--touch{margin-top:8px;margin-bottom:8px}.mdc-chip-set--input .mdc-chip{-webkit-animation:mdc-chip-entry 100ms cubic-bezier(0, 0, 0.2, 1);animation:mdc-chip-entry 100ms cubic-bezier(0, 0, 0.2, 1)}\n\n/*# sourceMappingURL=mdc.chips.min.css.map*/';g([b("ha-chip")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[n({type:Boolean})],key:"hasIcon",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"hasTrailingIcon",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"noText",value:()=>!1},{kind:"method",key:"render",value:function(){return s` +
+ ${this.hasIcon?s`
+ +
`:null} +
+ + + + + + ${this.hasTrailingIcon?s`
+ +
`:null} +
+ `}},{kind:"get",static:!0,key:"styles",value:function(){return f` + ${x(J)} + .mdc-chip { + background-color: var( + --ha-chip-background-color, + rgba(var(--rgb-primary-text-color), 0.15) + ); + color: var(--ha-chip-text-color, var(--primary-text-color)); + } + + .mdc-chip.no-text { + padding: 0 10px; + } + + .mdc-chip:hover { + color: var(--ha-chip-text-color, var(--primary-text-color)); + } + + .mdc-chip__icon--leading, + .mdc-chip__icon--trailing { + --mdc-icon-size: 18px; + line-height: 14px; + color: var(--ha-chip-icon-color, var(--ha-chip-text-color)); + } + .mdc-chip.mdc-chip--selected .mdc-chip__checkmark, + .mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) { + margin-right: -4px; + margin-inline-start: -4px; + margin-inline-end: 4px; + direction: var(--direction); + } + + span[role="gridcell"] { + line-height: 14px; + } + `}}]}}),k);class Z extends Y{constructor(){super(...arguments),this.checked=!1,this.indeterminate=!1,this.disabled=!1,this.name="",this.value="on",this.reducedTouchTarget=!1,this.animationClass="",this.shouldRenderRipple=!1,this.focused=!1,this.mdcFoundationClass=void 0,this.mdcFoundation=void 0,this.rippleElement=null,this.rippleHandlers=new T((()=>(this.shouldRenderRipple=!0,this.ripple.then((e=>this.rippleElement=e)),this.ripple)))}createAdapter(){return{}}update(e){const t=e.get("indeterminate"),i=e.get("checked"),c=e.get("disabled");if(void 0!==t||void 0!==i||void 0!==c){const e=this.calculateAnimationStateName(!!i,!!t,!!c),o=this.calculateAnimationStateName(this.checked,this.indeterminate,this.disabled);this.animationClass=`${e}-${o}`}super.update(e)}calculateAnimationStateName(e,t,i){return i?"disabled":t?"indeterminate":e?"checked":"unchecked"}renderRipple(){return this.shouldRenderRipple?this.renderRippleTemplate():""}renderRippleTemplate(){return s``}render(){const e=this.indeterminate||this.checked,t={"mdc-checkbox--disabled":this.disabled,"mdc-checkbox--selected":e,"mdc-checkbox--touch":!this.reducedTouchTarget,"mdc-ripple-upgraded--background-focused":this.focused,"mdc-checkbox--anim-checked-indeterminate":"checked-indeterminate"==this.animationClass,"mdc-checkbox--anim-checked-unchecked":"checked-unchecked"==this.animationClass,"mdc-checkbox--anim-indeterminate-checked":"indeterminate-checked"==this.animationClass,"mdc-checkbox--anim-indeterminate-unchecked":"indeterminate-unchecked"==this.animationClass,"mdc-checkbox--anim-unchecked-checked":"unchecked-checked"==this.animationClass,"mdc-checkbox--anim-unchecked-indeterminate":"unchecked-indeterminate"==this.animationClass},i=this.indeterminate?"mixed":void 0;return s` +
+ +
+ + + +
+
+ ${this.renderRipple()} +
`}setFormData(e){this.name&&this.checked&&e.append(this.name,this.value)}handleFocus(){this.focused=!0,this.handleRippleFocus()}handleBlur(){this.focused=!1,this.handleRippleBlur()}handleRippleMouseDown(e){const t=()=>{window.removeEventListener("mouseup",t),this.handleRippleDeactivate()};window.addEventListener("mouseup",t),this.rippleHandlers.startPress(e)}handleRippleTouchStart(e){this.rippleHandlers.startPress(e)}handleRippleDeactivate(){this.rippleHandlers.endPress()}handleRippleMouseEnter(){this.rippleHandlers.startHover()}handleRippleMouseLeave(){this.rippleHandlers.endHover()}handleRippleFocus(){this.rippleHandlers.startFocus()}handleRippleBlur(){this.rippleHandlers.endFocus()}handleChange(){this.checked=this.formElement.checked,this.indeterminate=this.formElement.indeterminate}resetAnimationClass(){this.animationClass=""}get isRippleActive(){var e;return(null===(e=this.rippleElement)||void 0===e?void 0:e.isActive)||!1}}o([r(".mdc-checkbox")],Z.prototype,"mdcRoot",void 0),o([r("input")],Z.prototype,"formElement",void 0),o([n({type:Boolean,reflect:!0})],Z.prototype,"checked",void 0),o([n({type:Boolean})],Z.prototype,"indeterminate",void 0),o([n({type:Boolean,reflect:!0})],Z.prototype,"disabled",void 0),o([n({type:String,reflect:!0})],Z.prototype,"name",void 0),o([n({type:String})],Z.prototype,"value",void 0),o([_,n({type:String,attribute:"aria-label"})],Z.prototype,"ariaLabel",void 0),o([_,n({type:String,attribute:"aria-labelledby"})],Z.prototype,"ariaLabelledBy",void 0),o([_,n({type:String,attribute:"aria-describedby"})],Z.prototype,"ariaDescribedBy",void 0),o([n({type:Boolean})],Z.prototype,"reducedTouchTarget",void 0),o([a()],Z.prototype,"animationClass",void 0),o([a()],Z.prototype,"shouldRenderRipple",void 0),o([a()],Z.prototype,"focused",void 0),o([y("mwc-ripple")],Z.prototype,"ripple",void 0),o([v({passive:!0})],Z.prototype,"handleRippleTouchStart",null);const ee=f`.mdc-checkbox{padding:calc((40px - 18px) / 2);padding:calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2);margin:calc((40px - 40px) / 2);margin:calc((var(--mdc-checkbox-touch-target-size, 40px) - 40px) / 2)}.mdc-checkbox .mdc-checkbox__ripple::before,.mdc-checkbox .mdc-checkbox__ripple::after{background-color:#000;background-color:var(--mdc-ripple-color, #000)}.mdc-checkbox:hover .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-ripple-surface--hover .mdc-checkbox__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-checkbox.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before,.mdc-checkbox:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-checkbox:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after{transition:opacity 150ms linear}.mdc-checkbox:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-checkbox.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-checkbox--selected .mdc-checkbox__ripple::after{background-color:#018786;background-color:var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786))}.mdc-checkbox.mdc-checkbox--selected:hover .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-checkbox--selected.mdc-ripple-surface--hover .mdc-checkbox__ripple::before{opacity:0.04;opacity:var(--mdc-ripple-hover-opacity, 0.04)}.mdc-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded--background-focused .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):focus .mdc-checkbox__ripple::before{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-focus-opacity, 0.12)}.mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded) .mdc-checkbox__ripple::after{transition:opacity 150ms linear}.mdc-checkbox.mdc-checkbox--selected:not(.mdc-ripple-upgraded):active .mdc-checkbox__ripple::after{transition-duration:75ms;opacity:0.12;opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-checkbox.mdc-checkbox--selected.mdc-ripple-upgraded{--mdc-ripple-fg-opacity:var(--mdc-ripple-press-opacity, 0.12)}.mdc-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::before,.mdc-checkbox.mdc-ripple-upgraded--background-focused.mdc-checkbox--selected .mdc-checkbox__ripple::after{background-color:#018786;background-color:var(--mdc-ripple-color, var(--mdc-theme-secondary, #018786))}.mdc-checkbox .mdc-checkbox__background{top:calc((40px - 18px) / 2);top:calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2);left:calc((40px - 18px) / 2);left:calc((var(--mdc-checkbox-ripple-size, 40px) - 18px) / 2)}.mdc-checkbox .mdc-checkbox__native-control{top:calc((40px - 40px) / 2);top:calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);right:calc((40px - 40px) / 2);right:calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);left:calc((40px - 40px) / 2);left:calc((40px - var(--mdc-checkbox-touch-target-size, 40px)) / 2);width:40px;width:var(--mdc-checkbox-touch-target-size, 40px);height:40px;height:var(--mdc-checkbox-touch-target-size, 40px)}.mdc-checkbox .mdc-checkbox__native-control:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true])~.mdc-checkbox__background{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}.mdc-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background,.mdc-checkbox .mdc-checkbox__native-control:enabled:indeterminate~.mdc-checkbox__background,.mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true]:enabled~.mdc-checkbox__background{border-color:#018786;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));background-color:#018786;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786))}@keyframes mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786{0%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}50%{border-color:#018786;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));background-color:#018786;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786))}}@keyframes mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786{0%,80%{border-color:#018786;border-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786));background-color:#018786;background-color:var(--mdc-checkbox-checked-color, var(--mdc-theme-secondary, #018786))}100%{border-color:rgba(0, 0, 0, 0.54);border-color:var(--mdc-checkbox-unchecked-color, rgba(0, 0, 0, 0.54));background-color:transparent}}.mdc-checkbox.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-checkbox.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__native-control:enabled~.mdc-checkbox__background{animation-name:mdc-checkbox-fade-in-background-8A000000FF01878600000000FF018786}.mdc-checkbox.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background,.mdc-checkbox.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__native-control:enabled~.mdc-checkbox__background{animation-name:mdc-checkbox-fade-out-background-8A000000FF01878600000000FF018786}.mdc-checkbox .mdc-checkbox__native-control[disabled]:not(:checked):not(:indeterminate):not([data-indeterminate=true])~.mdc-checkbox__background{border-color:rgba(0, 0, 0, 0.38);border-color:var(--mdc-checkbox-disabled-color, rgba(0, 0, 0, 0.38));background-color:transparent}.mdc-checkbox .mdc-checkbox__native-control[disabled]:checked~.mdc-checkbox__background,.mdc-checkbox .mdc-checkbox__native-control[disabled]:indeterminate~.mdc-checkbox__background,.mdc-checkbox .mdc-checkbox__native-control[data-indeterminate=true][disabled]~.mdc-checkbox__background{border-color:transparent;background-color:rgba(0, 0, 0, 0.38);background-color:var(--mdc-checkbox-disabled-color, rgba(0, 0, 0, 0.38))}.mdc-checkbox .mdc-checkbox__native-control:enabled~.mdc-checkbox__background .mdc-checkbox__checkmark{color:#fff;color:var(--mdc-checkbox-ink-color, #fff)}.mdc-checkbox .mdc-checkbox__native-control:enabled~.mdc-checkbox__background .mdc-checkbox__mixedmark{border-color:#fff;border-color:var(--mdc-checkbox-ink-color, #fff)}.mdc-checkbox .mdc-checkbox__native-control:disabled~.mdc-checkbox__background .mdc-checkbox__checkmark{color:#fff;color:var(--mdc-checkbox-ink-color, #fff)}.mdc-checkbox .mdc-checkbox__native-control:disabled~.mdc-checkbox__background .mdc-checkbox__mixedmark{border-color:#fff;border-color:var(--mdc-checkbox-ink-color, #fff)}.mdc-touch-target-wrapper{display:inline}@keyframes mdc-checkbox-unchecked-checked-checkmark-path{0%,50%{stroke-dashoffset:29.7833385}50%{animation-timing-function:cubic-bezier(0, 0, 0.2, 1)}100%{stroke-dashoffset:0}}@keyframes mdc-checkbox-unchecked-indeterminate-mixedmark{0%,68.2%{transform:scaleX(0)}68.2%{animation-timing-function:cubic-bezier(0, 0, 0, 1)}100%{transform:scaleX(1)}}@keyframes mdc-checkbox-checked-unchecked-checkmark-path{from{animation-timing-function:cubic-bezier(0.4, 0, 1, 1);opacity:1;stroke-dashoffset:0}to{opacity:0;stroke-dashoffset:-29.7833385}}@keyframes mdc-checkbox-checked-indeterminate-checkmark{from{animation-timing-function:cubic-bezier(0, 0, 0.2, 1);transform:rotate(0deg);opacity:1}to{transform:rotate(45deg);opacity:0}}@keyframes mdc-checkbox-indeterminate-checked-checkmark{from{animation-timing-function:cubic-bezier(0.14, 0, 0, 1);transform:rotate(45deg);opacity:0}to{transform:rotate(360deg);opacity:1}}@keyframes mdc-checkbox-checked-indeterminate-mixedmark{from{animation-timing-function:mdc-animation-deceleration-curve-timing-function;transform:rotate(-45deg);opacity:0}to{transform:rotate(0deg);opacity:1}}@keyframes mdc-checkbox-indeterminate-checked-mixedmark{from{animation-timing-function:cubic-bezier(0.14, 0, 0, 1);transform:rotate(0deg);opacity:1}to{transform:rotate(315deg);opacity:0}}@keyframes mdc-checkbox-indeterminate-unchecked-mixedmark{0%{animation-timing-function:linear;transform:scaleX(1);opacity:1}32.8%,100%{transform:scaleX(0);opacity:0}}.mdc-checkbox{display:inline-block;position:relative;flex:0 0 18px;box-sizing:content-box;width:18px;height:18px;line-height:0;white-space:nowrap;cursor:pointer;vertical-align:bottom}@media screen and (forced-colors: active),(-ms-high-contrast: active){.mdc-checkbox__native-control[disabled]:not(:checked):not(:indeterminate):not([data-indeterminate=true])~.mdc-checkbox__background{border-color:GrayText;border-color:var(--mdc-checkbox-disabled-color, GrayText);background-color:transparent}.mdc-checkbox__native-control[disabled]:checked~.mdc-checkbox__background,.mdc-checkbox__native-control[disabled]:indeterminate~.mdc-checkbox__background,.mdc-checkbox__native-control[data-indeterminate=true][disabled]~.mdc-checkbox__background{border-color:GrayText;background-color:transparent;background-color:var(--mdc-checkbox-disabled-color, transparent)}.mdc-checkbox__native-control:disabled~.mdc-checkbox__background .mdc-checkbox__checkmark{color:GrayText;color:var(--mdc-checkbox-ink-color, GrayText)}.mdc-checkbox__native-control:disabled~.mdc-checkbox__background .mdc-checkbox__mixedmark{border-color:GrayText;border-color:var(--mdc-checkbox-ink-color, GrayText)}.mdc-checkbox__mixedmark{margin:0 1px}}.mdc-checkbox--disabled{cursor:default;pointer-events:none}.mdc-checkbox__background{display:inline-flex;position:absolute;align-items:center;justify-content:center;box-sizing:border-box;width:18px;height:18px;border:2px solid currentColor;border-radius:2px;background-color:transparent;pointer-events:none;will-change:background-color,border-color;transition:background-color 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),border-color 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-checkbox__checkmark{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;opacity:0;transition:opacity 180ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-checkbox--upgraded .mdc-checkbox__checkmark{opacity:1}.mdc-checkbox__checkmark-path{transition:stroke-dashoffset 180ms 0ms cubic-bezier(0.4, 0, 0.6, 1);stroke:currentColor;stroke-width:3.12px;stroke-dashoffset:29.7833385;stroke-dasharray:29.7833385}.mdc-checkbox__mixedmark{width:100%;height:0;transform:scaleX(0) rotate(0deg);border-width:1px;border-style:solid;opacity:0;transition:opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__background,.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__background,.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__background,.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__background{animation-duration:180ms;animation-timing-function:linear}.mdc-checkbox--anim-unchecked-checked .mdc-checkbox__checkmark-path{animation:mdc-checkbox-unchecked-checked-checkmark-path 180ms linear 0s;transition:none}.mdc-checkbox--anim-unchecked-indeterminate .mdc-checkbox__mixedmark{animation:mdc-checkbox-unchecked-indeterminate-mixedmark 90ms linear 0s;transition:none}.mdc-checkbox--anim-checked-unchecked .mdc-checkbox__checkmark-path{animation:mdc-checkbox-checked-unchecked-checkmark-path 90ms linear 0s;transition:none}.mdc-checkbox--anim-checked-indeterminate .mdc-checkbox__checkmark{animation:mdc-checkbox-checked-indeterminate-checkmark 90ms linear 0s;transition:none}.mdc-checkbox--anim-checked-indeterminate .mdc-checkbox__mixedmark{animation:mdc-checkbox-checked-indeterminate-mixedmark 90ms linear 0s;transition:none}.mdc-checkbox--anim-indeterminate-checked .mdc-checkbox__checkmark{animation:mdc-checkbox-indeterminate-checked-checkmark 500ms linear 0s;transition:none}.mdc-checkbox--anim-indeterminate-checked .mdc-checkbox__mixedmark{animation:mdc-checkbox-indeterminate-checked-mixedmark 500ms linear 0s;transition:none}.mdc-checkbox--anim-indeterminate-unchecked .mdc-checkbox__mixedmark{animation:mdc-checkbox-indeterminate-unchecked-mixedmark 300ms linear 0s;transition:none}.mdc-checkbox__native-control:checked~.mdc-checkbox__background,.mdc-checkbox__native-control:indeterminate~.mdc-checkbox__background,.mdc-checkbox__native-control[data-indeterminate=true]~.mdc-checkbox__background{transition:border-color 90ms 0ms cubic-bezier(0, 0, 0.2, 1),background-color 90ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-checkbox__native-control:checked~.mdc-checkbox__background .mdc-checkbox__checkmark-path,.mdc-checkbox__native-control:indeterminate~.mdc-checkbox__background .mdc-checkbox__checkmark-path,.mdc-checkbox__native-control[data-indeterminate=true]~.mdc-checkbox__background .mdc-checkbox__checkmark-path{stroke-dashoffset:0}.mdc-checkbox__native-control{position:absolute;margin:0;padding:0;opacity:0;cursor:inherit}.mdc-checkbox__native-control:disabled{cursor:default;pointer-events:none}.mdc-checkbox--touch{margin:calc((48px - 40px) / 2);margin:calc((var(--mdc-checkbox-state-layer-size, 48px) - var(--mdc-checkbox-state-layer-size, 40px)) / 2)}.mdc-checkbox--touch .mdc-checkbox__native-control{top:calc((40px - 48px) / 2);top:calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);right:calc((40px - 48px) / 2);right:calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);left:calc((40px - 48px) / 2);left:calc((var(--mdc-checkbox-state-layer-size, 40px) - var(--mdc-checkbox-state-layer-size, 48px)) / 2);width:48px;width:var(--mdc-checkbox-state-layer-size, 48px);height:48px;height:var(--mdc-checkbox-state-layer-size, 48px)}.mdc-checkbox__native-control:checked~.mdc-checkbox__background .mdc-checkbox__checkmark{transition:opacity 180ms 0ms cubic-bezier(0, 0, 0.2, 1),transform 180ms 0ms cubic-bezier(0, 0, 0.2, 1);opacity:1}.mdc-checkbox__native-control:checked~.mdc-checkbox__background .mdc-checkbox__mixedmark{transform:scaleX(1) rotate(-45deg)}.mdc-checkbox__native-control:indeterminate~.mdc-checkbox__background .mdc-checkbox__checkmark,.mdc-checkbox__native-control[data-indeterminate=true]~.mdc-checkbox__background .mdc-checkbox__checkmark{transform:rotate(45deg);opacity:0;transition:opacity 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1),transform 90ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-checkbox__native-control:indeterminate~.mdc-checkbox__background .mdc-checkbox__mixedmark,.mdc-checkbox__native-control[data-indeterminate=true]~.mdc-checkbox__background .mdc-checkbox__mixedmark{transform:scaleX(1) rotate(0deg);opacity:1}.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__background,.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__checkmark,.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__checkmark-path,.mdc-checkbox.mdc-checkbox--upgraded .mdc-checkbox__mixedmark{transition:none}:host{outline:none;display:inline-flex;-webkit-tap-highlight-color:transparent}:host([checked]),:host([indeterminate]){--mdc-ripple-color:var(--mdc-theme-secondary, #018786)}.mdc-checkbox .mdc-checkbox__background::before{content:none}`;g([b("ha-checkbox")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",static:!0,key:"styles",value:()=>[ee,f` + :host { + --mdc-theme-secondary: var(--primary-color); + } + `]}]}}),Z);var te={ROOT:"mdc-form-field"},ie={LABEL_SELECTOR:".mdc-form-field > label"},ce=function(i){function c(e){var o=i.call(this,t(t({},c.defaultAdapter),e))||this;return o.click=function(){o.handleClick()},o}return e(c,i),Object.defineProperty(c,"cssClasses",{get:function(){return te},enumerable:!1,configurable:!0}),Object.defineProperty(c,"strings",{get:function(){return ie},enumerable:!1,configurable:!0}),Object.defineProperty(c,"defaultAdapter",{get:function(){return{activateInputRipple:function(){},deactivateInputRipple:function(){},deregisterInteractionHandler:function(){},registerInteractionHandler:function(){}}},enumerable:!1,configurable:!0}),c.prototype.init=function(){this.adapter.registerInteractionHandler("click",this.click)},c.prototype.destroy=function(){this.adapter.deregisterInteractionHandler("click",this.click)},c.prototype.handleClick=function(){var e=this;this.adapter.activateInputRipple(),requestAnimationFrame((function(){e.adapter.deactivateInputRipple()}))},c}(c);class oe extends d{constructor(){super(...arguments),this.alignEnd=!1,this.spaceBetween=!1,this.nowrap=!1,this.label="",this.mdcFoundationClass=ce}createAdapter(){return{registerInteractionHandler:(e,t)=>{this.labelEl.addEventListener(e,t)},deregisterInteractionHandler:(e,t)=>{this.labelEl.removeEventListener(e,t)},activateInputRipple:async()=>{const e=this.input;if(e instanceof Y){const t=await e.ripple;t&&t.startPress()}},deactivateInputRipple:async()=>{const e=this.input;if(e instanceof Y){const t=await e.ripple;t&&t.endPress()}}}}get input(){var e,t;return null!==(t=null===(e=this.slottedInputs)||void 0===e?void 0:e[0])&&void 0!==t?t:null}render(){const e={"mdc-form-field--align-end":this.alignEnd,"mdc-form-field--space-between":this.spaceBetween,"mdc-form-field--nowrap":this.nowrap};return s` +
+ + +
`}click(){this._labelClick()}_labelClick(){const e=this.input;e&&(e.focus(),e.click())}}o([n({type:Boolean})],oe.prototype,"alignEnd",void 0),o([n({type:Boolean})],oe.prototype,"spaceBetween",void 0),o([n({type:Boolean})],oe.prototype,"nowrap",void 0),o([n({type:String}),C((async function(e){var t;null===(t=this.input)||void 0===t||t.setAttribute("aria-label",e)}))],oe.prototype,"label",void 0),o([r(".mdc-form-field")],oe.prototype,"mdcRoot",void 0),o([I("",!0,"*")],oe.prototype,"slottedInputs",void 0),o([r("label")],oe.prototype,"labelEl",void 0);const re=f`.mdc-form-field{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));display:inline-flex;align-items:center;vertical-align:middle}.mdc-form-field>label{margin-left:0;margin-right:auto;padding-left:4px;padding-right:0;order:0}[dir=rtl] .mdc-form-field>label,.mdc-form-field>label[dir=rtl]{margin-left:auto;margin-right:0}[dir=rtl] .mdc-form-field>label,.mdc-form-field>label[dir=rtl]{padding-left:0;padding-right:4px}.mdc-form-field--nowrap>label{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.mdc-form-field--align-end>label{margin-left:auto;margin-right:0;padding-left:0;padding-right:4px;order:-1}[dir=rtl] .mdc-form-field--align-end>label,.mdc-form-field--align-end>label[dir=rtl]{margin-left:0;margin-right:auto}[dir=rtl] .mdc-form-field--align-end>label,.mdc-form-field--align-end>label[dir=rtl]{padding-left:4px;padding-right:0}.mdc-form-field--space-between{justify-content:space-between}.mdc-form-field--space-between>label{margin:0}[dir=rtl] .mdc-form-field--space-between>label,.mdc-form-field--space-between>label[dir=rtl]{margin:0}:host{display:inline-flex}.mdc-form-field{width:100%}::slotted(*){-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87))}::slotted(mwc-switch){margin-right:10px}[dir=rtl] ::slotted(mwc-switch),::slotted(mwc-switch[dir=rtl]){margin-left:10px}`;g([b("ha-formfield")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"method",key:"_labelClick",value:function(){const e=this.input;if(e)switch(e.focus(),e.tagName){case"HA-CHECKBOX":case"HA-RADIO":e.checked=!e.checked,E(e,"change");break;default:e.click()}}},{kind:"field",static:!0,key:"styles",value:()=>[re,f` + :host(:not([alignEnd])) ::slotted(ha-switch) { + margin-right: 10px; + margin-inline-end: 10px; + margin-inline-start: inline; + } + .mdc-form-field > label { + direction: var(--direction); + margin-inline-start: 0; + margin-inline-end: auto; + padding-inline-start: 4px; + padding-inline-end: 0; + } + `]}]}}),oe);export{R as C,Y as F,oe as a,ee as b,Z as c,J as d,re as s}; diff --git a/custom_components/hacs/hacs_frontend/c.01f18260.js.gz b/custom_components/hacs/hacs_frontend/c.01f18260.js.gz new file mode 100644 index 00000000..004f9825 Binary files /dev/null and b/custom_components/hacs/hacs_frontend/c.01f18260.js.gz differ diff --git a/custom_components/hacs/hacs_frontend/c.04294c77.js b/custom_components/hacs/hacs_frontend/c.04294c77.js deleted file mode 100644 index ddcef209..00000000 --- a/custom_components/hacs/hacs_frontend/c.04294c77.js +++ /dev/null @@ -1,63 +0,0 @@ -import{a as o,H as s,e as t,t as i,m as e,aj as a,a3 as r,a0 as l,a1 as h,$ as n,ak as c,al as d,am as p,an as _,ao as y,ai as m,d as u,r as v,n as g}from"./main-c805434e.js";import{c as f}from"./c.4a97632a.js";import"./c.6b338b4b.js";import"./c.7e9628d7.js";import{s as b}from"./c.791b7770.js";import{u as w}from"./c.15b2193e.js";import"./c.d49c601d.js";import"./c.70de318c.js";import"./c.ff857a48.js";import"./c.fa497e12.js";import"./c.8e28b461.js";import"./c.249923af.js";import"./c.9175c851.js";import"./c.1fc70989.js";import"./c.78610cf7.js";let k=o([g("hacs-download-dialog")],(function(o,s){return{F:class extends s{constructor(...s){super(...s),o(this)}},d:[{kind:"field",decorators:[t()],key:"repository",value:void 0},{kind:"field",decorators:[i()],key:"_toggle",value:()=>!0},{kind:"field",decorators:[i()],key:"_installing",value:()=>!1},{kind:"field",decorators:[i()],key:"_error",value:void 0},{kind:"field",decorators:[i()],key:"_repository",value:void 0},{kind:"field",decorators:[i()],key:"_downloadRepositoryData",value:()=>({beta:!1,version:""})},{kind:"method",key:"shouldUpdate",value:function(o){return o.forEach(((o,s)=>{"hass"===s&&(this.sidebarDocked='"docked"'===window.localStorage.getItem("dockedSidebar")),"repositories"===s&&(this._repository=this._getRepository(this.hacs.repositories,this.repository))})),o.has("sidebarDocked")||o.has("narrow")||o.has("active")||o.has("_toggle")||o.has("_error")||o.has("_repository")||o.has("_downloadRepositoryData")||o.has("_installing")}},{kind:"field",key:"_getRepository",value:()=>e(((o,s)=>null==o?void 0:o.find((o=>o.id===s))))},{kind:"field",key:"_getInstallPath",value:()=>e((o=>{let s=o.local_path;return"theme"===o.category&&(s=`${s}/${o.file_name}`),s}))},{kind:"method",key:"firstUpdated",value:async function(){var o,s;if(this._repository=this._getRepository(this.hacs.repositories,this.repository),null===(o=this._repository)||void 0===o||!o.updated_info){await a(this.hass,this._repository.id);const o=await r(this.hass);this.dispatchEvent(new CustomEvent("update-hacs",{detail:{repositories:o},bubbles:!0,composed:!0})),this._repository=this._getRepository(o,this.repository)}this._toggle=!1,l(this.hass,(o=>this._error=o),h.ERROR),this._downloadRepositoryData.beta=this._repository.beta,this._downloadRepositoryData.version="version"===(null===(s=this._repository)||void 0===s?void 0:s.version_or_commit)?this._repository.releases[0]:""}},{kind:"method",key:"render",value:function(){var o;if(!this.active||!this._repository)return n``;const s=this._getInstallPath(this._repository),t=[{name:"beta",selector:{boolean:{}}},{name:"version",selector:{select:{options:"version"===this._repository.version_or_commit?this._repository.releases.concat("hacs/integration"===this._repository.full_name||this._repository.hide_default_branch?[]:[this._repository.default_branch]):[],mode:"dropdown"}}}];return n` - -
- ${"version"===this._repository.version_or_commit?n` - "beta"===o.name?this.hacs.localize("dialog_download.show_beta"):this.hacs.localize("dialog_download.select_version")} - @value-changed=${this._valueChanged} - > - - `:""} - ${this._repository.can_install?"":n` - ${this.hacs.localize("confirm.home_assistant_version_not_correct",{haversion:this.hass.config.version,minversion:this._repository.homeassistant})} - `} -
- ${this.hacs.localize("dialog_download.note_downloaded",{location:n`'${s}'`})} - ${"plugin"===this._repository.category&&"storage"!==this.hacs.status.lovelace_mode?n` -

${this.hacs.localize("dialog_download.lovelace_instruction")}

-
-                url: ${c({repository:this._repository,skipTag:!0})}
-                type: module
-                
- `:""} - ${"integration"===this._repository.category?n`

${this.hacs.localize("dialog_download.restart")}

`:""} -
- ${null!==(o=this._error)&&void 0!==o&&o.message?n` - ${this._error.message} - `:""} -
- - ${this._installing?n``:this.hacs.localize("common.download")} - - - ${this.hacs.localize("common.repository")} - -
- `}},{kind:"method",key:"_valueChanged",value:async function(o){let s=!1;if(this._downloadRepositoryData.beta!==o.detail.value.beta&&(s=!0,this._toggle=!0,await d(this.hass,this.repository)),o.detail.value.version&&(s=!0,this._toggle=!0,await p(this.hass,this.repository,o.detail.value.version)),s){const o=await r(this.hass);this.dispatchEvent(new CustomEvent("update-hacs",{detail:{repositories:o},bubbles:!0,composed:!0})),this._repository=this._getRepository(o,this.repository),this._toggle=!1}this._downloadRepositoryData=o.detail.value}},{kind:"method",key:"_installRepository",value:async function(){var o;if(this._installing=!0,!this._repository)return;const s=this._downloadRepositoryData.version||this._repository.available_version||this._repository.default_branch;"commit"!==(null===(o=this._repository)||void 0===o?void 0:o.version_or_commit)?await _(this.hass,this._repository.id,s):await y(this.hass,this._repository.id),this.hacs.log.debug(this._repository.category,"_installRepository"),this.hacs.log.debug(this.hacs.status.lovelace_mode,"_installRepository"),"plugin"===this._repository.category&&"storage"===this.hacs.status.lovelace_mode&&await w(this.hass,this._repository,s),this._installing=!1,this.dispatchEvent(new Event("hacs-secondary-dialog-closed",{bubbles:!0,composed:!0})),this.dispatchEvent(new Event("hacs-dialog-closed",{bubbles:!0,composed:!0})),"plugin"===this._repository.category&&b(this,{title:this.hacs.localize("common.reload"),text:n`${this.hacs.localize("dialog.reload.description")}
${this.hacs.localize("dialog.reload.confirm")}`,dismissText:this.hacs.localize("common.cancel"),confirmText:this.hacs.localize("common.reload"),confirm:()=>{m.location.href=m.location.href}})}},{kind:"get",static:!0,key:"styles",value:function(){return[u,v` - .note { - margin-top: 12px; - } - .lovelace { - margin-top: 8px; - } - pre { - white-space: pre-line; - user-select: all; - } - `]}}]}}),s);export{k as HacsDonwloadDialog}; diff --git a/custom_components/hacs/hacs_frontend/c.04294c77.js.gz b/custom_components/hacs/hacs_frontend/c.04294c77.js.gz deleted file mode 100644 index 3b026877..00000000 Binary files a/custom_components/hacs/hacs_frontend/c.04294c77.js.gz and /dev/null differ diff --git a/custom_components/hacs/hacs_frontend/c.3c21dfe4.js b/custom_components/hacs/hacs_frontend/c.063631e8.js similarity index 66% rename from custom_components/hacs/hacs_frontend/c.3c21dfe4.js rename to custom_components/hacs/hacs_frontend/c.063631e8.js index 5e9f9180..fd04d550 100644 --- a/custom_components/hacs/hacs_frontend/c.3c21dfe4.js +++ b/custom_components/hacs/hacs_frontend/c.063631e8.js @@ -1,4 +1,4 @@ -import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q as d,S as c,x as u,P as h,b3 as v,a as p,h as m,e as f,i as g,I as _,J as y,$ as k,z as b,ac as x,ad as $,n as w,b4 as C,aV as A,b5 as I,b6 as E,b7 as S,b8 as z,b9 as L,ba as O,bb as T,bc as P,bd as M,be as F,bf as B,aO as D,bg as N,bh as V,bi as R,bj as j,bk as q,bl as U,bm as H,bn as G,bo as W,bp as K,bq as Y,aB as Z,br as Q,bs as X,bt as J,bu as ee,bv as te,ag as ie,bw as ae,bx as ne,by as oe,bz as se,bA as re,bB as le,bC as de,bD as ce,bE as ue,bF as he,bG as ve,bH as pe,bI as me,bJ as fe,bK as ge,bL as _e,bM as ye,bN as ke,bO as be,bP as xe,bQ as $e,bR as we,bS as Ce,bT as Ae,bU as Ie,E as Ee,bV as Se,bW as ze,bX as Le,bY as Oe,bZ as Te,b_ as Pe,b$ as Me,c0 as Fe,c1 as Be,c2 as De,c3 as Ne,c4 as Ve,c5 as Re,c6 as je,c7 as qe,c8 as Ue,c9 as He,ca as Ge,cb as We,cc as Ke,cd as Ye,ce as Ze,cf as Qe,cg as Xe,ch as Je,ci as et,cj as tt,ck as it,cl as at,cm as nt,cn as ot,co as st,cp as rt,cq as lt,cr as dt,cs as ct,ct as ut,cu as ht,cv as vt,cw as pt,cx as mt,cy as ft,cz as gt,cA as _t,cB as yt,cC as kt,cD as bt,cE as xt,cF as $t,cG as wt,aM as Ct,cH as At,cI as It,cJ as Et,cK as St,cL as zt,cM as Lt,cN as Ot,cO as Tt,cP as Pt,cQ as Mt,cR as Ft,cS as Bt,cT as Dt,cU as Nt,cV as Vt,cW as Rt,cX as jt,cY as qt,cZ as Ut,c_ as Ht,c$ as Gt,d0 as Wt,d1 as Kt,d2 as Yt,d3 as Zt,d4 as Qt,d5 as Xt,d6 as Jt,d7 as ei,d8 as ti,d9 as ii,da as ai,db as ni,dc as oi,dd as si,de as ri,df as li,dg as di,dh as ci,di as ui,dj as hi,dk as vi,dl as pi,dm as mi,dn as fi,dp as gi,dq as _i,dr as yi,ds as ki,dt as bi,du as xi,dv as $i,dw as wi,dx as Ci,dy as Ai,dz as Ii,dA as Ei,dB as Si,dC as zi,dD as Li,dE as Oi,dF as Ti,dG as Pi,dH as Mi,dI as Fi,dJ as Bi,t as Di,O as Ni,j as Vi,m as Ri,Z as ji,aQ as qi,dK as Ui,dL as Hi,dM as Gi,aR as Wi,dN as Ki,_ as Yi,o as Zi,dO as Qi,dP as Xi,dQ as Ji,dR as ea,dS as ta,dT as ia,dU as aa,dV as na,dW as oa,dX as sa,dY as ra,dZ as la,d_ as da,d$ as ca,e0 as ua,aZ as ha,e1 as va,e2 as pa,U as ma,e3 as fa,e4 as ga,e5 as _a,e6 as ya,e7 as ka,e8 as ba,e9 as xa,ea as $a,G as wa,af as Ca}from"./main-c805434e.js";import{d as Aa,a as Ia}from"./c.7e9628d7.js";import{i as Ea}from"./c.b39f7e4d.js";import"./c.fa497e12.js";import"./c.6b338b4b.js";import"./c.249923af.js";import{s as Sa,a as za,b as La}from"./c.791b7770.js";import{T as Oa,d as Ta,e as Pa}from"./c.ff857a48.js";import{b as Ma,e as Fa}from"./c.be11274c.js";import{c as Ba,u as Da}from"./c.743a15a1.js";import"./c.ee356d91.js";import{g as Na}from"./c.eb245438.js";import{c as Va}from"./c.1fc70989.js";import{a as Ra}from"./c.9175c851.js";import"./c.28b63723.js";let ja=!1,qa=[],Ua=[];function Ha(){ja=!0,requestAnimationFrame((function(){ja=!1,function(e){for(;e.length;)Ga(e.shift())}(qa),setTimeout((function(){!function(e){for(let t=0,i=e.length;t{throw e}))}}function Wa(e){if(!e||"object"!=typeof e)return e;if("[object Date]"==Object.prototype.toString.call(e))return new Date(e.getTime());if(Array.isArray(e))return e.map(Wa);var t={};return Object.keys(e).forEach((function(i){t[i]=Wa(e[i])})),t}const Ka=(e,t)=>et?1:0,Ya=(e,t)=>Ka(e.toLowerCase(),t.toLowerCase());class Za extends TypeError{constructor(e,t){let i;const{message:a,...n}=e,{path:o}=e;super(0===o.length?a:"At path: "+o.join(".")+" -- "+a),this.value=void 0,this.key=void 0,this.type=void 0,this.refinement=void 0,this.path=void 0,this.branch=void 0,this.failures=void 0,Object.assign(this,n),this.name=this.constructor.name,this.failures=()=>{var a;return null!=(a=i)?a:i=[e,...t()]}}}function Qa(e){return"object"==typeof e&&null!=e}function Xa(e){return"string"==typeof e?JSON.stringify(e):""+e}function Ja(e,t,i,a){if(!0===e)return;!1===e?e={}:"string"==typeof e&&(e={message:e});const{path:n,branch:o}=t,{type:s}=i,{refinement:r,message:l="Expected a value of type `"+s+"`"+(r?" with refinement `"+r+"`":"")+", but received: `"+Xa(a)+"`"}=e;return{value:a,type:s,refinement:r,key:n[n.length-1],path:n,branch:o,...e,message:l}}function*en(e,t,i,a){(function(e){return Qa(e)&&"function"==typeof e[Symbol.iterator]})(e)||(e=[e]);for(const n of e){const e=Ja(n,t,i,a);e&&(yield e)}}function*tn(e,t,i={}){const{path:a=[],branch:n=[e],coerce:o=!1,mask:s=!1}=i,r={path:a,branch:n};if(o&&(e=t.coercer(e,r),s&&"type"!==t.type&&Qa(t.schema)&&Qa(e)&&!Array.isArray(e)))for(const i in e)void 0===t.schema[i]&&delete e[i];let l=!0;for(const i of t.validator(e,r))l=!1,yield[i,void 0];for(let[i,d,c]of t.entries(e,r)){const t=tn(d,c,{path:void 0===i?a:[...a,i],branch:void 0===i?n:[...n,d],coerce:o,mask:s});for(const a of t)a[0]?(l=!1,yield[a[0],void 0]):o&&(d=a[1],void 0===i?e=d:e instanceof Map?e.set(i,d):e instanceof Set?e.add(d):Qa(e)&&(e[i]=d))}if(l)for(const i of t.refiner(e,r))l=!1,yield[i,void 0];l&&(yield[void 0,e])}class an{constructor(e){this.TYPE=void 0,this.type=void 0,this.schema=void 0,this.coercer=void 0,this.validator=void 0,this.refiner=void 0,this.entries=void 0;const{type:t,schema:i,validator:a,refiner:n,coercer:o=(e=>e),entries:s=function*(){}}=e;this.type=t,this.schema=i,this.entries=s,this.coercer=o,this.validator=a?(e,t)=>en(a(e,t),t,this,e):()=>[],this.refiner=n?(e,t)=>en(n(e,t),t,this,e):()=>[]}assert(e){return nn(e,this)}create(e){return function(e,t){const i=sn(e,t,{coerce:!0});if(i[0])throw i[0];return i[1]}(e,this)}is(e){return on(e,this)}mask(e){return function(e,t){const i=sn(e,t,{coerce:!0,mask:!0});if(i[0])throw i[0];return i[1]}(e,this)}validate(e,t={}){return sn(e,this,t)}}function nn(e,t){const i=sn(e,t);if(i[0])throw i[0]}function on(e,t){return!sn(e,t)[0]}function sn(e,t,i={}){const a=tn(e,t,i),n=function(e){const{done:t,value:i}=e.next();return t?void 0:i}(a);if(n[0]){const e=new Za(n[0],(function*(){for(const e of a)e[0]&&(yield e[0])}));return[e,void 0]}return[void 0,n[1]]}function rn(...e){const t="type"===e[0].type,i=e.map((e=>e.schema)),a=Object.assign({},...i);return t?function(e){const t=Object.keys(e);return new an({type:"type",schema:e,*entries(i){if(Qa(i))for(const a of t)yield[a,i[a],e[a]]},validator:e=>Qa(e)||"Expected an object, but received: "+Xa(e)})}(a):pn(a)}function ln(e,t){return new an({type:e,schema:null,validator:t})}function dn(){return ln("any",(()=>!0))}function cn(e){return new an({type:"array",schema:e,*entries(t){if(e&&Array.isArray(t))for(const[i,a]of t.entries())yield[i,a,e]},coercer:e=>Array.isArray(e)?e.slice():e,validator:e=>Array.isArray(e)||"Expected an array value, but received: "+Xa(e)})}function un(){return ln("boolean",(e=>"boolean"==typeof e))}function hn(e){const t=Xa(e),i=typeof e;return new an({type:"literal",schema:"string"===i||"number"===i||"boolean"===i?e:null,validator:i=>i===e||"Expected the literal `"+t+"`, but received: "+Xa(i)})}function vn(){return ln("number",(e=>"number"==typeof e&&!isNaN(e)||"Expected a number, but received: "+Xa(e)))}function pn(e){const t=e?Object.keys(e):[],i=ln("never",(()=>!1));return new an({type:"object",schema:e||null,*entries(a){if(e&&Qa(a)){const n=new Set(Object.keys(a));for(const i of t)n.delete(i),yield[i,a[i],e[i]];for(const e of n)yield[e,a[e],i]}},validator:e=>Qa(e)||"Expected an object, but received: "+Xa(e),coercer:e=>Qa(e)?{...e}:e})}function mn(e){return new an({...e,validator:(t,i)=>void 0===t||e.validator(t,i),refiner:(t,i)=>void 0===t||e.refiner(t,i)})}function fn(){return ln("string",(e=>"string"==typeof e||"Expected a string, but received: "+Xa(e)))}function gn(e){const t=e.map((e=>e.type)).join(" | ");return new an({type:"union",schema:null,coercer(t,i){const a=e.find((e=>{const[i]=e.validate(t,{coerce:!0});return!i}))||ln("unknown",(()=>!0));return a.coercer(t,i)},validator(i,a){const n=[];for(const t of e){const[...e]=tn(i,t,a),[o]=e;if(!o[0])return[];for(const[t]of e)t&&n.push(t)}return["Expected the value to satisfy a union of `"+t+"`, but received: "+Xa(i),...n]}})}const _n=(e,t)=>{if(!(t instanceof Za))return{warnings:[t.message],errors:void 0};const i=[],a=[];for(const n of t.failures())if(void 0===n.value)i.push(e.localize("ui.errors.config.key_missing","key",n.path.join(".")));else if("never"===n.type)a.push(e.localize("ui.errors.config.key_not_expected","key",n.path.join(".")));else{if("union"===n.type)continue;"enums"===n.type?a.push(e.localize("ui.errors.config.key_wrong_type","key",n.path.join("."),"type_correct",n.message.replace("Expected ","").split(", ")[0],"type_wrong",JSON.stringify(n.value))):a.push(e.localize("ui.errors.config.key_wrong_type","key",n.path.join("."),"type_correct",n.refinement||n.type,"type_wrong",JSON.stringify(n.value)))}return{warnings:a,errors:i}},yn=(e,t)=>e.callWS({type:"validate_config",...t}),kn=e=>e.substr(e.indexOf(".")+1),bn=pn({alias:mn(fn()),enabled:mn(un())}),xn=pn({entity_id:mn(gn([fn(),cn(fn())])),device_id:mn(gn([fn(),cn(fn())])),area_id:mn(gn([fn(),cn(fn())]))});rn(bn,pn({service:mn(fn()),service_template:mn(fn()),entity_id:mn(fn()),target:mn(xn),data:mn(pn())}));const $n=rn(bn,pn({service:hn("media_player.play_media"),target:mn(pn({entity_id:mn(fn())})),entity_id:mn(fn()),data:pn({media_content_id:fn(),media_content_type:fn()}),metadata:pn()})),wn=rn(bn,pn({service:hn("scene.turn_on"),target:mn(pn({entity_id:mn(fn())})),entity_id:mn(fn()),metadata:pn()})),Cn=(t,i)=>e(t,"hass-notification",i),An=e=>e.substr(0,e.indexOf(".")),In=e=>{return t=e.entity_id,void 0===(i=e.attributes).friendly_name?kn(t).replace(/_/g," "):i.friendly_name||"";var t,i};class En extends HTMLElement{static get version(){return"23.0.10"}}customElements.define("vaadin-material-styles",En);const Sn=e=>class extends e{static get properties(){return{theme:{type:String,readOnly:!0}}}attributeChangedCallback(e,t,i){super.attributeChangedCallback(e,t,i),"theme"===e&&this._setTheme(i)}},zn=[];function Ln(e,i,a={}){var n;e&&(n=e,Fn(customElements.get(n))&&console.warn(`The custom element definition for "${e}"\n was finalized before a style module was registered.\n Make sure to add component specific style modules before\n importing the corresponding custom element.`)),i=function(e=[]){return[e].flat(1/0).filter((e=>e instanceof t||(console.warn("An item in styles is not of type CSSResult. Use `unsafeCSS` or `css`."),!1)))}(i),window.Vaadin&&window.Vaadin.styleModules?window.Vaadin.styleModules.registerStyles(e,i,a):zn.push({themeFor:e,styles:i,include:a.include,moduleId:a.moduleId})}function On(){return window.Vaadin&&window.Vaadin.styleModules?window.Vaadin.styleModules.getAllThemes():zn}function Tn(e=""){let t=0;return 0===e.indexOf("lumo-")||0===e.indexOf("material-")?t=1:0===e.indexOf("vaadin-")&&(t=2),t}function Pn(e){const t=[];return e.include&&[].concat(e.include).forEach((e=>{const i=On().find((t=>t.moduleId===e));i?t.push(...Pn(i),...i.styles):console.warn(`Included moduleId ${e} not found in style registry`)}),e.styles),t}function Mn(e){const t=e+"-default-theme",i=On().filter((i=>i.moduleId!==t&&function(e,t){return(e||"").split(" ").some((e=>new RegExp("^"+e.split("*").join(".*")+"$").test(t)))}(i.themeFor,e))).map((e=>({...e,styles:[...Pn(e),...e.styles],includePriority:Tn(e.moduleId)}))).sort(((e,t)=>t.includePriority-e.includePriority));return i.length>0?i:On().filter((e=>e.moduleId===t))}function Fn(e){return e&&Object.prototype.hasOwnProperty.call(e,"__themes")}const Bn=e=>class extends(Sn(e)){static finalize(){super.finalize();const e=this.prototype._template;e&&!Fn(this)&&function(e,t){const i=document.createElement("style");i.innerHTML=e.map((e=>e.cssText)).join("\n"),t.content.appendChild(i)}(this.getStylesForThis(),e)}static finalizeStyles(e){const t=this.getStylesForThis();return e?[e,...t]:t}static getStylesForThis(){const e=Object.getPrototypeOf(this.prototype),t=(e?e.constructor.__themes:[])||[];this.__themes=[...t,...Mn(this.is)];const i=this.__themes.flatMap((e=>e.styles));return i.filter(((e,t)=>t===i.lastIndexOf(e)))}};Ln("",i` +import{A as e,aY as t,r as i,aZ as a,aa as n,a7 as o,a_ as s,a$ as r,a9 as l,S as d,T as c,x as u,Q as h,b0 as p,a as v,h as m,e as f,i as g,L as _,N as y,$ as k,z as b,ac as x,ad as $,n as w,b1 as C,aQ as A,b2 as I,b3 as E,b4 as z,b5 as S,b6 as L,b7 as T,b8 as O,b9 as P,ba as M,bb as F,bc as D,aB as B,bd as N,be as V,bf as j,bg as q,bh as R,bi as U,bj as H,bk as G,bl as W,bm as K,bn as Y,aS as Z,bo as Q,bp as X,bq as J,br as ee,bs as te,ag as ie,bt as ae,bu as ne,bv as oe,bw as se,bx as re,by as le,bz as de,bA as ce,bB as ue,bC as he,bD as pe,bE as ve,bF as me,bG as fe,bH as ge,bI as _e,bJ as ye,bK as ke,bL as be,bM as xe,bN as $e,bO as we,bP as Ce,bQ as Ae,bR as Ie,E as Ee,bS as ze,bT as Se,bU as Le,bV as Te,bW as Oe,bX as Pe,bY as Me,bZ as Fe,b_ as De,b$ as Be,c0 as Ne,c1 as Ve,c2 as je,c3 as qe,c4 as Re,c5 as Ue,c6 as He,c7 as Ge,c8 as We,c9 as Ke,ca as Ye,cb as Ze,cc as Qe,cd as Xe,ce as Je,cf as et,cg as tt,ch as it,ci as at,cj as nt,ck as ot,cl as st,cm as rt,cn as lt,co as dt,cp as ct,cq as ut,cr as ht,cs as pt,ct as vt,cu as mt,cv as ft,cw as gt,cx as _t,cy as yt,cz as kt,cA as bt,cB as xt,cC as $t,cD as wt,az as Ct,cE as At,cF as It,cG as Et,cH as zt,cI as St,cJ as Lt,cK as Tt,cL as Ot,cM as Pt,cN as Mt,cO as Ft,cP as Dt,cQ as Bt,cR as Nt,cS as Vt,cT as jt,cU as qt,cV as Rt,cW as Ut,cX as Ht,cY as Gt,cZ as Wt,c_ as Kt,c$ as Yt,d0 as Zt,d1 as Qt,d2 as Xt,d3 as Jt,d4 as ei,d5 as ti,d6 as ii,d7 as ai,d8 as ni,d9 as oi,da as si,db as ri,dc as li,dd as di,de as ci,df as ui,dg as hi,dh as pi,di as vi,dj as mi,dk as fi,dl as gi,dm as _i,dn as yi,dp as ki,dq as bi,dr as xi,ds as $i,dt as wi,du as Ci,dv as Ai,dw as Ii,dx as Ei,dy as zi,dz as Si,dA as Li,dB as Ti,dC as Oi,dD as Pi,dE as Mi,dF as Fi,dG as Di,dH as Bi,dI as Ni,t as Vi,I as ji,j as qi,m as Ri,Z as Ui,aD as Hi,dJ as Gi,dK as Wi,dL as Ki,aM as Yi,dM as Zi,_ as Qi,o as Xi,dN as Ji,dO as ea,dP as ta,dQ as ia,dR as aa,dS as na,dT as oa,dU as sa,dV as ra,dW as la,dX as da,dY as ca,dZ as ua,d_ as ha,d$ as pa,aK as va,e0 as ma,e1 as fa,V as ga,e2 as _a,e3 as ya,e4 as ka,e5 as ba,e6 as xa,e7 as $a,e8 as wa,e9 as Ca,J as Aa,af as Ia}from"./main-7bc9a818.js";import{d as Ea,a as za}from"./c.67735e63.js";import"./c.cf66b923.js";import"./c.5d9598b2.js";import"./c.fb76e5d5.js";import"./c.9475214f.js";import{s as Sa,a as La,b as Ta}from"./c.38b86040.js";import{T as Oa,a as Pa,s as Ma}from"./c.2aa297ae.js";import{b as Fa,e as Da}from"./c.d9dcade0.js";import{d as Ba}from"./c.01f18260.js";import{i as Na}from"./c.21c042d4.js";import{c as Va,u as ja}from"./c.743a15a1.js";import"./c.5ec2d281.js";import{g as qa}from"./c.6711bf6c.js";import"./c.e0e56ec4.js";import{a as Ra}from"./c.487362b0.js";import"./c.9a1f96ed.js";let Ua=!1,Ha=[],Ga=[];function Wa(){Ua=!0,requestAnimationFrame((function(){Ua=!1,function(e){for(;e.length;)Ka(e.shift())}(Ha),setTimeout((function(){!function(e){for(let t=0,i=e.length;t{throw e}))}}function Ya(e){if(!e||"object"!=typeof e)return e;if("[object Date]"==Object.prototype.toString.call(e))return new Date(e.getTime());if(Array.isArray(e))return e.map(Ya);var t={};return Object.keys(e).forEach((function(i){t[i]=Ya(e[i])})),t}const Za=(e,t)=>et?1:0,Qa=(e,t)=>Za(e.toLowerCase(),t.toLowerCase());class Xa extends TypeError{constructor(e,t){let i;const{message:a,...n}=e,{path:o}=e;super(0===o.length?a:"At path: "+o.join(".")+" -- "+a),this.value=void 0,this.key=void 0,this.type=void 0,this.refinement=void 0,this.path=void 0,this.branch=void 0,this.failures=void 0,Object.assign(this,n),this.name=this.constructor.name,this.failures=()=>{var a;return null!=(a=i)?a:i=[e,...t()]}}}function Ja(e){return"object"==typeof e&&null!=e}function en(e){return"string"==typeof e?JSON.stringify(e):""+e}function tn(e,t,i,a){if(!0===e)return;!1===e?e={}:"string"==typeof e&&(e={message:e});const{path:n,branch:o}=t,{type:s}=i,{refinement:r,message:l="Expected a value of type `"+s+"`"+(r?" with refinement `"+r+"`":"")+", but received: `"+en(a)+"`"}=e;return{value:a,type:s,refinement:r,key:n[n.length-1],path:n,branch:o,...e,message:l}}function*an(e,t,i,a){(function(e){return Ja(e)&&"function"==typeof e[Symbol.iterator]})(e)||(e=[e]);for(const n of e){const e=tn(n,t,i,a);e&&(yield e)}}function*nn(e,t,i){void 0===i&&(i={});const{path:a=[],branch:n=[e],coerce:o=!1,mask:s=!1}=i,r={path:a,branch:n};if(o&&(e=t.coercer(e,r),s&&"type"!==t.type&&Ja(t.schema)&&Ja(e)&&!Array.isArray(e)))for(const i in e)void 0===t.schema[i]&&delete e[i];let l=!0;for(const i of t.validator(e,r))l=!1,yield[i,void 0];for(let[i,d,c]of t.entries(e,r)){const t=nn(d,c,{path:void 0===i?a:[...a,i],branch:void 0===i?n:[...n,d],coerce:o,mask:s});for(const a of t)a[0]?(l=!1,yield[a[0],void 0]):o&&(d=a[1],void 0===i?e=d:e instanceof Map?e.set(i,d):e instanceof Set?e.add(d):Ja(e)&&(e[i]=d))}if(l)for(const i of t.refiner(e,r))l=!1,yield[i,void 0];l&&(yield[void 0,e])}class on{constructor(e){this.TYPE=void 0,this.type=void 0,this.schema=void 0,this.coercer=void 0,this.validator=void 0,this.refiner=void 0,this.entries=void 0;const{type:t,schema:i,validator:a,refiner:n,coercer:o=(e=>e),entries:s=function*(){}}=e;this.type=t,this.schema=i,this.entries=s,this.coercer=o,this.validator=a?(e,t)=>an(a(e,t),t,this,e):()=>[],this.refiner=n?(e,t)=>an(n(e,t),t,this,e):()=>[]}assert(e){return sn(e,this)}create(e){return function(e,t){const i=ln(e,t,{coerce:!0});if(i[0])throw i[0];return i[1]}(e,this)}is(e){return rn(e,this)}mask(e){return function(e,t){const i=ln(e,t,{coerce:!0,mask:!0});if(i[0])throw i[0];return i[1]}(e,this)}validate(e,t){return void 0===t&&(t={}),ln(e,this,t)}}function sn(e,t){const i=ln(e,t);if(i[0])throw i[0]}function rn(e,t){return!ln(e,t)[0]}function ln(e,t,i){void 0===i&&(i={});const a=nn(e,t,i),n=function(e){const{done:t,value:i}=e.next();return t?void 0:i}(a);if(n[0]){const e=new Xa(n[0],(function*(){for(const e of a)e[0]&&(yield e[0])}));return[e,void 0]}return[void 0,n[1]]}function dn(){for(var e=arguments.length,t=new Array(e),i=0;ie.schema)),o=Object.assign({},...n);return a?yn(o):fn(o)}function cn(e,t){return new on({type:e,schema:null,validator:t})}function un(){return cn("any",(()=>!0))}function hn(e){return new on({type:"array",schema:e,*entries(t){if(e&&Array.isArray(t))for(const[i,a]of t.entries())yield[i,a,e]},coercer:e=>Array.isArray(e)?e.slice():e,validator:e=>Array.isArray(e)||"Expected an array value, but received: "+en(e)})}function pn(){return cn("boolean",(e=>"boolean"==typeof e))}function vn(e){const t=en(e),i=typeof e;return new on({type:"literal",schema:"string"===i||"number"===i||"boolean"===i?e:null,validator:i=>i===e||"Expected the literal `"+t+"`, but received: "+en(i)})}function mn(){return cn("number",(e=>"number"==typeof e&&!isNaN(e)||"Expected a number, but received: "+en(e)))}function fn(e){const t=e?Object.keys(e):[],i=cn("never",(()=>!1));return new on({type:"object",schema:e||null,*entries(a){if(e&&Ja(a)){const n=new Set(Object.keys(a));for(const i of t)n.delete(i),yield[i,a[i],e[i]];for(const e of n)yield[e,a[e],i]}},validator:e=>Ja(e)||"Expected an object, but received: "+en(e),coercer:e=>Ja(e)?{...e}:e})}function gn(e){return new on({...e,validator:(t,i)=>void 0===t||e.validator(t,i),refiner:(t,i)=>void 0===t||e.refiner(t,i)})}function _n(){return cn("string",(e=>"string"==typeof e||"Expected a string, but received: "+en(e)))}function yn(e){const t=Object.keys(e);return new on({type:"type",schema:e,*entries(i){if(Ja(i))for(const a of t)yield[a,i[a],e[a]]},validator:e=>Ja(e)||"Expected an object, but received: "+en(e)})}function kn(e){const t=e.map((e=>e.type)).join(" | ");return new on({type:"union",schema:null,coercer(t,i){const a=e.find((e=>{const[i]=e.validate(t,{coerce:!0});return!i}))||cn("unknown",(()=>!0));return a.coercer(t,i)},validator(i,a){const n=[];for(const t of e){const[...e]=nn(i,t,a),[o]=e;if(!o[0])return[];for(const[t]of e)t&&n.push(t)}return["Expected the value to satisfy a union of `"+t+"`, but received: "+en(i),...n]}})}const bn=(e,t)=>{if(!(t instanceof Xa))return{warnings:[t.message],errors:void 0};const i=[],a=[];for(const n of t.failures())if(void 0===n.value)i.push(e.localize("ui.errors.config.key_missing","key",n.path.join(".")));else if("never"===n.type)a.push(e.localize("ui.errors.config.key_not_expected","key",n.path.join(".")));else{if("union"===n.type)continue;"enums"===n.type?a.push(e.localize("ui.errors.config.key_wrong_type","key",n.path.join("."),"type_correct",n.message.replace("Expected ","").split(", ")[0],"type_wrong",JSON.stringify(n.value))):a.push(e.localize("ui.errors.config.key_wrong_type","key",n.path.join("."),"type_correct",n.refinement||n.type,"type_wrong",JSON.stringify(n.value)))}return{warnings:a,errors:i}},xn=(e,t)=>e.callWS({type:"validate_config",...t}),$n=e=>e.substr(e.indexOf(".")+1),wn=fn({alias:gn(_n()),enabled:gn(pn())}),Cn=fn({entity_id:gn(kn([_n(),hn(_n())])),device_id:gn(kn([_n(),hn(_n())])),area_id:gn(kn([_n(),hn(_n())]))});dn(wn,fn({service:gn(_n()),service_template:gn(_n()),entity_id:gn(_n()),target:gn(Cn),data:gn(fn())}));const An=dn(wn,fn({service:vn("media_player.play_media"),target:gn(fn({entity_id:gn(_n())})),entity_id:gn(_n()),data:fn({media_content_id:_n(),media_content_type:_n()}),metadata:fn()})),In=dn(wn,fn({service:vn("scene.turn_on"),target:gn(fn({entity_id:gn(_n())})),entity_id:gn(_n()),metadata:fn()})),En=(t,i)=>e(t,"hass-notification",i),zn=e=>e.substr(0,e.indexOf(".")),Sn=e=>{return t=e.entity_id,void 0===(i=e.attributes).friendly_name?$n(t).replace(/_/g," "):i.friendly_name||"";var t,i};class Ln extends HTMLElement{static get version(){return"23.1.3"}}customElements.define("vaadin-material-styles",Ln);const Tn=e=>class extends e{static get properties(){return{theme:{type:String,reflectToAttribute:!0,observer:"__deprecatedThemePropertyChanged"},_theme:{type:String,readOnly:!0}}}__deprecatedThemePropertyChanged(e){this._set_theme(e)}},On=[];function Pn(e,i,a={}){var n;e&&(n=e,Nn(customElements.get(n))&&console.warn(`The custom element definition for "${e}"\n was finalized before a style module was registered.\n Make sure to add component specific style modules before\n importing the corresponding custom element.`)),i=function(e=[]){return[e].flat(1/0).filter((e=>e instanceof t||(console.warn("An item in styles is not of type CSSResult. Use `unsafeCSS` or `css`."),!1)))}(i),window.Vaadin&&window.Vaadin.styleModules?window.Vaadin.styleModules.registerStyles(e,i,a):On.push({themeFor:e,styles:i,include:a.include,moduleId:a.moduleId})}function Mn(){return window.Vaadin&&window.Vaadin.styleModules?window.Vaadin.styleModules.getAllThemes():On}function Fn(e=""){let t=0;return 0===e.indexOf("lumo-")||0===e.indexOf("material-")?t=1:0===e.indexOf("vaadin-")&&(t=2),t}function Dn(e){const t=[];return e.include&&[].concat(e.include).forEach((e=>{const i=Mn().find((t=>t.moduleId===e));i?t.push(...Dn(i),...i.styles):console.warn(`Included moduleId ${e} not found in style registry`)}),e.styles),t}function Bn(e){const t=`${e}-default-theme`,i=Mn().filter((i=>i.moduleId!==t&&function(e,t){return(e||"").split(" ").some((e=>new RegExp(`^${e.split("*").join(".*")}$`).test(t)))}(i.themeFor,e))).map((e=>({...e,styles:[...Dn(e),...e.styles],includePriority:Fn(e.moduleId)}))).sort(((e,t)=>t.includePriority-e.includePriority));return i.length>0?i:Mn().filter((e=>e.moduleId===t))}function Nn(e){return e&&Object.prototype.hasOwnProperty.call(e,"__themes")}const Vn=e=>class extends(Tn(e)){static finalize(){if(super.finalize(),this.elementStyles)return;const e=this.prototype._template;e&&!Nn(this)&&function(e,t){const i=document.createElement("style");i.innerHTML=e.map((e=>e.cssText)).join("\n"),t.content.appendChild(i)}(this.getStylesForThis(),e)}static finalizeStyles(e){const t=this.getStylesForThis();return e?[...super.finalizeStyles(e),...t]:t}static getStylesForThis(){const e=Object.getPrototypeOf(this.prototype),t=(e?e.constructor.__themes:[])||[];this.__themes=[...t,...Bn(this.is)];const i=this.__themes.flatMap((e=>e.styles));return i.filter(((e,t)=>t===i.lastIndexOf(e)))}};Pn("",i` :host { /* Text colors */ --material-body-text-color: var(--light-theme-text-color, rgba(0, 0, 0, 0.87)); @@ -92,7 +92,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a a { color: inherit; } -`,{moduleId:"material-color-light"});Ln("",i` +`,{moduleId:"material-color-light"});Pn("",i` :host { /* Text colors */ --material-body-text-color: var(--dark-theme-text-color, rgba(255, 255, 255, 1)); @@ -137,7 +137,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a background-color: var(--material-background-color); color: var(--material-body-text-color); } -`,{moduleId:"material-color-dark"});const Dn=i` +`,{moduleId:"material-color-dark"});const jn=i` :host { /* Text colors */ --material-body-text-color: var(--light-theme-text-color, rgba(0, 0, 0, 0.87)); @@ -161,7 +161,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a /* Divider colors */ --material-divider-color: rgba(0, 0, 0, 0.12); } -`,Nn=document.createElement("template");Nn.innerHTML=``,document.head.appendChild(Nn.content);const Vn=i` +`,qn=document.createElement("template");qn.innerHTML=``,document.head.appendChild(qn.content);const Rn=i` :host { /* Font family */ --material-font-family: 'Roboto', sans-serif; @@ -181,7 +181,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a /* Icon size */ --material-icon-font-size: 20px; } -`;Ln("",i` +`;Pn("",i` body, :host { font-family: var(--material-font-family); @@ -254,7 +254,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a strong { font-weight: 500; } -`,{moduleId:"material-typography"});const Rn=document.createElement("template");if(Rn.innerHTML=``,document.head.appendChild(Rn.content),!window.polymerSkipLoadingFontRoboto){const e="https://fonts.googleapis.com/css?family=Roboto+Mono:400,700|Roboto:400,300,300italic,400italic,500,500italic,700,700italic",t=document.createElement("link");t.rel="stylesheet",t.type="text/css",t.crossOrigin="anonymous",t.href=e,document.head.appendChild(t)}const jn=i` +`,{moduleId:"material-typography"});const Un=document.createElement("template");if(Un.innerHTML=``,document.head.appendChild(Un.content),!window.polymerSkipLoadingFontRoboto){const e="https://fonts.googleapis.com/css?family=Roboto+Mono:400,700|Roboto:400,300,300italic,400italic,500,500italic,700,700italic",t=document.createElement("link");t.rel="stylesheet",t.type="text/css",t.crossOrigin="anonymous",t.href=e,document.head.appendChild(t)}const Hn=i` /* prettier-ignore */ :host { /* from http://codepen.io/shyndman/pen/c5394ddf2e8b2a5c9185904b57421cdb */ @@ -267,7 +267,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a --material-shadow-elevation-16dp: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.4); --material-shadow-elevation-24dp: 0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px -7px rgba(0, 0, 0, 0.4); } -`,qn=document.createElement("template");qn.innerHTML=``,document.head.appendChild(qn.content);const Un=i` +`,Gn=document.createElement("template");Gn.innerHTML=``,document.head.appendChild(Gn.content);const Wn=i` :host { top: 16px; right: 16px; @@ -301,7 +301,79 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a opacity: 0; } } -`;Ln("",Un,{moduleId:"material-overlay"});const Hn=Un;Ln("",Hn,{moduleId:"material-menu-overlay"});Ln("vaadin-combo-box-overlay",[Hn,i` +`;Pn("",Wn,{moduleId:"material-overlay"}),Pn("vaadin-overlay",Wn,{moduleId:"material-vaadin-overlay"});const Kn=e=>e.test(navigator.userAgent),Yn=e=>e.test(navigator.platform);Kn(/Android/),Kn(/Chrome/)&&/Google Inc/.test(navigator.vendor),Kn(/Firefox/);const Zn=Yn(/^iPad/)||Yn(/^Mac/)&&navigator.maxTouchPoints>1,Qn=Yn(/^iPhone/)||Zn,Xn=Kn(/^((?!chrome|android).)*safari/i),Jn=(()=>{try{return document.createEvent("TouchEvent"),!0}catch(e){return!1}})(),eo=a((e=>class extends e{constructor(){super(),this.__controllers=new Set}connectedCallback(){super.connectedCallback(),this.__controllers.forEach((e=>{e.hostConnected&&e.hostConnected()}))}disconnectedCallback(){super.disconnectedCallback(),this.__controllers.forEach((e=>{e.hostDisconnected&&e.hostDisconnected()}))}addController(e){this.__controllers.add(e),void 0!==this.$&&this.isConnected&&e.hostConnected&&e.hostConnected()}removeController(e){this.__controllers.delete(e)}}));class to{static detectScrollType(){const e=document.createElement("div");e.textContent="ABCD",e.dir="rtl",e.style.fontSize="14px",e.style.width="4px",e.style.height="1px",e.style.position="absolute",e.style.top="-1000px",e.style.overflow="scroll",document.body.appendChild(e);let t="reverse";return e.scrollLeft>0?t="default":(e.scrollLeft=2,e.scrollLeft<2&&(t="negative")),document.body.removeChild(e),t}static getNormalizedScrollLeft(e,t,i){const{scrollLeft:a}=i;if("rtl"!==t||!e)return a;switch(e){case"negative":return i.scrollWidth-i.clientWidth+a;case"reverse":return i.scrollWidth-i.clientWidth-a;default:return a}}static setNormalizedScrollLeft(e,t,i,a){if("rtl"===t&&e)switch(e){case"negative":i.scrollLeft=i.clientWidth-i.scrollWidth+a;break;case"reverse":i.scrollLeft=i.scrollWidth-i.clientWidth-a;break;default:i.scrollLeft=a}else i.scrollLeft=a}}const io=[];let ao;function no(e,t,i=e.getAttribute("dir")){t?e.setAttribute("dir",t):null!=i&&e.removeAttribute("dir")}function oo(){return document.documentElement.getAttribute("dir")}new MutationObserver((function(){const e=oo();io.forEach((t=>{no(t,e)}))})).observe(document.documentElement,{attributes:!0,attributeFilter:["dir"]});const so=e=>class extends e{static get properties(){return{dir:{type:String,value:"",reflectToAttribute:!0,converter:{fromAttribute:e=>e||"",toAttribute:e=>""===e?null:e}}}}static finalize(){super.finalize(),ao||(ao=to.detectScrollType())}connectedCallback(){super.connectedCallback(),this.hasAttribute("dir")||(this.__subscribe(),no(this,oo(),null))}attributeChangedCallback(e,t,i){if(super.attributeChangedCallback(e,t,i),"dir"!==e)return;const a=oo(),n=i===a&&-1===io.indexOf(this),o=!i&&t&&-1===io.indexOf(this),s=i!==a&&t===a;n||o?(this.__subscribe(),no(this,a,i)):s&&this.__subscribe(!1)}disconnectedCallback(){super.disconnectedCallback(),this.__subscribe(!1),this.removeAttribute("dir")}_valueToNodeAttribute(e,t,i){("dir"!==i||""!==t||e.hasAttribute("dir"))&&super._valueToNodeAttribute(e,t,i)}_attributeToProperty(e,t,i){"dir"!==e||t?super._attributeToProperty(e,t,i):this.dir=""}__subscribe(e=!0){e?io.includes(this)||io.push(this):io.includes(this)&&io.splice(io.indexOf(this),1)}__getNormalizedScrollLeft(e){return to.getNormalizedScrollLeft(ao,this.getAttribute("dir")||"ltr",e)}__setNormalizedScrollLeft(e,t){return to.setNormalizedScrollLeft(ao,this.getAttribute("dir")||"ltr",e,t)}};function ro(e,t){const i=Math.max(e.tabIndex,0),a=Math.max(t.tabIndex,0);return 0===i||0===a?a>i:i>a}function lo(e){const t=e.length;if(t<2)return e;const i=Math.ceil(t/2);return function(e,t){const i=[];for(;e.length>0&&t.length>0;)ro(e[0],t[0])?i.push(t.shift()):i.push(e.shift());return i.concat(e,t)}(lo(e.slice(0,i)),lo(e.slice(i)))}function co(e,t){if(e.nodeType!==Node.ELEMENT_NODE||function(e){const t=e.style;if("hidden"===t.visibility||"none"===t.display)return!0;const i=window.getComputedStyle(e);return"hidden"===i.visibility||"none"===i.display}(e))return!1;const i=e,a=function(e){if(!function(e){return!e.matches('[tabindex="-1"]')&&(e.matches("input, select, textarea, button, object")?e.matches(":not([disabled])"):e.matches("a[href], area[href], iframe, [tabindex], [contentEditable]"))}(e))return-1;const t=e.getAttribute("tabindex")||0;return Number(t)}(i);let n=a>0;a>=0&&t.push(i);let o=[];return o="slot"===i.localName?i.assignedNodes({flatten:!0}):(i.shadowRoot||i).children,[...o].forEach((e=>{n=co(e,t)||n})),n}function uo(e){return e.getRootNode().activeElement===e}const ho=[];class po{constructor(e){this.host=e,this.__trapNode=null,this.__onKeyDown=this.__onKeyDown.bind(this)}hostConnected(){document.addEventListener("keydown",this.__onKeyDown)}hostDisconnected(){document.removeEventListener("keydown",this.__onKeyDown)}trapFocus(e){if(this.__trapNode=e,0===this.__focusableElements.length)throw this.__trapNode=null,new Error("The trap node should have at least one focusable descendant or be focusable itself.");ho.push(this),-1===this.__focusedElementIndex&&this.__focusableElements[0].focus()}releaseFocus(){this.__trapNode=null,ho.pop()}__onKeyDown(e){if(this.__trapNode&&this===Array.from(ho).pop()&&"Tab"===e.key){e.preventDefault();const t=e.shiftKey;this.__focusNextElement(t)}}__focusNextElement(e=!1){const t=this.__focusableElements,i=e?-1:1,a=this.__focusedElementIndex;t[(t.length+a+i)%t.length].focus()}get __focusableElements(){return function(e){const t=[];return co(e,t)?lo(t):t}(this.__trapNode)}get __focusedElementIndex(){const e=this.__focusableElements;return e.indexOf(e.filter(uo).pop())}}class vo extends(Vn(so(eo(n)))){static get template(){return o` + + +
+
+
+ +
+
+ `}static get is(){return"vaadin-overlay"}static get properties(){return{opened:{type:Boolean,notify:!0,observer:"_openedChanged",reflectToAttribute:!0},owner:Element,renderer:Function,template:{type:Object,notify:!0},content:{type:Object,notify:!0},withBackdrop:{type:Boolean,value:!1,reflectToAttribute:!0},model:Object,modeless:{type:Boolean,value:!1,reflectToAttribute:!0,observer:"_modelessChanged"},hidden:{type:Boolean,reflectToAttribute:!0,observer:"_hiddenChanged"},focusTrap:{type:Boolean,value:!1},restoreFocusOnClose:{type:Boolean,value:!1},restoreFocusNode:{type:HTMLElement},_mouseDownInside:{type:Boolean},_mouseUpInside:{type:Boolean},_instance:{type:Object},_originalContentPart:Object,_contentNodes:Array,_oldOwner:Element,_oldModel:Object,_oldTemplate:Object,_oldRenderer:Object,_oldOpened:Boolean}}static get observers(){return["_templateOrRendererChanged(template, renderer, owner, model, opened)"]}constructor(){super(),this._boundMouseDownListener=this._mouseDownListener.bind(this),this._boundMouseUpListener=this._mouseUpListener.bind(this),this._boundOutsideClickListener=this._outsideClickListener.bind(this),this._boundKeydownListener=this._keydownListener.bind(this),this._observer=new s(this,(e=>{this._setTemplateFromNodes(e.addedNodes)})),this._boundIronOverlayCanceledListener=this._ironOverlayCanceled.bind(this),Qn&&(this._boundIosResizeListener=()=>this._detectIosNavbar()),this.__focusTrapController=new po(this)}ready(){super.ready(),this._observer.flush(),this.addEventListener("click",(()=>{})),this.$.backdrop.addEventListener("click",(()=>{})),this.addController(this.__focusTrapController)}_detectIosNavbar(){if(!this.opened)return;const e=window.innerHeight,t=window.innerWidth>e,i=document.documentElement.clientHeight;t&&i>e?this.style.setProperty("--vaadin-overlay-viewport-bottom",i-e+"px"):this.style.setProperty("--vaadin-overlay-viewport-bottom","0")}_setTemplateFromNodes(e){this.template=e.filter((e=>e.localName&&"template"===e.localName))[0]||this.template}close(e){const t=new CustomEvent("vaadin-overlay-close",{bubbles:!0,cancelable:!0,detail:{sourceEvent:e}});this.dispatchEvent(t),t.defaultPrevented||(this.opened=!1)}connectedCallback(){super.connectedCallback(),this._boundIosResizeListener&&(this._detectIosNavbar(),window.addEventListener("resize",this._boundIosResizeListener))}disconnectedCallback(){super.disconnectedCallback(),this._boundIosResizeListener&&window.removeEventListener("resize",this._boundIosResizeListener)}requestContentUpdate(){this.renderer&&this.renderer.call(this.owner,this.content,this.owner,this.model)}_ironOverlayCanceled(e){e.preventDefault()}_mouseDownListener(e){this._mouseDownInside=e.composedPath().indexOf(this.$.overlay)>=0}_mouseUpListener(e){this._mouseUpInside=e.composedPath().indexOf(this.$.overlay)>=0}_outsideClickListener(e){if(e.composedPath().includes(this.$.overlay)||this._mouseDownInside||this._mouseUpInside)return this._mouseDownInside=!1,void(this._mouseUpInside=!1);if(!this._last)return;const t=new CustomEvent("vaadin-overlay-outside-click",{bubbles:!0,cancelable:!0,detail:{sourceEvent:e}});this.dispatchEvent(t),this.opened&&!t.defaultPrevented&&this.close(e)}_keydownListener(e){if(this._last&&(!this.modeless||e.composedPath().includes(this.$.overlay))&&"Escape"===e.key){const t=new CustomEvent("vaadin-overlay-escape-press",{bubbles:!0,cancelable:!0,detail:{sourceEvent:e}});this.dispatchEvent(t),this.opened&&!t.defaultPrevented&&this.close(e)}}_ensureTemplatized(){this._setTemplateFromNodes(Array.from(this.children))}_openedChanged(e,t){var i,a,n;this._instance||this._ensureTemplatized(),e?(this.__restoreFocusNode=this._getActiveElement(),this._animatedOpening(),i=this,a=()=>{this.focusTrap&&this.__focusTrapController.trapFocus(this.$.overlay);const e=new CustomEvent("vaadin-overlay-open",{bubbles:!0});this.dispatchEvent(e)},Ua||Wa(),Ga.push([i,a,n]),document.addEventListener("keydown",this._boundKeydownListener),this.modeless||this._addGlobalListeners()):t&&(this.focusTrap&&this.__focusTrapController.releaseFocus(),this._animatedClosing(),document.removeEventListener("keydown",this._boundKeydownListener),this.modeless||this._removeGlobalListeners())}_hiddenChanged(e){e&&this.hasAttribute("closing")&&this._flushAnimation("closing")}_shouldAnimate(){const e=getComputedStyle(this).getPropertyValue("animation-name");return!("none"===getComputedStyle(this).getPropertyValue("display"))&&e&&"none"!==e}_enqueueAnimation(e,t){const i=`__${e}Handler`,a=e=>{e&&e.target!==this||(t(),this.removeEventListener("animationend",a),delete this[i])};this[i]=a,this.addEventListener("animationend",a)}_flushAnimation(e){const t=`__${e}Handler`;"function"==typeof this[t]&&this[t]()}_animatedOpening(){this.parentNode===document.body&&this.hasAttribute("closing")&&this._flushAnimation("closing"),this._attachOverlay(),this.modeless||this._enterModalState(),this.setAttribute("opening",""),this._shouldAnimate()?this._enqueueAnimation("opening",(()=>{this._finishOpening()})):this._finishOpening()}_attachOverlay(){this._placeholder=document.createComment("vaadin-overlay-placeholder"),this.parentNode.insertBefore(this._placeholder,this),document.body.appendChild(this),this.bringToFront()}_finishOpening(){document.addEventListener("iron-overlay-canceled",this._boundIronOverlayCanceledListener),this.removeAttribute("opening")}_finishClosing(){document.removeEventListener("iron-overlay-canceled",this._boundIronOverlayCanceledListener),this._detachOverlay(),this.$.overlay.style.removeProperty("pointer-events"),this.removeAttribute("closing")}_animatedClosing(){if(this.hasAttribute("opening")&&this._flushAnimation("opening"),this._placeholder){this._exitModalState();const e=this.restoreFocusNode||this.__restoreFocusNode;if(this.restoreFocusOnClose&&e){const t=this._getActiveElement();(t===document.body||this._deepContains(t))&&setTimeout((()=>e.focus())),this.__restoreFocusNode=null}this.setAttribute("closing",""),this.dispatchEvent(new CustomEvent("vaadin-overlay-closing")),this._shouldAnimate()?this._enqueueAnimation("closing",(()=>{this._finishClosing()})):this._finishClosing()}}_detachOverlay(){this._placeholder.parentNode.insertBefore(this,this._placeholder),this._placeholder.parentNode.removeChild(this._placeholder)}static get __attachedInstances(){return Array.from(document.body.children).filter((e=>e instanceof vo&&!e.hasAttribute("closing"))).sort(((e,t)=>e.__zIndex-t.__zIndex||0))}get _last(){return this===vo.__attachedInstances.pop()}_modelessChanged(e){e?(this._removeGlobalListeners(),this._exitModalState()):this.opened&&(this._addGlobalListeners(),this._enterModalState())}_addGlobalListeners(){document.addEventListener("mousedown",this._boundMouseDownListener),document.addEventListener("mouseup",this._boundMouseUpListener),document.documentElement.addEventListener("click",this._boundOutsideClickListener,!0)}_enterModalState(){"none"!==document.body.style.pointerEvents&&(this._previousDocumentPointerEvents=document.body.style.pointerEvents,document.body.style.pointerEvents="none"),vo.__attachedInstances.forEach((e=>{e!==this&&(e.shadowRoot.querySelector('[part="overlay"]').style.pointerEvents="none")}))}_removeGlobalListeners(){document.removeEventListener("mousedown",this._boundMouseDownListener),document.removeEventListener("mouseup",this._boundMouseUpListener),document.documentElement.removeEventListener("click",this._boundOutsideClickListener,!0)}_exitModalState(){void 0!==this._previousDocumentPointerEvents&&(document.body.style.pointerEvents=this._previousDocumentPointerEvents,delete this._previousDocumentPointerEvents);const e=vo.__attachedInstances;let t;for(;(t=e.pop())&&(t===this||(t.shadowRoot.querySelector('[part="overlay"]').style.removeProperty("pointer-events"),t.modeless)););}_removeOldContent(){this.content&&this._contentNodes&&(this._observer.disconnect(),this._contentNodes.forEach((e=>{e.parentNode===this.content&&this.content.removeChild(e)})),this._originalContentPart&&(this.$.content.parentNode.replaceChild(this._originalContentPart,this.$.content),this.$.content=this._originalContentPart,this._originalContentPart=void 0),this._observer.connect(),this._contentNodes=void 0,this.content=void 0)}_stampOverlayTemplate(e){this._removeOldContent(),e._Templatizer||(e._Templatizer=r(e,this,{forwardHostProp(e,t){this._instance&&this._instance.forwardHostProp(e,t)}})),this._instance=new e._Templatizer({}),this._contentNodes=Array.from(this._instance.root.childNodes);const t=e._templateRoot||(e._templateRoot=e.getRootNode());if(t!==document){this.$.content.shadowRoot||this.$.content.attachShadow({mode:"open"});let e=Array.from(t.querySelectorAll("style")).reduce(((e,t)=>e+t.textContent),"");if(e=e.replace(/:host/g,":host-nomatch"),e){const t=document.createElement("style");t.textContent=e,this.$.content.shadowRoot.appendChild(t),this._contentNodes.unshift(t)}this.$.content.shadowRoot.appendChild(this._instance.root),this.content=this.$.content.shadowRoot}else this.appendChild(this._instance.root),this.content=this}_removeNewRendererOrTemplate(e,t,i,a){e!==t?this.template=void 0:i!==a&&(this.renderer=void 0)}_templateOrRendererChanged(e,t,i,a,n){if(e&&t)throw this._removeNewRendererOrTemplate(e,this._oldTemplate,t,this._oldRenderer),new Error("You should only use either a renderer or a template for overlay content");const o=this._oldOwner!==i||this._oldModel!==a;this._oldModel=a,this._oldOwner=i;const s=this._oldTemplate!==e;this._oldTemplate=e;const r=this._oldRenderer!==t;this._oldRenderer=t;const l=this._oldOpened!==n;this._oldOpened=n,r&&(this.content=this,this.content.innerHTML="",delete this.content._$litPart$),e&&s?this._stampOverlayTemplate(e):t&&(r||l||o)&&n&&this.requestContentUpdate()}_getActiveElement(){let e=document.activeElement||document.body;for(;e.shadowRoot&&e.shadowRoot.activeElement;)e=e.shadowRoot.activeElement;return e}_deepContains(e){if(this.contains(e))return!0;let t=e;const i=e.ownerDocument;for(;t&&t!==i&&t!==this;)t=t.parentNode||t.host;return t===this}bringToFront(){let e="";const t=vo.__attachedInstances.filter((e=>e!==this)).pop();if(t){e=t.__zIndex+1}this.style.zIndex=e,this.__zIndex=e||parseFloat(getComputedStyle(this).zIndex)}}customElements.define(vo.is,vo);const mo=Wn;Pn("",mo,{moduleId:"material-menu-overlay"});Pn("vaadin-combo-box-overlay",[mo,i` :host { --_vaadin-combo-box-items-container-border-width: 8px 0; --_vaadin-combo-box-items-container-border-style: solid; @@ -413,7 +485,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a animation: 3s linear infinite material-combo-box-loader-progress-rtl, 0.3s 0.1s both material-combo-box-loader-fade-in; } -`],{moduleId:"material-combo-box-overlay"});const Gn=document.createElement("template");Gn.innerHTML='\n \n',document.head.appendChild(Gn.content);const Wn=i` +`],{moduleId:"material-combo-box-overlay"});const fo=document.createElement("template");fo.innerHTML='\n \n',document.head.appendChild(fo.content);const go=i` :host { display: flex; align-items: center; @@ -476,14 +548,14 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a margin-right: 0; margin-left: 10px; } -`;Ln("vaadin-item",Wn,{moduleId:"material-item"});Ln("vaadin-combo-box-item",[Wn,i` +`;Pn("vaadin-item",go,{moduleId:"material-item"});Pn("vaadin-combo-box-item",[go,i` :host { cursor: pointer; -webkit-tap-highlight-color: transparent; padding: 4px 10px; --_material-item-selected-icon-display: block; } -`],{moduleId:"material-combo-box-item"});class Kn{static detectScrollType(){const e=document.createElement("div");e.textContent="ABCD",e.dir="rtl",e.style.fontSize="14px",e.style.width="4px",e.style.height="1px",e.style.position="absolute",e.style.top="-1000px",e.style.overflow="scroll",document.body.appendChild(e);let t="reverse";return e.scrollLeft>0?t="default":(e.scrollLeft=2,e.scrollLeft<2&&(t="negative")),document.body.removeChild(e),t}static getNormalizedScrollLeft(e,t,i){const{scrollLeft:a}=i;if("rtl"!==t||!e)return a;switch(e){case"negative":return i.scrollWidth-i.clientWidth+a;case"reverse":return i.scrollWidth-i.clientWidth-a;default:return a}}static setNormalizedScrollLeft(e,t,i,a){if("rtl"===t&&e)switch(e){case"negative":i.scrollLeft=i.clientWidth-i.scrollWidth+a;break;case"reverse":i.scrollLeft=i.scrollWidth-i.clientWidth-a;break;default:i.scrollLeft=a}else i.scrollLeft=a}}const Yn=[];let Zn;function Qn(e,t,i=e.getAttribute("dir")){t?e.setAttribute("dir",t):null!=i&&e.removeAttribute("dir")}function Xn(){return document.documentElement.getAttribute("dir")}new MutationObserver((function(){const e=Xn();Yn.forEach((t=>{Qn(t,e)}))})).observe(document.documentElement,{attributes:!0,attributeFilter:["dir"]});const Jn=e=>class extends e{static get properties(){return{dir:{type:String,value:"",reflectToAttribute:!0}}}static finalize(){super.finalize(),Zn||(Zn=Kn.detectScrollType())}connectedCallback(){super.connectedCallback(),this.hasAttribute("dir")||(this.__subscribe(),Qn(this,Xn(),null))}attributeChangedCallback(e,t,i){if(super.attributeChangedCallback(e,t,i),"dir"!==e)return;const a=Xn(),n=i===a&&-1===Yn.indexOf(this),o=!i&&t&&-1===Yn.indexOf(this),s=i!==a&&t===a;n||o?(this.__subscribe(),Qn(this,a,i)):s&&this.__subscribe(!1)}disconnectedCallback(){super.disconnectedCallback(),this.__subscribe(!1),this.removeAttribute("dir")}_valueToNodeAttribute(e,t,i){("dir"!==i||""!==t||e.hasAttribute("dir"))&&super._valueToNodeAttribute(e,t,i)}_attributeToProperty(e,t,i){"dir"!==e||t?super._attributeToProperty(e,t,i):this.dir=""}__subscribe(e=!0){e?-1===Yn.indexOf(this)&&Yn.push(this):Yn.indexOf(this)>-1&&Yn.splice(Yn.indexOf(this),1)}__getNormalizedScrollLeft(e){return Kn.getNormalizedScrollLeft(Zn,this.getAttribute("dir")||"ltr",e)}__setNormalizedScrollLeft(e,t){return Kn.setNormalizedScrollLeft(Zn,this.getAttribute("dir")||"ltr",e,t)}};class eo extends(Bn(Jn(a))){static get template(){return n` +`],{moduleId:"material-combo-box-item"});class _o extends(Vn(so(n))){static get template(){return o` - -
-
-
- -
-
- `}static get is(){return"vaadin-overlay"}static get properties(){return{opened:{type:Boolean,notify:!0,observer:"_openedChanged",reflectToAttribute:!0},owner:Element,renderer:Function,template:{type:Object,notify:!0},content:{type:Object,notify:!0},withBackdrop:{type:Boolean,value:!1,reflectToAttribute:!0},model:Object,modeless:{type:Boolean,value:!1,reflectToAttribute:!0,observer:"_modelessChanged"},hidden:{type:Boolean,reflectToAttribute:!0,observer:"_hiddenChanged"},focusTrap:{type:Boolean,value:!1},restoreFocusOnClose:{type:Boolean,value:!1},restoreFocusNode:{type:HTMLElement},_mouseDownInside:{type:Boolean},_mouseUpInside:{type:Boolean},_instance:{type:Object},_originalContentPart:Object,_contentNodes:Array,_oldOwner:Element,_oldModel:Object,_oldTemplate:Object,_oldRenderer:Object,_oldOpened:Boolean}}static get observers(){return["_templateOrRendererChanged(template, renderer, owner, model, opened)"]}constructor(){super(),this._boundMouseDownListener=this._mouseDownListener.bind(this),this._boundMouseUpListener=this._mouseUpListener.bind(this),this._boundOutsideClickListener=this._outsideClickListener.bind(this),this._boundKeydownListener=this._keydownListener.bind(this),this._observer=new s(this,(e=>{this._setTemplateFromNodes(e.addedNodes)})),this._boundIronOverlayCanceledListener=this._ironOverlayCanceled.bind(this),no&&(this._boundIosResizeListener=()=>this._detectIosNavbar()),this.__focusTrapController=new po(this)}ready(){super.ready(),this._observer.flush(),this.addEventListener("click",(()=>{})),this.$.backdrop.addEventListener("click",(()=>{})),this.addController(this.__focusTrapController)}_detectIosNavbar(){if(!this.opened)return;const e=window.innerHeight,t=window.innerWidth>e,i=document.documentElement.clientHeight;t&&i>e?this.style.setProperty("--vaadin-overlay-viewport-bottom",i-e+"px"):this.style.setProperty("--vaadin-overlay-viewport-bottom","0")}_setTemplateFromNodes(e){this.template=e.filter((e=>e.localName&&"template"===e.localName))[0]||this.template}close(e){var t=new CustomEvent("vaadin-overlay-close",{bubbles:!0,cancelable:!0,detail:{sourceEvent:e}});this.dispatchEvent(t),t.defaultPrevented||(this.opened=!1)}connectedCallback(){super.connectedCallback(),this._boundIosResizeListener&&(this._detectIosNavbar(),window.addEventListener("resize",this._boundIosResizeListener))}disconnectedCallback(){super.disconnectedCallback(),this._boundIosResizeListener&&window.removeEventListener("resize",this._boundIosResizeListener)}requestContentUpdate(){this.renderer&&this.renderer.call(this.owner,this.content,this.owner,this.model)}_ironOverlayCanceled(e){e.preventDefault()}_mouseDownListener(e){this._mouseDownInside=e.composedPath().indexOf(this.$.overlay)>=0}_mouseUpListener(e){this._mouseUpInside=e.composedPath().indexOf(this.$.overlay)>=0}_outsideClickListener(e){if(-1!==e.composedPath().indexOf(this.$.overlay)||this._mouseDownInside||this._mouseUpInside)return this._mouseDownInside=!1,void(this._mouseUpInside=!1);if(!this._last)return;const t=new CustomEvent("vaadin-overlay-outside-click",{bubbles:!0,cancelable:!0,detail:{sourceEvent:e}});this.dispatchEvent(t),this.opened&&!t.defaultPrevented&&this.close(e)}_keydownListener(e){if(this._last&&"Escape"===e.key){const t=new CustomEvent("vaadin-overlay-escape-press",{bubbles:!0,cancelable:!0,detail:{sourceEvent:e}});this.dispatchEvent(t),this.opened&&!t.defaultPrevented&&this.close(e)}}_ensureTemplatized(){this._setTemplateFromNodes(Array.from(this.children))}_openedChanged(e,t){var i,a,n;this._instance||this._ensureTemplatized(),e?(this.__restoreFocusNode=this._getActiveElement(),this._animatedOpening(),i=this,a=()=>{this.focusTrap&&this.__focusTrapController.trapFocus(this.$.overlay);const e=new CustomEvent("vaadin-overlay-open",{bubbles:!0});this.dispatchEvent(e)},ja||Ha(),Ua.push([i,a,n]),this.modeless||this._addGlobalListeners()):t&&(this.__focusTrapController.releaseFocus(),this._animatedClosing(),this.modeless||this._removeGlobalListeners())}_hiddenChanged(e){e&&this.hasAttribute("closing")&&this._flushAnimation("closing")}_shouldAnimate(){const e=getComputedStyle(this).getPropertyValue("animation-name");return!("none"===getComputedStyle(this).getPropertyValue("display"))&&e&&"none"!=e}_enqueueAnimation(e,t){const i=`__${e}Handler`,a=e=>{e&&e.target!==this||(t(),this.removeEventListener("animationend",a),delete this[i])};this[i]=a,this.addEventListener("animationend",a)}_flushAnimation(e){const t=`__${e}Handler`;"function"==typeof this[t]&&this[t]()}_animatedOpening(){this.parentNode===document.body&&this.hasAttribute("closing")&&this._flushAnimation("closing"),this._attachOverlay(),this.modeless||this._enterModalState(),this.setAttribute("opening",""),this._shouldAnimate()?this._enqueueAnimation("opening",(()=>{this._finishOpening()})):this._finishOpening()}_attachOverlay(){this._placeholder=document.createComment("vaadin-overlay-placeholder"),this.parentNode.insertBefore(this._placeholder,this),document.body.appendChild(this),this.bringToFront()}_finishOpening(){document.addEventListener("iron-overlay-canceled",this._boundIronOverlayCanceledListener),this.removeAttribute("opening")}_finishClosing(){document.removeEventListener("iron-overlay-canceled",this._boundIronOverlayCanceledListener),this._detachOverlay(),this.$.overlay.style.removeProperty("pointer-events"),this.removeAttribute("closing")}_animatedClosing(){if(this.hasAttribute("opening")&&this._flushAnimation("opening"),this._placeholder){this._exitModalState();const e=this.restoreFocusNode||this.__restoreFocusNode;if(this.restoreFocusOnClose&&e){const t=this._getActiveElement();(t===document.body||this._deepContains(t))&&setTimeout((()=>e.focus())),this.__restoreFocusNode=null}this.setAttribute("closing",""),this.dispatchEvent(new CustomEvent("vaadin-overlay-closing")),this._shouldAnimate()?this._enqueueAnimation("closing",(()=>{this._finishClosing()})):this._finishClosing()}}_detachOverlay(){this._placeholder.parentNode.insertBefore(this,this._placeholder),this._placeholder.parentNode.removeChild(this._placeholder)}static get __attachedInstances(){return Array.from(document.body.children).filter((e=>e instanceof mo&&!e.hasAttribute("closing"))).sort(((e,t)=>e.__zIndex-t.__zIndex||0))}get _last(){return this===mo.__attachedInstances.pop()}_modelessChanged(e){e?(this._removeGlobalListeners(),this._exitModalState()):this.opened&&(this._addGlobalListeners(),this._enterModalState())}_addGlobalListeners(){document.addEventListener("mousedown",this._boundMouseDownListener),document.addEventListener("mouseup",this._boundMouseUpListener),document.documentElement.addEventListener("click",this._boundOutsideClickListener,!0),document.addEventListener("keydown",this._boundKeydownListener)}_enterModalState(){"none"!==document.body.style.pointerEvents&&(this._previousDocumentPointerEvents=document.body.style.pointerEvents,document.body.style.pointerEvents="none"),mo.__attachedInstances.forEach((e=>{e!==this&&(e.shadowRoot.querySelector('[part="overlay"]').style.pointerEvents="none")}))}_removeGlobalListeners(){document.removeEventListener("mousedown",this._boundMouseDownListener),document.removeEventListener("mouseup",this._boundMouseUpListener),document.documentElement.removeEventListener("click",this._boundOutsideClickListener,!0),document.removeEventListener("keydown",this._boundKeydownListener)}_exitModalState(){void 0!==this._previousDocumentPointerEvents&&(document.body.style.pointerEvents=this._previousDocumentPointerEvents,delete this._previousDocumentPointerEvents);const e=mo.__attachedInstances;let t;for(;(t=e.pop())&&(t===this||(t.shadowRoot.querySelector('[part="overlay"]').style.removeProperty("pointer-events"),t.modeless)););}_removeOldContent(){this.content&&this._contentNodes&&(this._observer.disconnect(),this._contentNodes.forEach((e=>{e.parentNode===this.content&&this.content.removeChild(e)})),this._originalContentPart&&(this.$.content.parentNode.replaceChild(this._originalContentPart,this.$.content),this.$.content=this._originalContentPart,this._originalContentPart=void 0),this._observer.connect(),this._contentNodes=void 0,this.content=void 0)}_stampOverlayTemplate(e){this._removeOldContent(),e._Templatizer||(e._Templatizer=r(e,this,{forwardHostProp:function(e,t){this._instance&&this._instance.forwardHostProp(e,t)}})),this._instance=new e._Templatizer({}),this._contentNodes=Array.from(this._instance.root.childNodes);const t=e._templateRoot||(e._templateRoot=e.getRootNode());if(t!==document){this.$.content.shadowRoot||this.$.content.attachShadow({mode:"open"});let e=Array.from(t.querySelectorAll("style")).reduce(((e,t)=>e+t.textContent),"");if(e=e.replace(/:host/g,":host-nomatch"),e){const t=document.createElement("style");t.textContent=e,this.$.content.shadowRoot.appendChild(t),this._contentNodes.unshift(t)}this.$.content.shadowRoot.appendChild(this._instance.root),this.content=this.$.content.shadowRoot}else this.appendChild(this._instance.root),this.content=this}_removeNewRendererOrTemplate(e,t,i,a){e!==t?this.template=void 0:i!==a&&(this.renderer=void 0)}_templateOrRendererChanged(e,t,i,a,n){if(e&&t)throw this._removeNewRendererOrTemplate(e,this._oldTemplate,t,this._oldRenderer),new Error("You should only use either a renderer or a template for overlay content");const o=this._oldOwner!==i||this._oldModel!==a;this._oldModel=a,this._oldOwner=i;const s=this._oldTemplate!==e;this._oldTemplate=e;const r=this._oldRenderer!==t;this._oldRenderer=t;const l=this._oldOpened!==n;this._oldOpened=n,r&&(this.content=this,this.content.innerHTML="",delete this.content._$litPart$),e&&s?this._stampOverlayTemplate(e):t&&(r||l||o)&&n&&this.requestContentUpdate()}_getActiveElement(){let e=document.activeElement||document.body;for(;e.shadowRoot&&e.shadowRoot.activeElement;)e=e.shadowRoot.activeElement;return e}_deepContains(e){if(this.contains(e))return!0;let t=e;const i=e.ownerDocument;for(;t&&t!==i&&t!==this;)t=t.parentNode||t.host;return t===this}bringToFront(){let e="";const t=mo.__attachedInstances.filter((e=>e!==this)).pop();if(t){e=t.__zIndex+1}this.style.zIndex=e,this.__zIndex=e||parseFloat(getComputedStyle(this).zIndex)}}customElements.define(mo.is,mo);const fo={start:"top",end:"bottom"},go={start:"left",end:"right"},_o=e=>class extends e{static get properties(){return{positionTarget:{type:Object,value:null},horizontalAlign:{type:String,value:"start"},verticalAlign:{type:String,value:"top"},noHorizontalOverlap:{type:Boolean,value:!1},noVerticalOverlap:{type:Boolean,value:!1}}}static get observers(){return["__positionSettingsChanged(positionTarget, horizontalAlign, verticalAlign, noHorizontalOverlap, noVerticalOverlap)","__overlayOpenedChanged(opened)"]}constructor(){super(),this.__boundUpdatePosition=this._updatePosition.bind(this)}__overlayOpenedChanged(e){if(["scroll","resize"].forEach((t=>{e?window.addEventListener(t,this.__boundUpdatePosition):window.removeEventListener(t,this.__boundUpdatePosition)})),e){const e=getComputedStyle(this);this.__margins||(this.__margins={},["top","bottom","left","right"].forEach((t=>{this.__margins[t]=parseInt(e[t],10)}))),this.setAttribute("dir",e.direction),this._updatePosition(),requestAnimationFrame((()=>this._updatePosition()))}}get __isRTL(){return"rtl"===this.getAttribute("dir")}__positionSettingsChanged(){this._updatePosition()}_updatePosition(){if(!this.positionTarget||!this.opened)return;const e=this.positionTarget.getBoundingClientRect(),t=this.__shouldAlignStartVertically(e);this.style.justifyContent=t?"flex-start":"flex-end";const i=this.__shouldAlignStartHorizontally(e,this.__isRTL),a=!this.__isRTL&&i||this.__isRTL&&!i;this.style.alignItems=a?"flex-start":"flex-end";const n=this.getBoundingClientRect(),o=this.__calculatePositionInOneDimension(e,n,this.noVerticalOverlap,fo,this,t),s=this.__calculatePositionInOneDimension(e,n,this.noHorizontalOverlap,go,this,i);Object.assign(this.style,o,s),this.toggleAttribute("bottom-aligned",!t),this.toggleAttribute("top-aligned",t),this.toggleAttribute("end-aligned",!a),this.toggleAttribute("start-aligned",a)}__shouldAlignStartHorizontally(e,t){const i=Math.max(this.__oldContentWidth||0,this.$.overlay.offsetWidth);this.__oldContentWidth=this.$.overlay.offsetWidth;const a=Math.min(window.innerWidth,document.documentElement.clientWidth),n=!t&&"start"===this.horizontalAlign||t&&"end"===this.horizontalAlign;return this.__shouldAlignStart(e,i,a,this.__margins,n,this.noHorizontalOverlap,go)}__shouldAlignStartVertically(e){const t=Math.max(this.__oldContentHeight||0,this.$.overlay.offsetHeight);this.__oldContentHeight=this.$.overlay.offsetHeight;const i=Math.min(window.innerHeight,document.documentElement.clientHeight),a="top"===this.verticalAlign;return this.__shouldAlignStart(e,t,i,this.__margins,a,this.noVerticalOverlap,fo)}__shouldAlignStart(e,t,i,a,n,o,s){const r=i-e[o?s.end:s.start]-a[s.end],l=e[o?s.start:s.end]-a[s.start],d=n?r:l;return n===(d>(n?l:r)||d>t)}__calculatePositionInOneDimension(e,t,i,a,n,o){const s=o?a.start:a.end,r=o?a.end:a.start;return{[s]:parseFloat(n.style[s]||getComputedStyle(n)[s])+(t[o?a.start:a.end]-e[i===o?a.end:a.start])*(o?-1:1)+"px",[r]:""}}};let yo;Ln("vaadin-combo-box-overlay",i` + `}static get is(){return"vaadin-combo-box-item"}static get properties(){return{index:Number,item:Object,label:String,selected:{type:Boolean,value:!1,reflectToAttribute:!0},focused:{type:Boolean,value:!1,reflectToAttribute:!0},renderer:Function,_oldRenderer:Function}}static get observers(){return["__rendererOrItemChanged(renderer, index, item.*, selected, focused)","__updateLabel(label, renderer)"]}connectedCallback(){super.connectedCallback(),this._comboBox=this.parentNode.comboBox;const e=this._comboBox.getAttribute("dir");e&&this.setAttribute("dir",e)}requestContentUpdate(){if(!this.renderer)return;const e={index:this.index,item:this.item,focused:this.focused,selected:this.selected};this.renderer(this,this._comboBox,e)}__rendererOrItemChanged(e,t,i){void 0!==i&&void 0!==t&&(this._oldRenderer!==e&&(this.innerHTML="",delete this._$litPart$),e&&(this._oldRenderer=e,this.requestContentUpdate()))}__updateLabel(e,t){t||(this.textContent=e)}}customElements.define(_o.is,_o);const yo={start:"top",end:"bottom"},ko={start:"left",end:"right"},bo=e=>class extends e{static get properties(){return{positionTarget:{type:Object,value:null},horizontalAlign:{type:String,value:"start"},verticalAlign:{type:String,value:"top"},noHorizontalOverlap:{type:Boolean,value:!1},noVerticalOverlap:{type:Boolean,value:!1}}}static get observers(){return["__positionSettingsChanged(horizontalAlign, verticalAlign, noHorizontalOverlap, noVerticalOverlap)","__overlayOpenedChanged(opened, positionTarget)"]}constructor(){super(),this._updatePosition=this._updatePosition.bind(this)}connectedCallback(){super.connectedCallback(),this.opened&&this.__addUpdatePositionEventListeners()}disconnectedCallback(){super.disconnectedCallback(),this.__removeUpdatePositionEventListeners()}__addUpdatePositionEventListeners(){window.addEventListener("resize",this._updatePosition),this.__positionTargetAncestorRootNodes=function(e){const t=[];for(;e;){if(e.nodeType===Node.DOCUMENT_NODE){t.push(e);break}e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?e=e.assignedSlot?e.assignedSlot:e.parentNode:(t.push(e),e=e.host)}return t}(this.positionTarget),this.__positionTargetAncestorRootNodes.forEach((e=>{e.addEventListener("scroll",this._updatePosition,!0)}))}__removeUpdatePositionEventListeners(){window.removeEventListener("resize",this._updatePosition),this.__positionTargetAncestorRootNodes&&(this.__positionTargetAncestorRootNodes.forEach((e=>{e.removeEventListener("scroll",this._updatePosition,!0)})),this.__positionTargetAncestorRootNodes=null)}__overlayOpenedChanged(e,t){if(this.__removeUpdatePositionEventListeners(),e&&t&&this.__addUpdatePositionEventListeners(),e){const e=getComputedStyle(this);this.__margins||(this.__margins={},["top","bottom","left","right"].forEach((t=>{this.__margins[t]=parseInt(e[t],10)}))),this.setAttribute("dir",e.direction),this._updatePosition(),requestAnimationFrame((()=>this._updatePosition()))}}get __isRTL(){return"rtl"===this.getAttribute("dir")}__positionSettingsChanged(){this._updatePosition()}_updatePosition(){if(!this.positionTarget||!this.opened)return;const e=this.positionTarget.getBoundingClientRect(),t=this.__shouldAlignStartVertically(e);this.style.justifyContent=t?"flex-start":"flex-end";const i=this.__shouldAlignStartHorizontally(e,this.__isRTL),a=!this.__isRTL&&i||this.__isRTL&&!i;this.style.alignItems=a?"flex-start":"flex-end";const n=this.getBoundingClientRect(),o=this.__calculatePositionInOneDimension(e,n,this.noVerticalOverlap,yo,this,t),s=this.__calculatePositionInOneDimension(e,n,this.noHorizontalOverlap,ko,this,i);Object.assign(this.style,o,s),this.toggleAttribute("bottom-aligned",!t),this.toggleAttribute("top-aligned",t),this.toggleAttribute("end-aligned",!a),this.toggleAttribute("start-aligned",a)}__shouldAlignStartHorizontally(e,t){const i=Math.max(this.__oldContentWidth||0,this.$.overlay.offsetWidth);this.__oldContentWidth=this.$.overlay.offsetWidth;const a=Math.min(window.innerWidth,document.documentElement.clientWidth),n=!t&&"start"===this.horizontalAlign||t&&"end"===this.horizontalAlign;return this.__shouldAlignStart(e,i,a,this.__margins,n,this.noHorizontalOverlap,ko)}__shouldAlignStartVertically(e){const t=Math.max(this.__oldContentHeight||0,this.$.overlay.offsetHeight);this.__oldContentHeight=this.$.overlay.offsetHeight;const i=Math.min(window.innerHeight,document.documentElement.clientHeight),a="top"===this.verticalAlign;return this.__shouldAlignStart(e,t,i,this.__margins,a,this.noVerticalOverlap,yo)}__shouldAlignStart(e,t,i,a,n,o,s){const r=i-e[o?s.end:s.start]-a[s.end],l=e[o?s.start:s.end]-a[s.start],d=n?r:l;return n===(d>(n?l:r)||d>t)}__calculatePositionInOneDimension(e,t,i,a,n,o){const s=o?a.start:a.end,r=o?a.end:a.start;return{[s]:`${parseFloat(n.style[s]||getComputedStyle(n)[s])+(t[o?a.start:a.end]-e[i===o?a.end:a.start])*(o?-1:1)}px`,[r]:""}}};let xo;Pn("vaadin-combo-box-overlay",i` #overlay { width: var(--vaadin-combo-box-overlay-width, var(--_vaadin-combo-box-overlay-default-width, auto)); } @@ -579,7 +579,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a flex-direction: column; height: 100%; } - `,{moduleId:"vaadin-combo-box-overlay-styles"});class ko extends(_o(mo)){static get is(){return"vaadin-combo-box-overlay"}static get template(){return yo||(yo=super.template.cloneNode(!0),yo.content.querySelector('[part~="overlay"]').removeAttribute("tabindex")),yo}connectedCallback(){super.connectedCallback();const e=this.__dataHost,t=e&&e.getRootNode().host,i=t&&t.getAttribute("dir");i&&this.setAttribute("dir",i)}ready(){super.ready();const e=document.createElement("div");e.setAttribute("part","loader");const t=this.shadowRoot.querySelector('[part~="content"]');t.parentNode.insertBefore(e,t)}_outsideClickListener(e){const t=e.composedPath();t.includes(this.positionTarget)||t.includes(this)||this.close()}}customElements.define(ko.is,ko);let bo=0,xo=0;const $o=[];let wo=0,Co=!1;const Ao=document.createTextNode("");new window.MutationObserver((function(){Co=!1;const e=$o.length;for(let t=0;t{throw e}))}}$o.splice(0,e),xo+=e})).observe(Ao,{characterData:!0});const Io={after:e=>({run:t=>window.setTimeout(t,e),cancel(e){window.clearTimeout(e)}}),run:(e,t)=>window.setTimeout(e,t),cancel(e){window.clearTimeout(e)}},Eo={run:e=>window.requestAnimationFrame(e),cancel(e){window.cancelAnimationFrame(e)}},So={run:e=>window.requestIdleCallback?window.requestIdleCallback(e):window.setTimeout(e,16),cancel(e){window.cancelIdleCallback?window.cancelIdleCallback(e):window.clearTimeout(e)}},zo={run(e){Co||(Co=!0,Ao.textContent=wo,wo+=1),$o.push(e);const t=bo;return bo+=1,t},cancel(e){const t=e-xo;if(t>=0){if(!$o[t])throw new Error("invalid async handle: "+e);$o[t]=null}}};class Lo{constructor(){this._asyncModule=null,this._callback=null,this._timer=null}setConfig(e,t){this._asyncModule=e,this._callback=t,this._timer=this._asyncModule.run((()=>{this._timer=null,Oo.delete(this),this._callback()}))}cancel(){this.isActive()&&(this._cancelAsync(),Oo.delete(this))}_cancelAsync(){this.isActive()&&(this._asyncModule.cancel(this._timer),this._timer=null)}flush(){this.isActive()&&(this.cancel(),this._callback())}isActive(){return null!=this._timer}static debounce(e,t,i){return e instanceof Lo?e._cancelAsync():e=new Lo,e.setConfig(t,i),e}}let Oo=new Set;function To(){const e=Boolean(Oo.size);return Oo.forEach((e=>{try{e.flush()}catch(e){setTimeout((()=>{throw e}))}})),e}const Po=()=>{let e;do{e=To()}while(e)},Mo=navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/),Fo=Mo&&Mo[1]>=8,Bo={_ratio:.5,_scrollerPaddingTop:0,_scrollPosition:0,_physicalSize:0,_physicalAverage:0,_physicalAverageCount:0,_physicalTop:0,_virtualCount:0,_estScrollHeight:0,_scrollHeight:0,_viewportHeight:0,_viewportWidth:0,_physicalItems:null,_physicalSizes:null,_firstVisibleIndexVal:null,_lastVisibleIndexVal:null,_maxPages:2,_itemsPerRow:1,_itemWidth:0,_rowHeight:0,_templateCost:0,_parentModel:!0,get _physicalBottom(){return this._physicalTop+this._physicalSize},get _scrollBottom(){return this._scrollPosition+this._viewportHeight},get _virtualEnd(){return this._virtualStart+this._physicalCount-1},get _hiddenContentSize(){return(this.grid?this._physicalRows*this._rowHeight:this._physicalSize)-this._viewportHeight},get _maxScrollTop(){return this._estScrollHeight-this._viewportHeight+this._scrollOffset},get _maxVirtualStart(){var e=this._convertIndexToCompleteRow(this._virtualCount);return Math.max(0,e-this._physicalCount)},get _virtualStart(){return this._virtualStartVal||0},set _virtualStart(e){e=this._clamp(e,0,this._maxVirtualStart),this.grid&&(e-=e%this._itemsPerRow),this._virtualStartVal=e},get _physicalStart(){return this._physicalStartVal||0},set _physicalStart(e){(e%=this._physicalCount)<0&&(e=this._physicalCount+e),this.grid&&(e-=e%this._itemsPerRow),this._physicalStartVal=e},get _physicalEnd(){return(this._physicalStart+this._physicalCount-1)%this._physicalCount},get _physicalCount(){return this._physicalCountVal||0},set _physicalCount(e){this._physicalCountVal=e},get _optPhysicalSize(){return 0===this._viewportHeight?1/0:this._viewportHeight*this._maxPages},get _isVisible(){return Boolean(this.offsetWidth||this.offsetHeight)},get firstVisibleIndex(){var e=this._firstVisibleIndexVal;if(null==e){var t=this._physicalTop+this._scrollOffset;e=this._iterateItems(((e,i)=>(t+=this._getPhysicalSizeIncrement(e))>this._scrollPosition?this.grid?i-i%this._itemsPerRow:i:this.grid&&this._virtualCount-1===i?i-i%this._itemsPerRow:void 0))||0,this._firstVisibleIndexVal=e}return e},get lastVisibleIndex(){var e=this._lastVisibleIndexVal;if(null==e){if(this.grid)e=Math.min(this._virtualCount,this.firstVisibleIndex+this._estRowsInView*this._itemsPerRow-1);else{var t=this._physicalTop+this._scrollOffset;this._iterateItems(((i,a)=>{t=0;if(this._scrollPosition=e,this._firstVisibleIndexVal=null,this._lastVisibleIndexVal=null,Math.abs(t)>this._physicalSize&&this._physicalSize>0){t-=this._scrollOffset;var a=Math.round(t/this._physicalAverage)*this._itemsPerRow;this._virtualStart+=a,this._physicalStart+=a,this._physicalTop=Math.min(Math.floor(this._virtualStart/this._itemsPerRow)*this._physicalAverage,this._scrollPosition),this._update()}else if(this._physicalCount>0){var n=this._getReusables(i);i?(this._physicalTop=n.physicalTop,this._virtualStart+=n.indexes.length,this._physicalStart+=n.indexes.length):(this._virtualStart-=n.indexes.length,this._physicalStart-=n.indexes.length),this._update(n.indexes,i?null:n.indexes),this._debounce("_increasePoolIfNeeded",this._increasePoolIfNeeded.bind(this,0),zo)}},_getReusables:function(e){var t,i,a,n=[],o=this._hiddenContentSize*this._ratio,s=this._virtualStart,r=this._virtualEnd,l=this._physicalCount,d=this._physicalTop+this._scrollOffset,c=this._physicalBottom+this._scrollOffset,u=this._scrollPosition,h=this._scrollBottom;for(e?(t=this._physicalStart,i=u-d):(t=this._physicalEnd,i=c-h);i-=a=this._getPhysicalSizeIncrement(t),!(n.length>=l||i<=o);)if(e){if(r+n.length+1>=this._virtualCount)break;if(d+a>=u-this._scrollOffset)break;n.push(t),d+=a,t=(t+1)%l}else{if(s-n.length<=0)break;if(d+this._physicalSize-a<=h)break;n.push(t),d-=a,t=0===t?l-1:t-1}return{indexes:n,physicalTop:d-this._scrollOffset}},_update:function(e,t){if(!(e&&0===e.length||0===this._physicalCount)){if(this._manageFocus(),this._assignModels(e),this._updateMetrics(e),t)for(;t.length;){var i=t.pop();this._physicalTop-=this._getPhysicalSizeIncrement(i)}this._positionItems(),this._updateScrollerSize()}},_isClientFull:function(){return 0!=this._scrollBottom&&this._physicalBottom-1>=this._scrollBottom&&this._physicalTop<=this._scrollPosition},_increasePoolIfNeeded:function(e){var t=this._clamp(this._physicalCount+e,3,this._virtualCount-this._virtualStart);if(t=this._convertIndexToCompleteRow(t),this.grid){var i=t%this._itemsPerRow;i&&t-i<=this._physicalCount&&(t+=this._itemsPerRow),t-=i}var a=t-this._physicalCount,n=Math.round(.5*this._physicalCount);if(!(a<0)){if(a>0){var o=window.performance.now();[].push.apply(this._physicalItems,this._createPool(a));for(var s=0;sthis._physicalEnd&&this._isIndexRendered(this._focusedVirtualIndex)&&this._getPhysicalIndex(this._focusedVirtualIndex)=this._virtualCount-1||0===n||(this._isClientFull()?this._physicalSize0&&(this.updateViewportBoundaries(),this._increasePoolIfNeeded(3))},_gridChanged:function(e,t){void 0!==t&&(this.notifyResize(),Po(),e&&this._updateGridMetrics())},_itemsChanged:function(e){if("items"===e.path)this._virtualStart=0,this._physicalTop=0,this._virtualCount=this.items?this.items.length:0,this._physicalIndexForKey={},this._firstVisibleIndexVal=null,this._lastVisibleIndexVal=null,this._physicalCount=this._physicalCount||0,this._physicalItems=this._physicalItems||[],this._physicalSizes=this._physicalSizes||[],this._physicalStart=0,this._scrollTop>this._scrollOffset&&this._resetScrollPosition(0),this._removeFocusedItem(),this._debounce("_render",this._render,Eo);else if("items.splices"===e.path){if(this._adjustVirtualIndex(e.value.indexSplices),this._virtualCount=this.items?this.items.length:0,e.value.indexSplices.some((function(e){return e.addedCount>0||e.removed.length>0}))){var t=this._getActiveElement();this.contains(t)&&t.blur()}var i=e.value.indexSplices.some((function(e){return e.index+e.addedCount>=this._virtualStart&&e.index<=this._virtualEnd}),this);this._isClientFull()&&!i||this._debounce("_render",this._render,Eo)}else"items.length"!==e.path&&this._forwardItemPath(e.path,e.value)},_iterateItems:function(e,t){var i,a,n,o;if(2===arguments.length&&t){for(o=0;o=this._physicalStart?this._virtualStart+(e-this._physicalStart):this._virtualStart+(this._physicalCount-this._physicalStart)+e},_updateMetrics:function(e){Po();var t=0,i=0,a=this._physicalAverageCount,n=this._physicalAverage;this._iterateItems(((e,a)=>{i+=this._physicalSizes[e],this._physicalSizes[e]=this._physicalItems[e].offsetHeight,t+=this._physicalSizes[e],this._physicalAverageCount+=this._physicalSizes[e]?1:0}),e),this.grid?(this._updateGridMetrics(),this._physicalSize=Math.ceil(this._physicalCount/this._itemsPerRow)*this._rowHeight):(i=1===this._itemsPerRow?i:Math.ceil(this._physicalCount/this._itemsPerRow)*this._rowHeight,this._physicalSize=this._physicalSize+t-i,this._itemsPerRow=1),this._physicalAverageCount!==a&&(this._physicalAverage=Math.round((n*a+t)/this._physicalAverageCount))},_updateGridMetrics:function(){this._itemWidth=this._physicalCount>0?this._physicalItems[0].getBoundingClientRect().width:200,this._rowHeight=this._physicalCount>0?this._physicalItems[0].offsetHeight:200,this._itemsPerRow=this._itemWidth?Math.floor(this._viewportWidth/this._itemWidth):this._itemsPerRow},_positionItems:function(){this._adjustScrollPosition();var e=this._physicalTop;if(this.grid){var t=this._itemsPerRow*this._itemWidth,i=(this._viewportWidth-t)/2;this._iterateItems(((t,a)=>{var n=a%this._itemsPerRow,o=Math.floor(n*this._itemWidth+i);this._isRTL&&(o*=-1),this.translate3d(o+"px",e+"px",0,this._physicalItems[t]),this._shouldRenderNextRow(a)&&(e+=this._rowHeight)}))}else{const t=[];this._iterateItems(((i,a)=>{const n=this._physicalItems[i];this.translate3d(0,e+"px",0,n),e+=this._physicalSizes[i];const o=n.id;o&&t.push(o)})),t.length&&this.setAttribute("aria-owns",t.join(" "))}},_getPhysicalSizeIncrement:function(e){return this.grid?this._computeVidx(e)%this._itemsPerRow!=this._itemsPerRow-1?0:this._rowHeight:this._physicalSizes[e]},_shouldRenderNextRow:function(e){return e%this._itemsPerRow==this._itemsPerRow-1},_adjustScrollPosition:function(){var e=0===this._virtualStart?this._physicalTop:Math.min(this._scrollPosition+this._physicalTop,0);if(0!==e){this._physicalTop-=e;var t=this._scrollPosition;!Fo&&t>0&&this._resetScrollPosition(t-e)}},_resetScrollPosition:function(e){this.scrollTarget&&e>=0&&(this._scrollTop=e,this._scrollPosition=this._scrollTop)},_updateScrollerSize:function(e){this.grid?this._estScrollHeight=this._virtualRowCount*this._rowHeight:this._estScrollHeight=this._physicalBottom+Math.max(this._virtualCount-this._physicalCount-this._virtualStart,0)*this._physicalAverage,((e=(e=(e=e||0===this._scrollHeight)||this._scrollPosition>=this._estScrollHeight-this._physicalSize)||this.grid&&this.$.items.style.height=this._viewportHeight)&&(this.$.items.style.height=this._estScrollHeight+"px",this._scrollHeight=this._estScrollHeight)},scrollToIndex:function(e){if(!("number"!=typeof e||e<0||e>this.items.length-1)&&(Po(),0!==this._physicalCount)){e=this._clamp(e,0,this._virtualCount-1),(!this._isIndexRendered(e)||e>=this._maxVirtualStart)&&(this._virtualStart=this.grid?e-2*this._itemsPerRow:e-1),this._manageFocus(),this._assignModels(),this._updateMetrics(),this._physicalTop=Math.floor(this._virtualStart/this._itemsPerRow)*this._physicalAverage;for(var t=this._physicalStart,i=this._virtualStart,a=0,n=this._hiddenContentSize;i{this._firstVisibleIndexVal=null,this._lastVisibleIndexVal=null,this._isVisible?(this.updateViewportBoundaries(),this.toggleScrollListener(!0),this._resetAverage(),this._render()):this.toggleScrollListener(!1)}),Eo)},updateSizeForItem:function(e){return this.updateSizeForIndex(this.items.indexOf(e))},updateSizeForIndex:function(e){return this._isIndexRendered(e)?(this._updateMetrics([this._getPhysicalIndex(e)]),this._positionItems(),null):null},_convertIndexToCompleteRow:function(e){return this._itemsPerRow=this._itemsPerRow||1,this.grid?Math.ceil(e/this._itemsPerRow)*this._itemsPerRow:e},_isIndexRendered:function(e){return e>=this._virtualStart&&e<=this._virtualEnd},_isIndexVisible:function(e){return e>=this.firstVisibleIndex&&e<=this.lastVisibleIndex},_getPhysicalIndex:function(e){return(this._physicalStart+(e-this._virtualStart))%this._physicalCount},_clamp:function(e,t,i){return Math.min(i,Math.max(t,e))},_debounce:function(e,t,i){var a;this._debouncers=this._debouncers||{},this._debouncers[e]=Lo.debounce(this._debouncers[e],i,t.bind(this)),a=this._debouncers[e],Oo.add(a)}};class Do{constructor({createElements:e,updateElement:t,scrollTarget:i,scrollContainer:a,elementsContainer:n,reorderElements:o}){this.isAttached=!0,this._vidxOffset=0,this.createElements=e,this.updateElement=t,this.scrollTarget=i,this.scrollContainer=a,this.elementsContainer=n||a,this.reorderElements=o,this._maxPages=1.3,this.timeouts={SCROLL_REORDER:500,IGNORE_WHEEL:500},this.__resizeObserver=new ResizeObserver((()=>this._resizeHandler())),"visible"===getComputedStyle(this.scrollTarget).overflow&&(this.scrollTarget.style.overflow="auto"),"static"===getComputedStyle(this.scrollContainer).position&&(this.scrollContainer.style.position="relative"),this.__resizeObserver.observe(this.scrollTarget),this.scrollTarget.addEventListener("scroll",(()=>this._scrollHandler())),this._scrollLineHeight=this._getScrollLineHeight(),this.scrollTarget.addEventListener("wheel",(e=>this.__onWheel(e))),this.reorderElements&&(this.scrollTarget.addEventListener("mousedown",(()=>this.__mouseDown=!0)),this.scrollTarget.addEventListener("mouseup",(()=>{this.__mouseDown=!1,this.__pendingReorder&&this.__reorderElements()})))}_manageFocus(){}_removeFocusedItem(){}get scrollOffset(){return 0}get adjustedFirstVisibleIndex(){return this.firstVisibleIndex+this._vidxOffset}get adjustedLastVisibleIndex(){return this.lastVisibleIndex+this._vidxOffset}scrollToIndex(e){if("number"!=typeof e||isNaN(e)||0===this.size||!this.scrollTarget.offsetHeight)return;e=this._clamp(e,0,this.size-1);const t=this.__getVisibleElements().length;let i=Math.floor(e/this.size*this._virtualCount);this._virtualCount-i{i.__virtualIndex>=e&&i.__virtualIndex<=t&&this.__updateElement(i,i.__virtualIndex,!0)}))}__updateElement(e,t,i){e.style.minHeight&&(e.style.minHeight=""),this.__preventElementUpdates||e.__lastUpdatedIndex===t&&!i||(this.updateElement(e,t),e.__lastUpdatedIndex=t),0===e.offsetHeight&&(e.style.minHeight="200px")}__getIndexScrollOffset(e){const t=this.__getVisibleElements().find((t=>t.__virtualIndex===e));return t?this.scrollTarget.getBoundingClientRect().top-t.getBoundingClientRect().top:void 0}get size(){return this.__size}set size(e){if(e===this.size)return;let t,i;if(this.__preventElementUpdates=!0,e>0&&(t=this.adjustedFirstVisibleIndex,i=this.__getIndexScrollOffset(t)),this.__size=e,Po(),this._itemsChanged({path:"items"}),Po(),e>0){t=Math.min(t,e-1),this.scrollToIndex(t);const a=this.__getIndexScrollOffset(t);void 0!==i&&void 0!==a&&(this._scrollTop+=i-a)}this.elementsContainer.children.length||requestAnimationFrame((()=>this._resizeHandler())),this.__preventElementUpdates=!1,this._resizeHandler(),Po()}get _scrollTop(){return this.scrollTarget.scrollTop}set _scrollTop(e){this.scrollTarget.scrollTop=e}get items(){return{length:Math.min(this.size,1e5)}}get offsetHeight(){return this.scrollTarget.offsetHeight}get $(){return{items:this.scrollContainer}}updateViewportBoundaries(){const e=window.getComputedStyle(this.scrollTarget);this._scrollerPaddingTop=this.scrollTarget===this?0:parseInt(e["padding-top"],10),this._isRTL=Boolean("rtl"===e.direction),this._viewportWidth=this.elementsContainer.offsetWidth,this._viewportHeight=this.scrollTarget.offsetHeight,this._scrollPageHeight=this._viewportHeight-this._scrollLineHeight,this.grid&&this._updateGridMetrics()}setAttribute(){}_createPool(e){const t=this.createElements(e),i=document.createDocumentFragment();return t.forEach((e=>{e.style.position="absolute",i.appendChild(e),this.__resizeObserver.observe(e)})),this.elementsContainer.appendChild(i),t}_assignModels(e){this._iterateItems(((e,t)=>{const i=this._physicalItems[e];i.hidden=t>=this.size,i.hidden?delete i.__lastUpdatedIndex:(i.__virtualIndex=t+(this._vidxOffset||0),this.__updateElement(i,i.__virtualIndex))}),e)}_isClientFull(){return setTimeout((()=>this.__clientFull=!0)),this.__clientFull||super._isClientFull()}translate3d(e,t,i,a){a.style.transform=`translateY(${t})`}toggleScrollListener(){}_scrollHandler(){if(this._adjustVirtualIndexOffset(this._scrollTop-(this.__previousScrollTop||0)),super._scrollHandler(),0!==this._physicalCount){const e=this._getReusables(!0);this._physicalTop=e.physicalTop,this._virtualStart+=e.indexes.length,this._physicalStart+=e.indexes.length}this.reorderElements&&(this.__scrollReorderDebouncer=Lo.debounce(this.__scrollReorderDebouncer,Io.after(this.timeouts.SCROLL_REORDER),(()=>this.__reorderElements()))),this.__previousScrollTop=this._scrollTop}__onWheel(e){if(e.ctrlKey||this._hasScrolledAncestor(e.target,e.deltaX,e.deltaY))return;let t=e.deltaY;if(e.deltaMode===WheelEvent.DOM_DELTA_LINE?t*=this._scrollLineHeight:e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(t*=this._scrollPageHeight),this._deltaYAcc=this._deltaYAcc||0,this._wheelAnimationFrame)return this._deltaYAcc+=t,void e.preventDefault();t+=this._deltaYAcc,this._deltaYAcc=0,this._wheelAnimationFrame=!0,this.__debouncerWheelAnimationFrame=Lo.debounce(this.__debouncerWheelAnimationFrame,Eo,(()=>this._wheelAnimationFrame=!1));const i=Math.abs(e.deltaX)+Math.abs(t);this._canScroll(this.scrollTarget,e.deltaX,t)?(e.preventDefault(),this.scrollTarget.scrollTop+=t,this.scrollTarget.scrollLeft+=e.deltaX,this._hasResidualMomentum=!0,this._ignoreNewWheel=!0,this._debouncerIgnoreNewWheel=Lo.debounce(this._debouncerIgnoreNewWheel,Io.after(this.timeouts.IGNORE_WHEEL),(()=>this._ignoreNewWheel=!1))):this._hasResidualMomentum&&i<=this._previousMomentum||this._ignoreNewWheel?e.preventDefault():i>this._previousMomentum&&(this._hasResidualMomentum=!1),this._previousMomentum=i}_hasScrolledAncestor(e,t,i){return e!==this.scrollTarget&&e!==this.scrollTarget.getRootNode().host&&(!(!this._canScroll(e,t,i)||-1===["auto","scroll"].indexOf(getComputedStyle(e).overflow))||(e!==this&&e.parentElement?this._hasScrolledAncestor(e.parentElement,t,i):void 0))}_canScroll(e,t,i){return i>0&&e.scrollTop0||t>0&&e.scrollLeft0}_getScrollLineHeight(){const e=document.createElement("div");e.style.fontSize="initial",e.style.display="none",document.body.appendChild(e);const t=window.getComputedStyle(e).fontSize;return document.body.removeChild(e),t?window.parseInt(t):void 0}__getVisibleElements(){return Array.from(this.elementsContainer.children).filter((e=>!e.hidden))}__reorderElements(){if(this.__mouseDown)return void(this.__pendingReorder=!0);this.__pendingReorder=!1;const e=this._virtualStart+(this._vidxOffset||0),t=this.__getVisibleElements(),i=t.find((e=>e.contains(this.elementsContainer.getRootNode().activeElement)||e.contains(this.scrollTarget.getRootNode().activeElement)))||t[0];if(!i)return;const a=i.__virtualIndex-e,n=t.indexOf(i)-a;if(n>0)for(let e=0;ethis.scrollTarget.style.transform=e))}}_adjustVirtualIndexOffset(e){if(this._virtualCount>=this.size)this._vidxOffset=0;else if(this.__skipNextVirtualIndexAdjust)this.__skipNextVirtualIndexAdjust=!1;else if(Math.abs(e)>1e4){const e=this._scrollTop/(this.scrollTarget.scrollHeight-this.scrollTarget.offsetHeight),t=e*this.size;this._vidxOffset=Math.round(t-e*this._virtualCount)}else{const e=this._vidxOffset,t=1e3,i=100;0===this._scrollTop?(this._vidxOffset=0,e!==this._vidxOffset&&super.scrollToIndex(0)):this.firstVisibleIndex0&&(this._vidxOffset-=Math.min(this._vidxOffset,i),super.scrollToIndex(this.firstVisibleIndex+(e-this._vidxOffset)));const a=this.size-this._virtualCount;this._scrollTop>=this._maxScrollTop&&this._maxScrollTop>0?(this._vidxOffset=a,e!==this._vidxOffset&&super.scrollToIndex(this._virtualCount-1)):this.firstVisibleIndex>this._virtualCount-t&&this._vidxOffset{throw e}))}}Ao.splice(0,e),Co+=e})).observe(zo,{characterData:!0});const So={after:e=>({run:t=>window.setTimeout(t,e),cancel(e){window.clearTimeout(e)}}),run:(e,t)=>window.setTimeout(e,t),cancel(e){window.clearTimeout(e)}},Lo={run:e=>window.requestAnimationFrame(e),cancel(e){window.cancelAnimationFrame(e)}},To={run:e=>window.requestIdleCallback?window.requestIdleCallback(e):window.setTimeout(e,16),cancel(e){window.cancelIdleCallback?window.cancelIdleCallback(e):window.clearTimeout(e)}},Oo={run(e){Eo||(Eo=!0,zo.textContent=Io,Io+=1),Ao.push(e);const t=wo;return wo+=1,t},cancel(e){const t=e-Co;if(t>=0){if(!Ao[t])throw new Error(`invalid async handle: ${e}`);Ao[t]=null}}};class Po{static debounce(e,t,i){return e instanceof Po?e._cancelAsync():e=new Po,e.setConfig(t,i),e}constructor(){this._asyncModule=null,this._callback=null,this._timer=null}setConfig(e,t){this._asyncModule=e,this._callback=t,this._timer=this._asyncModule.run((()=>{this._timer=null,Mo.delete(this),this._callback()}))}cancel(){this.isActive()&&(this._cancelAsync(),Mo.delete(this))}_cancelAsync(){this.isActive()&&(this._asyncModule.cancel(this._timer),this._timer=null)}flush(){this.isActive()&&(this.cancel(),this._callback())}isActive(){return null!=this._timer}}let Mo=new Set;function Fo(){const e=Boolean(Mo.size);return Mo.forEach((e=>{try{e.flush()}catch(e){setTimeout((()=>{throw e}))}})),e}const Do=()=>{let e;do{e=Fo()}while(e)},Bo=navigator.userAgent.match(/iP(?:hone|ad;(?: U;)? CPU) OS (\d+)/),No=Bo&&Bo[1]>=8,Vo={_ratio:.5,_scrollerPaddingTop:0,_scrollPosition:0,_physicalSize:0,_physicalAverage:0,_physicalAverageCount:0,_physicalTop:0,_virtualCount:0,_estScrollHeight:0,_scrollHeight:0,_viewportHeight:0,_viewportWidth:0,_physicalItems:null,_physicalSizes:null,_firstVisibleIndexVal:null,_lastVisibleIndexVal:null,_maxPages:2,_templateCost:0,get _physicalBottom(){return this._physicalTop+this._physicalSize},get _scrollBottom(){return this._scrollPosition+this._viewportHeight},get _virtualEnd(){return this._virtualStart+this._physicalCount-1},get _hiddenContentSize(){return this._physicalSize-this._viewportHeight},get _maxScrollTop(){return this._estScrollHeight-this._viewportHeight+this._scrollOffset},get _maxVirtualStart(){const e=this._virtualCount;return Math.max(0,e-this._physicalCount)},get _virtualStart(){return this._virtualStartVal||0},set _virtualStart(e){e=this._clamp(e,0,this._maxVirtualStart),this._virtualStartVal=e},get _physicalStart(){return this._physicalStartVal||0},set _physicalStart(e){(e%=this._physicalCount)<0&&(e=this._physicalCount+e),this._physicalStartVal=e},get _physicalEnd(){return(this._physicalStart+this._physicalCount-1)%this._physicalCount},get _physicalCount(){return this._physicalCountVal||0},set _physicalCount(e){this._physicalCountVal=e},get _optPhysicalSize(){return 0===this._viewportHeight?1/0:this._viewportHeight*this._maxPages},get _isVisible(){return Boolean(this.offsetWidth||this.offsetHeight)},get firstVisibleIndex(){let e=this._firstVisibleIndexVal;if(null==e){let t=this._physicalTop+this._scrollOffset;e=this._iterateItems(((e,i)=>{if(t+=this._getPhysicalSizeIncrement(e),t>this._scrollPosition)return i}))||0,this._firstVisibleIndexVal=e}return e},get lastVisibleIndex(){let e=this._lastVisibleIndexVal;if(null==e){let t=this._physicalTop+this._scrollOffset;this._iterateItems(((i,a)=>{t=0;if(this._scrollPosition=e,this._firstVisibleIndexVal=null,this._lastVisibleIndexVal=null,Math.abs(t)>this._physicalSize&&this._physicalSize>0){t-=this._scrollOffset;const e=Math.round(t/this._physicalAverage);this._virtualStart+=e,this._physicalStart+=e,this._physicalTop=Math.min(Math.floor(this._virtualStart)*this._physicalAverage,this._scrollPosition),this._update()}else if(this._physicalCount>0){const e=this._getReusables(i);i?(this._physicalTop=e.physicalTop,this._virtualStart+=e.indexes.length,this._physicalStart+=e.indexes.length):(this._virtualStart-=e.indexes.length,this._physicalStart-=e.indexes.length),this._update(e.indexes,i?null:e.indexes),this._debounce("_increasePoolIfNeeded",this._increasePoolIfNeeded.bind(this,0),Oo)}},_getReusables(e){let t,i,a;const n=[],o=this._hiddenContentSize*this._ratio,s=this._virtualStart,r=this._virtualEnd,l=this._physicalCount;let d=this._physicalTop+this._scrollOffset;const c=this._physicalBottom+this._scrollOffset,u=this._scrollPosition,h=this._scrollBottom;for(e?(t=this._physicalStart,i=u-d):(t=this._physicalEnd,i=c-h);a=this._getPhysicalSizeIncrement(t),i-=a,!(n.length>=l||i<=o);)if(e){if(r+n.length+1>=this._virtualCount)break;if(d+a>=u-this._scrollOffset)break;n.push(t),d+=a,t=(t+1)%l}else{if(s-n.length<=0)break;if(d+this._physicalSize-a<=h)break;n.push(t),d-=a,t=0===t?l-1:t-1}return{indexes:n,physicalTop:d-this._scrollOffset}},_update(e,t){if(!(e&&0===e.length||0===this._physicalCount)){if(this._assignModels(e),this._updateMetrics(e),t)for(;t.length;){const e=t.pop();this._physicalTop-=this._getPhysicalSizeIncrement(e)}this._positionItems(),this._updateScrollerSize()}},_isClientFull(){return 0!==this._scrollBottom&&this._physicalBottom-1>=this._scrollBottom&&this._physicalTop<=this._scrollPosition},_increasePoolIfNeeded(e){const t=this._clamp(this._physicalCount+e,3,this._virtualCount-this._virtualStart)-this._physicalCount;let i=Math.round(.5*this._physicalCount);if(!(t<0)){if(t>0){const e=window.performance.now();[].push.apply(this._physicalItems,this._createPool(t));for(let e=0;ethis._physicalEnd&&this._isIndexRendered(this._focusedVirtualIndex)&&this._getPhysicalIndex(this._focusedVirtualIndex)=this._virtualCount-1||0===i||(this._isClientFull()?this._physicalSize0&&(this.updateViewportBoundaries(),this._increasePoolIfNeeded(3))},_itemsChanged(e){"items"===e.path&&(this._virtualStart=0,this._physicalTop=0,this._virtualCount=this.items?this.items.length:0,this._physicalIndexForKey={},this._firstVisibleIndexVal=null,this._lastVisibleIndexVal=null,this._physicalCount=this._physicalCount||0,this._physicalItems=this._physicalItems||[],this._physicalSizes=this._physicalSizes||[],this._physicalStart=0,this._scrollTop>this._scrollOffset&&this._resetScrollPosition(0),this._debounce("_render",this._render,Lo))},_iterateItems(e,t){let i,a,n,o;if(2===arguments.length&&t){for(o=0;o=this._physicalStart?this._virtualStart+(e-this._physicalStart):this._virtualStart+(this._physicalCount-this._physicalStart)+e},_updateMetrics(e){Do();let t=0,i=0;const a=this._physicalAverageCount,n=this._physicalAverage;this._iterateItems(((e,a)=>{i+=this._physicalSizes[e],this._physicalSizes[e]=this._physicalItems[e].offsetHeight,t+=this._physicalSizes[e],this._physicalAverageCount+=this._physicalSizes[e]?1:0}),e),this._physicalSize=this._physicalSize+t-i,this._physicalAverageCount!==a&&(this._physicalAverage=Math.round((n*a+t)/this._physicalAverageCount))},_positionItems(){this._adjustScrollPosition();let e=this._physicalTop;this._iterateItems((t=>{this.translate3d(0,`${e}px`,0,this._physicalItems[t]),e+=this._physicalSizes[t]}))},_getPhysicalSizeIncrement(e){return this._physicalSizes[e]},_adjustScrollPosition(){const e=0===this._virtualStart?this._physicalTop:Math.min(this._scrollPosition+this._physicalTop,0);if(0!==e){this._physicalTop-=e;const t=this._scrollPosition;!No&&t>0&&this._resetScrollPosition(t-e)}},_resetScrollPosition(e){this.scrollTarget&&e>=0&&(this._scrollTop=e,this._scrollPosition=this._scrollTop)},_updateScrollerSize(e){this._estScrollHeight=this._physicalBottom+Math.max(this._virtualCount-this._physicalCount-this._virtualStart,0)*this._physicalAverage,((e=(e=e||0===this._scrollHeight)||this._scrollPosition>=this._estScrollHeight-this._physicalSize)||Math.abs(this._estScrollHeight-this._scrollHeight)>=this._viewportHeight)&&(this.$.items.style.height=`${this._estScrollHeight}px`,this._scrollHeight=this._estScrollHeight)},scrollToIndex(e){if("number"!=typeof e||e<0||e>this.items.length-1)return;if(Do(),0===this._physicalCount)return;e=this._clamp(e,0,this._virtualCount-1),(!this._isIndexRendered(e)||e>=this._maxVirtualStart)&&(this._virtualStart=e-1),this._assignModels(),this._updateMetrics(),this._physicalTop=this._virtualStart*this._physicalAverage;let t=this._physicalStart,i=this._virtualStart,a=0;const n=this._hiddenContentSize;for(;i{this._firstVisibleIndexVal=null,this._lastVisibleIndexVal=null,this._isVisible?(this.updateViewportBoundaries(),this.toggleScrollListener(!0),this._resetAverage(),this._render()):this.toggleScrollListener(!1)}),Lo)},_isIndexRendered(e){return e>=this._virtualStart&&e<=this._virtualEnd},_getPhysicalIndex(e){return(this._physicalStart+(e-this._virtualStart))%this._physicalCount},_clamp:(e,t,i)=>Math.min(i,Math.max(t,e)),_debounce(e,t,i){var a;this._debouncers=this._debouncers||{},this._debouncers[e]=Po.debounce(this._debouncers[e],i,t.bind(this)),a=this._debouncers[e],Mo.add(a)}};class jo{constructor({createElements:e,updateElement:t,scrollTarget:i,scrollContainer:a,elementsContainer:n,reorderElements:o}){this.isAttached=!0,this._vidxOffset=0,this.createElements=e,this.updateElement=t,this.scrollTarget=i,this.scrollContainer=a,this.elementsContainer=n||a,this.reorderElements=o,this._maxPages=1.3,this.__placeholderHeight=200,this.__elementHeightQueue=Array(10),this.timeouts={SCROLL_REORDER:500,IGNORE_WHEEL:500},this.__resizeObserver=new ResizeObserver((()=>this._resizeHandler())),"visible"===getComputedStyle(this.scrollTarget).overflow&&(this.scrollTarget.style.overflow="auto"),"static"===getComputedStyle(this.scrollContainer).position&&(this.scrollContainer.style.position="relative"),this.__resizeObserver.observe(this.scrollTarget),this.scrollTarget.addEventListener("scroll",(()=>this._scrollHandler())),this._scrollLineHeight=this._getScrollLineHeight(),this.scrollTarget.addEventListener("wheel",(e=>this.__onWheel(e))),this.reorderElements&&(this.scrollTarget.addEventListener("mousedown",(()=>this.__mouseDown=!0)),this.scrollTarget.addEventListener("mouseup",(()=>{this.__mouseDown=!1,this.__pendingReorder&&this.__reorderElements()})))}get scrollOffset(){return 0}get adjustedFirstVisibleIndex(){return this.firstVisibleIndex+this._vidxOffset}get adjustedLastVisibleIndex(){return this.lastVisibleIndex+this._vidxOffset}scrollToIndex(e){if("number"!=typeof e||isNaN(e)||0===this.size||!this.scrollTarget.offsetHeight)return;e=this._clamp(e,0,this.size-1);const t=this.__getVisibleElements().length;let i=Math.floor(e/this.size*this._virtualCount);this._virtualCount-i{i.__virtualIndex>=e&&i.__virtualIndex<=t&&this.__updateElement(i,i.__virtualIndex,!0)}))}__updateElement(e,t,i){e.style.paddingTop&&(e.style.paddingTop=""),this.__preventElementUpdates||e.__lastUpdatedIndex===t&&!i||(this.updateElement(e,t),e.__lastUpdatedIndex=t);const a=e.offsetHeight;if(0===a)e.style.paddingTop=`${this.__placeholderHeight}px`;else{this.__elementHeightQueue.push(a),this.__elementHeightQueue.shift();const e=this.__elementHeightQueue.filter((e=>void 0!==e));this.__placeholderHeight=Math.round(e.reduce(((e,t)=>e+t),0)/e.length)}}__getIndexScrollOffset(e){const t=this.__getVisibleElements().find((t=>t.__virtualIndex===e));return t?this.scrollTarget.getBoundingClientRect().top-t.getBoundingClientRect().top:void 0}get size(){return this.__size}set size(e){if(e===this.size)return;let t,i;if(this.__preventElementUpdates=!0,e>0&&(t=this.adjustedFirstVisibleIndex,i=this.__getIndexScrollOffset(t)),this.__size=e,Do(),this._itemsChanged({path:"items"}),Do(),e>0){t=Math.min(t,e-1),this.scrollToIndex(t);const a=this.__getIndexScrollOffset(t);void 0!==i&&void 0!==a&&(this._scrollTop+=i-a)}this.elementsContainer.children.length||requestAnimationFrame((()=>this._resizeHandler())),this.__preventElementUpdates=!1,this._resizeHandler(),Do()}get _scrollTop(){return this.scrollTarget.scrollTop}set _scrollTop(e){this.scrollTarget.scrollTop=e}get items(){return{length:Math.min(this.size,1e5)}}get offsetHeight(){return this.scrollTarget.offsetHeight}get $(){return{items:this.scrollContainer}}updateViewportBoundaries(){const e=window.getComputedStyle(this.scrollTarget);this._scrollerPaddingTop=this.scrollTarget===this?0:parseInt(e["padding-top"],10),this._isRTL=Boolean("rtl"===e.direction),this._viewportWidth=this.elementsContainer.offsetWidth,this._viewportHeight=this.scrollTarget.offsetHeight,this._scrollPageHeight=this._viewportHeight-this._scrollLineHeight,this.grid&&this._updateGridMetrics()}setAttribute(){}_createPool(e){const t=this.createElements(e),i=document.createDocumentFragment();return t.forEach((e=>{e.style.position="absolute",i.appendChild(e),this.__resizeObserver.observe(e)})),this.elementsContainer.appendChild(i),t}_assignModels(e){this._iterateItems(((e,t)=>{const i=this._physicalItems[e];i.hidden=t>=this.size,i.hidden?delete i.__lastUpdatedIndex:(i.__virtualIndex=t+(this._vidxOffset||0),this.__updateElement(i,i.__virtualIndex))}),e)}_isClientFull(){return setTimeout((()=>this.__clientFull=!0)),this.__clientFull||super._isClientFull()}translate3d(e,t,i,a){a.style.transform=`translateY(${t})`}toggleScrollListener(){}_scrollHandler(){this._adjustVirtualIndexOffset(this._scrollTop-(this.__previousScrollTop||0));const e=this.scrollTarget.scrollTop-this._scrollPosition;if(super._scrollHandler(),0!==this._physicalCount){const t=e>=0,i=this._getReusables(!t);i.indexes.length&&(this._physicalTop=i.physicalTop,t?(this._virtualStart-=i.indexes.length,this._physicalStart-=i.indexes.length):(this._virtualStart+=i.indexes.length,this._physicalStart+=i.indexes.length),this._resizeHandler())}this.reorderElements&&(this.__scrollReorderDebouncer=Po.debounce(this.__scrollReorderDebouncer,So.after(this.timeouts.SCROLL_REORDER),(()=>this.__reorderElements()))),this.__previousScrollTop=this._scrollTop}__onWheel(e){if(e.ctrlKey||this._hasScrolledAncestor(e.target,e.deltaX,e.deltaY))return;let t=e.deltaY;if(e.deltaMode===WheelEvent.DOM_DELTA_LINE?t*=this._scrollLineHeight:e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(t*=this._scrollPageHeight),this._deltaYAcc=this._deltaYAcc||0,this._wheelAnimationFrame)return this._deltaYAcc+=t,void e.preventDefault();t+=this._deltaYAcc,this._deltaYAcc=0,this._wheelAnimationFrame=!0,this.__debouncerWheelAnimationFrame=Po.debounce(this.__debouncerWheelAnimationFrame,Lo,(()=>this._wheelAnimationFrame=!1));const i=Math.abs(e.deltaX)+Math.abs(t);this._canScroll(this.scrollTarget,e.deltaX,t)?(e.preventDefault(),this.scrollTarget.scrollTop+=t,this.scrollTarget.scrollLeft+=e.deltaX,this._hasResidualMomentum=!0,this._ignoreNewWheel=!0,this._debouncerIgnoreNewWheel=Po.debounce(this._debouncerIgnoreNewWheel,So.after(this.timeouts.IGNORE_WHEEL),(()=>this._ignoreNewWheel=!1))):this._hasResidualMomentum&&i<=this._previousMomentum||this._ignoreNewWheel?e.preventDefault():i>this._previousMomentum&&(this._hasResidualMomentum=!1),this._previousMomentum=i}_hasScrolledAncestor(e,t,i){return e!==this.scrollTarget&&e!==this.scrollTarget.getRootNode().host&&(!(!this._canScroll(e,t,i)||-1===["auto","scroll"].indexOf(getComputedStyle(e).overflow))||(e!==this&&e.parentElement?this._hasScrolledAncestor(e.parentElement,t,i):void 0))}_canScroll(e,t,i){return i>0&&e.scrollTop0||t>0&&e.scrollLeft0}_getScrollLineHeight(){const e=document.createElement("div");e.style.fontSize="initial",e.style.display="none",document.body.appendChild(e);const t=window.getComputedStyle(e).fontSize;return document.body.removeChild(e),t?window.parseInt(t):void 0}__getVisibleElements(){return Array.from(this.elementsContainer.children).filter((e=>!e.hidden))}__reorderElements(){if(this.__mouseDown)return void(this.__pendingReorder=!0);this.__pendingReorder=!1;const e=this._virtualStart+(this._vidxOffset||0),t=this.__getVisibleElements(),i=t.find((e=>e.contains(this.elementsContainer.getRootNode().activeElement)||e.contains(this.scrollTarget.getRootNode().activeElement)))||t[0];if(!i)return;const a=i.__virtualIndex-e,n=t.indexOf(i)-a;if(n>0)for(let e=0;ethis.scrollTarget.style.transform=e))}}_adjustVirtualIndexOffset(e){if(this._virtualCount>=this.size)this._vidxOffset=0;else if(this.__skipNextVirtualIndexAdjust)this.__skipNextVirtualIndexAdjust=!1;else if(Math.abs(e)>1e4){const e=this._scrollTop/(this.scrollTarget.scrollHeight-this.scrollTarget.offsetHeight),t=e*this.size;this._vidxOffset=Math.round(t-e*this._virtualCount)}else{const e=this._vidxOffset,t=1e3,i=100;0===this._scrollTop?(this._vidxOffset=0,e!==this._vidxOffset&&super.scrollToIndex(0)):this.firstVisibleIndex0&&(this._vidxOffset-=Math.min(this._vidxOffset,i),super.scrollToIndex(this.firstVisibleIndex+(e-this._vidxOffset)));const a=this.size-this._virtualCount;this._scrollTop>=this._maxScrollTop&&this._maxScrollTop>0?(this._vidxOffset=a,e!==this._vidxOffset&&super.scrollToIndex(this._virtualCount-1)):this.firstVisibleIndex>this._virtualCount-t&&this._vidxOffset :host { display: block; @@ -605,7 +605,7 @@ import{A as e,a$ as t,r as i,aa as a,a7 as n,b0 as o,b1 as s,b2 as r,a9 as l,Q a
- `}static get properties(){return{items:{type:Array,observer:"__itemsChanged"},focusedIndex:{type:Number,observer:"__focusedIndexChanged"},loading:{type:Boolean,observer:"__loadingChanged"},opened:{type:Boolean,observer:"__openedChanged"},selectedItem:{type:Object},itemIdPath:{type:String},comboBox:{type:Object},getItemLabel:{type:Object},renderer:{type:Object,observer:"__rendererChanged"},theme:{type:String}}}constructor(){super(),this.__boundOnItemClick=this.__onItemClick.bind(this)}__openedChanged(e){e&&this.requestContentUpdate()}ready(){super.ready(),this.__hostTagName=this.constructor.is.replace("-scroller",""),this.setAttribute("role","listbox"),this.addEventListener("click",(e=>e.stopPropagation())),this.__patchWheelOverScrolling(),this.__virtualizer=new No({createElements:this.__createElements.bind(this),updateElement:this.__updateElement.bind(this),elementsContainer:this,scrollTarget:this,scrollContainer:this.$.selector})}requestContentUpdate(){this.__virtualizer&&this.__virtualizer.update()}scrollIntoView(e){if(!(this.opened&&e>=0))return;const t=this._visibleItemsCount();let i=e;e>this.__virtualizer.lastVisibleIndex-1?(this.__virtualizer.scrollToIndex(e),i=e-t+1):e>this.__virtualizer.firstVisibleIndex&&(i=this.__virtualizer.firstVisibleIndex),this.__virtualizer.scrollToIndex(Math.max(0,i));const a=[...this.children].find((e=>!e.hidden&&e.index===this.__virtualizer.lastVisibleIndex));if(!a||e!==a.index)return;const n=a.getBoundingClientRect(),o=this.getBoundingClientRect(),s=n.bottom-o.bottom+this._viewportTotalPaddingBottom;s>0&&(this.scrollTop+=s)}__getAriaRole(e){return void 0!==e&&"option"}__getAriaSelected(e,t){return this.__isItemFocused(e,t).toString()}__isItemFocused(e,t){return e==t}__isItemSelected(e,t,i){return!(e instanceof Vo)&&(i&&void 0!==e&&void 0!==t?this.get(i,e)===this.get(i,t):e===t)}__itemsChanged(e){this.__virtualizer&&e&&(this.__virtualizer.size=e.length,this.__virtualizer.flush(),this.setAttribute("aria-setsize",e.length),this.requestContentUpdate())}__loadingChanged(e){this.__virtualizer&&!e&&setTimeout((()=>this.requestContentUpdate()))}__focusedIndexChanged(e,t){this.__virtualizer&&(e!==t&&this.requestContentUpdate(),e>=0&&!this.loading&&this.scrollIntoView(e))}__rendererChanged(e,t){(e||t)&&this.requestContentUpdate()}__createElements(e){return[...Array(e)].map((()=>{const e=document.createElement(`${this.__hostTagName}-item`);return e.addEventListener("click",this.__boundOnItemClick),e.tabIndex="-1",e.style.width="100%",e}))}__updateElement(e,t){const i=this.items[t],a=this.focusedIndex;e.setProperties({item:i,index:this.__requestItemByIndex(i,t),label:this.getItemLabel(i),selected:this.__isItemSelected(i,this.selectedItem,this.itemIdPath),renderer:this.renderer,focused:this.__isItemFocused(a,t)}),e.id=`${this.__hostTagName}-item-${t}`,e.setAttribute("role",this.__getAriaRole(t)),e.setAttribute("aria-selected",this.__getAriaSelected(a,t)),e.setAttribute("aria-posinset",t+1),this.theme?e.setAttribute("theme",this.theme):e.removeAttribute("theme")}__onItemClick(e){this.dispatchEvent(new CustomEvent("selection-changed",{detail:{item:e.currentTarget.item}}))}__patchWheelOverScrolling(){this.$.selector.addEventListener("wheel",(e=>{const t=0===this.scrollTop,i=this.scrollHeight-this.scrollTop-this.clientHeight<=1;(t&&e.deltaY<0||i&&e.deltaY>0)&&e.preventDefault()}))}get _viewportTotalPaddingBottom(){if(void 0===this._cachedViewportTotalPaddingBottom){const e=window.getComputedStyle(this.$.selector);this._cachedViewportTotalPaddingBottom=[e.paddingBottom,e.borderBottomWidth].map((e=>parseInt(e,10))).reduce(((e,t)=>e+t))}return this._cachedViewportTotalPaddingBottom}__requestItemByIndex(e,t){return e instanceof Vo&&void 0!==t&&this.dispatchEvent(new CustomEvent("index-requested",{detail:{index:t,currentScrollerPos:this._oldScrollerPosition}})),t}_visibleItemsCount(){this.__virtualizer.scrollToIndex(this.__virtualizer.firstVisibleIndex);return this.__virtualizer.size>0?this.__virtualizer.lastVisibleIndex-this.__virtualizer.firstVisibleIndex+1:0}}customElements.define(Ro.is,Ro);class jo extends a{static get is(){return"vaadin-combo-box-dropdown"}static get template(){return n` + `}static get properties(){return{items:{type:Array,observer:"__itemsChanged"},focusedIndex:{type:Number,observer:"__focusedIndexChanged"},loading:{type:Boolean,observer:"__loadingChanged"},opened:{type:Boolean,observer:"__openedChanged"},selectedItem:{type:Object},itemIdPath:{type:String},comboBox:{type:Object},getItemLabel:{type:Object},renderer:{type:Object,observer:"__rendererChanged"},theme:{type:String}}}constructor(){super(),this.__boundOnItemClick=this.__onItemClick.bind(this)}__openedChanged(e){e&&this.requestContentUpdate()}ready(){super.ready(),this.__hostTagName=this.constructor.is.replace("-scroller",""),this.setAttribute("role","listbox"),this.addEventListener("click",(e=>e.stopPropagation())),this.__patchWheelOverScrolling(),this.__virtualizer=new qo({createElements:this.__createElements.bind(this),updateElement:this.__updateElement.bind(this),elementsContainer:this,scrollTarget:this,scrollContainer:this.$.selector})}requestContentUpdate(){this.__virtualizer&&this.__virtualizer.update()}scrollIntoView(e){if(!(this.opened&&e>=0))return;const t=this._visibleItemsCount();let i=e;e>this.__virtualizer.lastVisibleIndex-1?(this.__virtualizer.scrollToIndex(e),i=e-t+1):e>this.__virtualizer.firstVisibleIndex&&(i=this.__virtualizer.firstVisibleIndex),this.__virtualizer.scrollToIndex(Math.max(0,i));const a=[...this.children].find((e=>!e.hidden&&e.index===this.__virtualizer.lastVisibleIndex));if(!a||e!==a.index)return;const n=a.getBoundingClientRect(),o=this.getBoundingClientRect(),s=n.bottom-o.bottom+this._viewportTotalPaddingBottom;s>0&&(this.scrollTop+=s)}__getAriaRole(e){return void 0!==e&&"option"}__getAriaSelected(e,t){return this.__isItemFocused(e,t).toString()}__isItemFocused(e,t){return e===t}__isItemSelected(e,t,i){return!(e instanceof Ro)&&(i&&void 0!==e&&void 0!==t?this.get(i,e)===this.get(i,t):e===t)}__itemsChanged(e){this.__virtualizer&&e&&(this.__virtualizer.size=e.length,this.__virtualizer.flush(),this.setAttribute("aria-setsize",e.length),this.requestContentUpdate())}__loadingChanged(e){this.__virtualizer&&!e&&setTimeout((()=>this.requestContentUpdate()))}__focusedIndexChanged(e,t){this.__virtualizer&&(e!==t&&this.requestContentUpdate(),e>=0&&!this.loading&&this.scrollIntoView(e))}__rendererChanged(e,t){(e||t)&&this.requestContentUpdate()}__createElements(e){return[...Array(e)].map((()=>{const e=document.createElement(`${this.__hostTagName}-item`);return e.addEventListener("click",this.__boundOnItemClick),e.tabIndex="-1",e.style.width="100%",e}))}__updateElement(e,t){const i=this.items[t],a=this.focusedIndex;e.setProperties({item:i,index:this.__requestItemByIndex(i,t),label:this.getItemLabel(i),selected:this.__isItemSelected(i,this.selectedItem,this.itemIdPath),renderer:this.renderer,focused:this.__isItemFocused(a,t)}),e.id=`${this.__hostTagName}-item-${t}`,e.setAttribute("role",this.__getAriaRole(t)),e.setAttribute("aria-selected",this.__getAriaSelected(a,t)),e.setAttribute("aria-posinset",t+1),this.theme?e.setAttribute("theme",this.theme):e.removeAttribute("theme")}__onItemClick(e){this.dispatchEvent(new CustomEvent("selection-changed",{detail:{item:e.currentTarget.item}}))}__patchWheelOverScrolling(){this.$.selector.addEventListener("wheel",(e=>{const t=0===this.scrollTop,i=this.scrollHeight-this.scrollTop-this.clientHeight<=1;(t&&e.deltaY<0||i&&e.deltaY>0)&&e.preventDefault()}))}get _viewportTotalPaddingBottom(){if(void 0===this._cachedViewportTotalPaddingBottom){const e=window.getComputedStyle(this.$.selector);this._cachedViewportTotalPaddingBottom=[e.paddingBottom,e.borderBottomWidth].map((e=>parseInt(e,10))).reduce(((e,t)=>e+t))}return this._cachedViewportTotalPaddingBottom}__requestItemByIndex(e,t){return e instanceof Ro&&void 0!==t&&this.dispatchEvent(new CustomEvent("index-requested",{detail:{index:t,currentScrollerPos:this._oldScrollerPosition}})),t}_visibleItemsCount(){this.__virtualizer.scrollToIndex(this.__virtualizer.firstVisibleIndex);return this.__virtualizer.size>0?this.__virtualizer.lastVisibleIndex-this.__virtualizer.firstVisibleIndex+1:0}}customElements.define(Uo.is,Uo);class Ho extends n{static get is(){return"vaadin-combo-box-dropdown"}static get template(){return o` - `}static get properties(){return{opened:Boolean,positionTarget:{type:Object,observer:"_positionTargetChanged"},renderer:Function,loading:{type:Boolean,value:!1,reflectToAttribute:!0},theme:String,_selectedItem:{type:Object},_items:{type:Array},_focusedIndex:{type:Number,value:-1},focusedItem:{type:String,computed:"_getFocusedItem(_focusedIndex)"},_itemLabelPath:{type:String,value:"label"},_itemValuePath:{type:String,value:"value"},_scroller:Object,_itemIdPath:String,_overlayOpened:{type:Boolean,observer:"_openedChanged"}}}static get observers(){return["_openedOrItemsChanged(opened, _items, loading)","__updateScroller(_scroller, _items, opened, loading, _selectedItem, _itemIdPath, _focusedIndex, renderer, theme)"]}constructor(){super();const e=jo._uniqueId=1+jo._uniqueId||0;this.scrollerId=`${this.localName}-scroller-${e}`}ready(){super.ready(),this.__hostTagName=this.constructor.is.replace("-dropdown","");const e=this.$.overlay,t=`${this.__hostTagName}-scroller`;e.renderer=e=>{if(!e.firstChild){const i=document.createElement(t);e.appendChild(i)}},e.requestContentUpdate(),this._scroller=e.content.querySelector(t),this._scroller.id=this.scrollerId,this._scroller.getItemLabel=this.getItemLabel.bind(this),this._scroller.comboBox=this.getRootNode().host,this._scroller.addEventListener("selection-changed",(e=>this._forwardScrollerEvent(e))),this._scroller.addEventListener("index-requested",(e=>this._forwardScrollerEvent(e))),e.addEventListener("touchend",(e=>this._fireTouchAction(e))),e.addEventListener("touchmove",(e=>this._fireTouchAction(e))),e.addEventListener("mousedown",(e=>e.preventDefault())),e.addEventListener("vaadin-overlay-outside-click",(e=>{e.preventDefault()}))}disconnectedCallback(){super.disconnectedCallback(),this._overlayOpened=!1}_fireTouchAction(e){this.dispatchEvent(new CustomEvent("vaadin-overlay-touch-action",{detail:{sourceEvent:e}}))}_forwardScrollerEvent(e){this.dispatchEvent(new CustomEvent(e.type,{detail:e.detail}))}_openedChanged(e,t){e?(this._setOverlayWidth(),this._scroller.style.maxHeight=getComputedStyle(this).getPropertyValue(`--${this.__hostTagName}-overlay-max-height`)||"65vh",this.dispatchEvent(new CustomEvent("vaadin-combo-box-dropdown-opened",{bubbles:!0,composed:!0}))):t&&!this.__emptyItems&&this.dispatchEvent(new CustomEvent("vaadin-combo-box-dropdown-closed",{bubbles:!0,composed:!0}))}_openedOrItemsChanged(e,t,i){const a=t&&t.length;a||(this.__emptyItems=!0),this._overlayOpened=!(!e||!i&&!a),this.__emptyItems=!1}_getFocusedItem(e){if(e>=0)return this._items[e]}indexOfLabel(e){if(this._items&&e)for(let t=0;tclass extends e{static get properties(){return{pageSize:{type:Number,value:50,observer:"_pageSizeChanged"},size:{type:Number,observer:"_sizeChanged"},dataProvider:{type:Object,observer:"_dataProviderChanged"},_pendingRequests:{value:()=>({})},__placeHolder:{value:new Vo}}}static get observers(){return["_dataProviderFilterChanged(filter, dataProvider)","_dataProviderClearFilter(dataProvider, opened, value)","_warnDataProviderValue(dataProvider, value)","_ensureFirstPage(opened)"]}_dataProviderClearFilter(e,t,i){!e||this.loading||!this.filter||t&&this.autoOpenDisabled&&i===this.filter||(this.size=void 0,this._pendingRequests={},this.filter="",this.clearCache())}ready(){super.ready(),this.clearCache(),this.$.dropdown.addEventListener("index-requested",(e=>{const t=e.detail.index,i=e.detail.currentScrollerPos,a=Math.floor(1.5*this.pageSize);if(!this._shouldSkipIndex(t,a,i)&&void 0!==t){const e=this._getPageForIndex(t);this._shouldLoadPage(e)&&this._loadPage(e)}}))}_dataProviderFilterChanged(){this._shouldFetchData()&&(this.size=void 0,this._pendingRequests={},this.clearCache())}_shouldFetchData(){return!!this.dataProvider&&(this.opened||this.filter&&this.filter.length)}_ensureFirstPage(e){e&&this._shouldLoadPage(0)&&this._loadPage(0)}_shouldSkipIndex(e,t,i){return 0!==i&&e>=i-t&&e<=i+t}_shouldLoadPage(e){if(!this.filteredItems||this._forceNextRequest)return this._forceNextRequest=!1,!0;const t=this.filteredItems[e*this.pageSize];return void 0!==t?t instanceof Vo:void 0===this.size}_loadPage(e){if(!this._pendingRequests[e]&&this.dataProvider){this.loading=!0;const t={page:e,pageSize:this.pageSize,filter:this.filter},i=(a,n)=>{if(this._pendingRequests[e]===i){if(this.filteredItems)this.splice("filteredItems",t.page*t.pageSize,a.length,...a);else{const e=[];e.splice(t.page*t.pageSize,a.length,...a),this.filteredItems=e}this._isValidValue(this.value)&&this._getItemValue(this.selectedItem)!==this.value&&this._selectItemForValue(this.value),this.opened||this.hasAttribute("focused")||this._commitValue(),this.size=n,delete this._pendingRequests[e],0===Object.keys(this._pendingRequests).length&&(this.loading=!1)}};this._pendingRequests[e]||(this._pendingRequests[e]=i,this.dataProvider(t,i))}}_getPageForIndex(e){return Math.floor(e/this.pageSize)}clearCache(){if(!this.dataProvider)return;this._pendingRequests={};const e=[];for(let t=0;t<(this.size||0);t++)e.push(this.__placeHolder);this.filteredItems=e,this._shouldFetchData()?this._loadPage(0):this._forceNextRequest=!0}_sizeChanged(e=0){const t=(this.filteredItems||[]).slice(0,e);for(let i=0;i 0");this.clearCache()}_dataProviderChanged(e,t){this._ensureItemsOrDataProvider((()=>{this.dataProvider=t}))}_ensureItemsOrDataProvider(e){if(void 0!==this.items&&void 0!==this.dataProvider)throw e(),new Error("Using `items` and `dataProvider` together is not supported");this.dataProvider&&!this.filteredItems&&(this.filteredItems=[])}_warnDataProviderValue(e,t){if(e&&""!==t&&(void 0===this.selectedItem||null===this.selectedItem)){const e=this._indexOfValue(t,this.filteredItems);(e<0||!this._getItemLabel(this.filteredItems[e]))&&console.warn("Warning: unable to determine the label for the provided `value`. Nothing to display in the text field. This usually happens when setting an initial `value` before any items are returned from the `dataProvider` callback. Consider setting `selectedItem` instead of `value`")}}_flushPendingRequests(e){if(this._pendingRequests){const t=Math.ceil(e/this.pageSize),i=Object.keys(this._pendingRequests);for(let a=0;a=t&&this._pendingRequests[n]([],e)}}}},Uo=o((e=>class extends e{static get properties(){return{disabled:{type:Boolean,value:!1,observer:"_disabledChanged",reflectToAttribute:!0}}}_disabledChanged(e){this._setAriaDisabled(e)}_setAriaDisabled(e){e?this.setAttribute("aria-disabled","true"):this.removeAttribute("aria-disabled")}click(){this.disabled||super.click()}})),Ho=o((e=>class extends e{ready(){super.ready(),this.addEventListener("keydown",(e=>{this._onKeyDown(e)})),this.addEventListener("keyup",(e=>{this._onKeyUp(e)}))}_onKeyDown(e){}_onKeyUp(e){}}));const Go=o((e=>class extends e{static get properties(){return{inputElement:{type:Object,readOnly:!0,observer:"_inputElementChanged"},type:{type:String,readOnly:!0},value:{type:String,value:"",observer:"_valueChanged",notify:!0}}}constructor(){super(),this._boundOnInput=this._onInput.bind(this),this._boundOnChange=this._onChange.bind(this)}clear(){this.value=""}_addInputListeners(e){e.addEventListener("input",this._boundOnInput),e.addEventListener("change",this._boundOnChange)}_removeInputListeners(e){e.removeEventListener("input",this._boundOnInput),e.removeEventListener("change",this._boundOnChange)}_forwardInputValue(e){this.inputElement&&(this.inputElement.value=null!=e?e:"")}_inputElementChanged(e,t){e?this._addInputListeners(e):t&&this._removeInputListeners(t)}_onInput(e){this.__userInput=e.isTrusted,this.value=e.target.value,this.__userInput=!1}_onChange(e){}_toggleHasValue(e){this.toggleAttribute("has-value",e)}_valueChanged(e,t){this._toggleHasValue(""!==e&&null!=e),""===e&&void 0===t||this.__userInput||this._forwardInputValue(e)}}));class Wo{constructor(e){this.host=e,e.addEventListener("opened-changed",(()=>{e.opened||this.__setVirtualKeyboardEnabled(!1)})),e.addEventListener("blur",(()=>this.__setVirtualKeyboardEnabled(!0))),e.addEventListener("touchstart",(()=>this.__setVirtualKeyboardEnabled(!0)))}__setVirtualKeyboardEnabled(e){this.host.inputElement&&(this.host.inputElement.inputMode=e?"":"none")}}const Ko=e=>class extends(ro(Ho(Go(Uo(e))))){static get properties(){return{opened:{type:Boolean,notify:!0,value:!1,reflectToAttribute:!0,observer:"_openedChanged"},autoOpenDisabled:{type:Boolean},readonly:{type:Boolean,value:!1,reflectToAttribute:!0},renderer:Function,items:{type:Array,observer:"_itemsChanged"},allowCustomValue:{type:Boolean,value:!1},filteredItems:{type:Array},_lastCommittedValue:String,loading:{type:Boolean,value:!1,reflectToAttribute:!0,observer:"_loadingChanged"},_focusedIndex:{type:Number,observer:"_focusedIndexChanged",value:-1},filter:{type:String,value:"",notify:!0},selectedItem:{type:Object,notify:!0},itemLabelPath:{type:String,value:"label",observer:"_itemLabelPathChanged"},itemValuePath:{type:String,value:"value"},itemIdPath:String,_toggleElement:{type:Object,observer:"_toggleElementChanged"},_closeOnBlurIsPrevented:Boolean,__restoreFocusOnClose:Boolean}}static get observers(){return["_filterChanged(filter, itemValuePath, itemLabelPath)","_itemsOrPathsChanged(items.*, itemValuePath, itemLabelPath)","_filteredItemsChanged(filteredItems.*, itemValuePath, itemLabelPath)","_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)"]}constructor(){super(),this._boundOnFocusout=this._onFocusout.bind(this),this._boundOverlaySelectedItemChanged=this._overlaySelectedItemChanged.bind(this),this._boundOnClearButtonMouseDown=this.__onClearButtonMouseDown.bind(this),this._boundClose=this.close.bind(this),this._boundOnOpened=this._onOpened.bind(this),this._boundOnClick=this._onClick.bind(this),this._boundOnOverlayTouchAction=this._onOverlayTouchAction.bind(this),this._boundOnTouchend=this._onTouchend.bind(this)}get _inputElementValue(){return this.inputElement?this.inputElement[this._propertyForValue]:void 0}set _inputElementValue(e){this.inputElement&&(this.inputElement[this._propertyForValue]=e)}_inputElementChanged(e){super._inputElementChanged(e),e&&(e.autocomplete="off",e.autocapitalize="off",e.setAttribute("role","combobox"),e.setAttribute("aria-autocomplete","list"),e.setAttribute("aria-expanded",!!this.opened),e.setAttribute("spellcheck","false"),e.setAttribute("autocorrect","off"),this._revertInputValueToValue(),this.clearElement&&this.clearElement.addEventListener("mousedown",this._boundOnClearButtonMouseDown))}ready(){super.ready(),this.addEventListener("focusout",this._boundOnFocusout),this._lastCommittedValue=this.value,this.$.dropdown.addEventListener("selection-changed",this._boundOverlaySelectedItemChanged),this.addEventListener("vaadin-combo-box-dropdown-closed",this._boundClose),this.addEventListener("vaadin-combo-box-dropdown-opened",this._boundOnOpened),this.addEventListener("click",this._boundOnClick),this.$.dropdown.addEventListener("vaadin-overlay-touch-action",this._boundOnOverlayTouchAction),this.addEventListener("touchend",this._boundOnTouchend);const e=()=>{requestAnimationFrame((()=>{this.$.dropdown.$.overlay.bringToFront()}))};var t;this.addEventListener("mousedown",e),this.addEventListener("touchstart",e),t=this,window.Vaadin&&window.Vaadin.templateRendererCallback?window.Vaadin.templateRendererCallback(t):t.querySelector("template")&&console.warn(`WARNING: